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