1 //===-- runtime/file.cpp ----------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "file.h" 10 #include "magic-numbers.h" 11 #include "memory.h" 12 #include <algorithm> 13 #include <cerrno> 14 #include <cstring> 15 #include <fcntl.h> 16 #include <stdlib.h> 17 #ifdef _WIN32 18 #include <io.h> 19 #include <windows.h> 20 #else 21 #include <sys/stat.h> 22 #include <unistd.h> 23 #endif 24 25 namespace Fortran::runtime::io { 26 27 void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) { 28 path_ = std::move(path); 29 pathLength_ = bytes; 30 } 31 32 static int openfile_mkstemp(IoErrorHandler &handler) { 33 #ifdef _WIN32 34 const unsigned int uUnique{0}; 35 // GetTempFileNameA needs a directory name < MAX_PATH-14 characters in length. 36 // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea 37 char tempDirName[MAX_PATH - 14]; 38 char tempFileName[MAX_PATH]; 39 unsigned long nBufferLength{sizeof(tempDirName)}; 40 nBufferLength = ::GetTempPathA(nBufferLength, tempDirName); 41 if (nBufferLength > sizeof(tempDirName) || nBufferLength == 0) { 42 return -1; 43 } 44 if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) { 45 return -1; 46 } 47 int fd{::_open(tempFileName, _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE)}; 48 #else 49 char path[]{"/tmp/Fortran-Scratch-XXXXXX"}; 50 int fd{::mkstemp(path)}; 51 #endif 52 if (fd < 0) { 53 handler.SignalErrno(); 54 } 55 #ifndef _WIN32 56 ::unlink(path); 57 #endif 58 return fd; 59 } 60 61 void OpenFile::Open(OpenStatus status, std::optional<Action> action, 62 Position position, IoErrorHandler &handler) { 63 if (fd_ >= 0 && 64 (status == OpenStatus::Old || status == OpenStatus::Unknown)) { 65 return; 66 } 67 if (fd_ >= 0) { 68 if (fd_ <= 2) { 69 // don't actually close a standard file descriptor, we might need it 70 } else { 71 if (::close(fd_) != 0) { 72 handler.SignalErrno(); 73 } 74 } 75 fd_ = -1; 76 } 77 if (status == OpenStatus::Scratch) { 78 if (path_.get()) { 79 handler.SignalError("FILE= must not appear with STATUS='SCRATCH'"); 80 path_.reset(); 81 } 82 if (!action) { 83 action = Action::ReadWrite; 84 } 85 fd_ = openfile_mkstemp(handler); 86 } else { 87 if (!path_.get()) { 88 handler.SignalError("FILE= is required"); 89 return; 90 } 91 int flags{0}; 92 if (status != OpenStatus::Old) { 93 flags |= O_CREAT; 94 } 95 if (status == OpenStatus::New) { 96 flags |= O_EXCL; 97 } else if (status == OpenStatus::Replace) { 98 flags |= O_TRUNC; 99 } 100 if (!action) { 101 // Try to open read/write, back off to read-only on failure 102 fd_ = ::open(path_.get(), flags | O_RDWR, 0600); 103 if (fd_ >= 0) { 104 action = Action::ReadWrite; 105 } else { 106 action = Action::Read; 107 } 108 } 109 if (fd_ < 0) { 110 switch (*action) { 111 case Action::Read: 112 flags |= O_RDONLY; 113 break; 114 case Action::Write: 115 flags |= O_WRONLY; 116 break; 117 case Action::ReadWrite: 118 flags |= O_RDWR; 119 break; 120 } 121 fd_ = ::open(path_.get(), flags, 0600); 122 if (fd_ < 0) { 123 handler.SignalErrno(); 124 } 125 } 126 } 127 RUNTIME_CHECK(handler, action.has_value()); 128 pending_.reset(); 129 if (position == Position::Append && !RawSeekToEnd()) { 130 handler.SignalErrno(); 131 } 132 isTerminal_ = ::isatty(fd_) == 1; 133 mayRead_ = *action != Action::Write; 134 mayWrite_ = *action != Action::Read; 135 if (status == OpenStatus::Old || status == OpenStatus::Unknown) { 136 knownSize_.reset(); 137 #ifndef _WIN32 138 struct stat buf; 139 if (::fstat(fd_, &buf) == 0) { 140 mayPosition_ = S_ISREG(buf.st_mode); 141 knownSize_ = buf.st_size; 142 } 143 #else // TODO: _WIN32 144 mayPosition_ = true; 145 #endif 146 } else { 147 knownSize_ = 0; 148 mayPosition_ = true; 149 } 150 } 151 152 void OpenFile::Predefine(int fd) { 153 fd_ = fd; 154 path_.reset(); 155 pathLength_ = 0; 156 position_ = 0; 157 knownSize_.reset(); 158 nextId_ = 0; 159 pending_.reset(); 160 mayRead_ = fd == 0; 161 mayWrite_ = fd != 0; 162 mayPosition_ = false; 163 } 164 165 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) { 166 CheckOpen(handler); 167 pending_.reset(); 168 knownSize_.reset(); 169 switch (status) { 170 case CloseStatus::Keep: 171 break; 172 case CloseStatus::Delete: 173 if (path_.get()) { 174 ::unlink(path_.get()); 175 } 176 break; 177 } 178 path_.reset(); 179 if (fd_ >= 0) { 180 if (::close(fd_) != 0) { 181 handler.SignalErrno(); 182 } 183 fd_ = -1; 184 } 185 } 186 187 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes, 188 std::size_t maxBytes, IoErrorHandler &handler) { 189 if (maxBytes == 0) { 190 return 0; 191 } 192 CheckOpen(handler); 193 if (!Seek(at, handler)) { 194 return 0; 195 } 196 minBytes = std::min(minBytes, maxBytes); 197 std::size_t got{0}; 198 while (got < minBytes) { 199 auto chunk{::read(fd_, buffer + got, maxBytes - got)}; 200 if (chunk == 0) { 201 handler.SignalEnd(); 202 break; 203 } else if (chunk < 0) { 204 auto err{errno}; 205 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 206 handler.SignalError(err); 207 break; 208 } 209 } else { 210 position_ += chunk; 211 got += chunk; 212 } 213 } 214 return got; 215 } 216 217 std::size_t OpenFile::Write(FileOffset at, const char *buffer, 218 std::size_t bytes, IoErrorHandler &handler) { 219 if (bytes == 0) { 220 return 0; 221 } 222 CheckOpen(handler); 223 if (!Seek(at, handler)) { 224 return 0; 225 } 226 std::size_t put{0}; 227 while (put < bytes) { 228 auto chunk{::write(fd_, buffer + put, bytes - put)}; 229 if (chunk >= 0) { 230 position_ += chunk; 231 put += chunk; 232 } else { 233 auto err{errno}; 234 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 235 handler.SignalError(err); 236 break; 237 } 238 } 239 } 240 if (knownSize_ && position_ > *knownSize_) { 241 knownSize_ = position_; 242 } 243 return put; 244 } 245 246 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) { 247 #ifdef _WIN32 248 return !::_chsize(fd, at); 249 #else 250 return ::ftruncate(fd, at); 251 #endif 252 } 253 254 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) { 255 CheckOpen(handler); 256 if (!knownSize_ || *knownSize_ != at) { 257 if (openfile_ftruncate(fd_, at) != 0) { 258 handler.SignalErrno(); 259 } 260 knownSize_ = at; 261 } 262 } 263 264 // The operation is performed immediately; the results are saved 265 // to be claimed by a later WAIT statement. 266 // TODO: True asynchronicity 267 int OpenFile::ReadAsynchronously( 268 FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) { 269 CheckOpen(handler); 270 int iostat{0}; 271 for (std::size_t got{0}; got < bytes;) { 272 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L 273 auto chunk{::pread(fd_, buffer + got, bytes - got, at)}; 274 #else 275 auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1}; 276 #endif 277 if (chunk == 0) { 278 iostat = FORTRAN_RUNTIME_IOSTAT_END; 279 break; 280 } 281 if (chunk < 0) { 282 auto err{errno}; 283 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 284 iostat = err; 285 break; 286 } 287 } else { 288 at += chunk; 289 got += chunk; 290 } 291 } 292 return PendingResult(handler, iostat); 293 } 294 295 // TODO: True asynchronicity 296 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer, 297 std::size_t bytes, IoErrorHandler &handler) { 298 CheckOpen(handler); 299 int iostat{0}; 300 for (std::size_t put{0}; put < bytes;) { 301 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L 302 auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)}; 303 #else 304 auto chunk{ 305 Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1}; 306 #endif 307 if (chunk >= 0) { 308 at += chunk; 309 put += chunk; 310 } else { 311 auto err{errno}; 312 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 313 iostat = err; 314 break; 315 } 316 } 317 } 318 return PendingResult(handler, iostat); 319 } 320 321 void OpenFile::Wait(int id, IoErrorHandler &handler) { 322 std::optional<int> ioStat; 323 Pending *prev{nullptr}; 324 for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) { 325 if (p->id == id) { 326 ioStat = p->ioStat; 327 if (prev) { 328 prev->next.reset(p->next.release()); 329 } else { 330 pending_.reset(p->next.release()); 331 } 332 break; 333 } 334 } 335 if (ioStat) { 336 handler.SignalError(*ioStat); 337 } 338 } 339 340 void OpenFile::WaitAll(IoErrorHandler &handler) { 341 while (true) { 342 int ioStat; 343 if (pending_) { 344 ioStat = pending_->ioStat; 345 pending_.reset(pending_->next.release()); 346 } else { 347 return; 348 } 349 handler.SignalError(ioStat); 350 } 351 } 352 353 void OpenFile::CheckOpen(const Terminator &terminator) { 354 RUNTIME_CHECK(terminator, fd_ >= 0); 355 } 356 357 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) { 358 if (at == position_) { 359 return true; 360 } else if (RawSeek(at)) { 361 position_ = at; 362 return true; 363 } else { 364 handler.SignalErrno(); 365 return false; 366 } 367 } 368 369 bool OpenFile::RawSeek(FileOffset at) { 370 #ifdef _LARGEFILE64_SOURCE 371 return ::lseek64(fd_, at, SEEK_SET) == at; 372 #else 373 return ::lseek(fd_, at, SEEK_SET) == at; 374 #endif 375 } 376 377 bool OpenFile::RawSeekToEnd() { 378 #ifdef _LARGEFILE64_SOURCE 379 std::int64_t at{::lseek64(fd_, 0, SEEK_END)}; 380 #else 381 std::int64_t at{::lseek(fd_, 0, SEEK_END)}; 382 #endif 383 if (at >= 0) { 384 knownSize_ = at; 385 return true; 386 } else { 387 return false; 388 } 389 } 390 391 int OpenFile::PendingResult(const Terminator &terminator, int iostat) { 392 int id{nextId_++}; 393 pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_)); 394 return id; 395 } 396 397 bool IsATerminal(int fd) { return ::isatty(fd); } 398 399 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; } 400 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; } 401 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; } 402 bool MayReadAndWrite(const char *path) { 403 return ::access(path, R_OK | W_OK) == 0; 404 } 405 } // namespace Fortran::runtime::io 406