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 or even write-only 101 // on failure 102 fd_ = ::open(path_.get(), flags | O_RDWR, 0600); 103 if (fd_ >= 0) { 104 action = Action::ReadWrite; 105 } else { 106 fd_ = ::open(path_.get(), flags | O_RDONLY, 0600); 107 if (fd_ >= 0) { 108 action = Action::Read; 109 } else { 110 action = Action::Write; 111 } 112 } 113 } 114 if (fd_ < 0) { 115 switch (*action) { 116 case Action::Read: 117 flags |= O_RDONLY; 118 break; 119 case Action::Write: 120 flags |= O_WRONLY; 121 break; 122 case Action::ReadWrite: 123 flags |= O_RDWR; 124 break; 125 } 126 fd_ = ::open(path_.get(), flags, 0600); 127 if (fd_ < 0) { 128 handler.SignalErrno(); 129 } 130 } 131 } 132 RUNTIME_CHECK(handler, action.has_value()); 133 pending_.reset(); 134 if (position == Position::Append && !RawSeekToEnd()) { 135 handler.SignalError(IostatOpenBadAppend); 136 } 137 isTerminal_ = ::isatty(fd_) == 1; 138 mayRead_ = *action != Action::Write; 139 mayWrite_ = *action != Action::Read; 140 if (status == OpenStatus::Old || status == OpenStatus::Unknown) { 141 knownSize_.reset(); 142 #ifndef _WIN32 143 struct stat buf; 144 if (::fstat(fd_, &buf) == 0) { 145 mayPosition_ = S_ISREG(buf.st_mode); 146 knownSize_ = buf.st_size; 147 } 148 #else // TODO: _WIN32 149 mayPosition_ = true; 150 #endif 151 } else { 152 knownSize_ = 0; 153 mayPosition_ = true; 154 } 155 openPosition_ = position; // for INQUIRE(POSITION=) 156 } 157 158 void OpenFile::Predefine(int fd) { 159 fd_ = fd; 160 path_.reset(); 161 pathLength_ = 0; 162 position_ = 0; 163 knownSize_.reset(); 164 nextId_ = 0; 165 pending_.reset(); 166 mayRead_ = fd == 0; 167 mayWrite_ = fd != 0; 168 mayPosition_ = false; 169 #ifdef _WIN32 170 isWindowsTextFile_ = true; 171 #endif 172 } 173 174 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) { 175 pending_.reset(); 176 knownSize_.reset(); 177 switch (status) { 178 case CloseStatus::Keep: 179 break; 180 case CloseStatus::Delete: 181 if (path_.get()) { 182 ::unlink(path_.get()); 183 } 184 break; 185 } 186 path_.reset(); 187 CloseFd(handler); 188 } 189 190 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes, 191 std::size_t maxBytes, IoErrorHandler &handler) { 192 if (maxBytes == 0) { 193 return 0; 194 } 195 CheckOpen(handler); 196 if (!Seek(at, handler)) { 197 return 0; 198 } 199 minBytes = std::min(minBytes, maxBytes); 200 std::size_t got{0}; 201 while (got < minBytes) { 202 auto chunk{::read(fd_, buffer + got, maxBytes - got)}; 203 if (chunk == 0) { 204 break; 205 } else if (chunk < 0) { 206 auto err{errno}; 207 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 208 handler.SignalError(err); 209 break; 210 } 211 } else { 212 SetPosition(position_ + chunk); 213 got += chunk; 214 } 215 } 216 return got; 217 } 218 219 std::size_t OpenFile::Write(FileOffset at, const char *buffer, 220 std::size_t bytes, IoErrorHandler &handler) { 221 if (bytes == 0) { 222 return 0; 223 } 224 CheckOpen(handler); 225 if (!Seek(at, handler)) { 226 return 0; 227 } 228 std::size_t put{0}; 229 while (put < bytes) { 230 auto chunk{::write(fd_, buffer + put, bytes - put)}; 231 if (chunk >= 0) { 232 SetPosition(position_ + chunk); 233 put += chunk; 234 } else { 235 auto err{errno}; 236 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 237 handler.SignalError(err); 238 break; 239 } 240 } 241 } 242 if (knownSize_ && position_ > *knownSize_) { 243 knownSize_ = position_; 244 } 245 return put; 246 } 247 248 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) { 249 #ifdef _WIN32 250 return ::_chsize(fd, at); 251 #else 252 return ::ftruncate(fd, at); 253 #endif 254 } 255 256 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) { 257 CheckOpen(handler); 258 if (!knownSize_ || *knownSize_ != at) { 259 if (openfile_ftruncate(fd_, at) != 0) { 260 handler.SignalErrno(); 261 } 262 knownSize_ = at; 263 } 264 } 265 266 // The operation is performed immediately; the results are saved 267 // to be claimed by a later WAIT statement. 268 // TODO: True asynchronicity 269 int OpenFile::ReadAsynchronously( 270 FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) { 271 CheckOpen(handler); 272 int iostat{0}; 273 for (std::size_t got{0}; got < bytes;) { 274 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L 275 auto chunk{::pread(fd_, buffer + got, bytes - got, at)}; 276 #else 277 auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1}; 278 #endif 279 if (chunk == 0) { 280 iostat = FORTRAN_RUNTIME_IOSTAT_END; 281 break; 282 } 283 if (chunk < 0) { 284 auto err{errno}; 285 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 286 iostat = err; 287 break; 288 } 289 } else { 290 at += chunk; 291 got += chunk; 292 } 293 } 294 return PendingResult(handler, iostat); 295 } 296 297 // TODO: True asynchronicity 298 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer, 299 std::size_t bytes, IoErrorHandler &handler) { 300 CheckOpen(handler); 301 int iostat{0}; 302 for (std::size_t put{0}; put < bytes;) { 303 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L 304 auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)}; 305 #else 306 auto chunk{ 307 Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1}; 308 #endif 309 if (chunk >= 0) { 310 at += chunk; 311 put += chunk; 312 } else { 313 auto err{errno}; 314 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { 315 iostat = err; 316 break; 317 } 318 } 319 } 320 return PendingResult(handler, iostat); 321 } 322 323 void OpenFile::Wait(int id, IoErrorHandler &handler) { 324 std::optional<int> ioStat; 325 Pending *prev{nullptr}; 326 for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) { 327 if (p->id == id) { 328 ioStat = p->ioStat; 329 if (prev) { 330 prev->next.reset(p->next.release()); 331 } else { 332 pending_.reset(p->next.release()); 333 } 334 break; 335 } 336 } 337 if (ioStat) { 338 handler.SignalError(*ioStat); 339 } 340 } 341 342 void OpenFile::WaitAll(IoErrorHandler &handler) { 343 while (true) { 344 int ioStat; 345 if (pending_) { 346 ioStat = pending_->ioStat; 347 pending_.reset(pending_->next.release()); 348 } else { 349 return; 350 } 351 handler.SignalError(ioStat); 352 } 353 } 354 355 Position OpenFile::InquirePosition() const { 356 if (openPosition_) { // from OPEN statement 357 return *openPosition_; 358 } else { // unit has been repositioned since opening 359 if (position_ == knownSize_.value_or(position_ + 1)) { 360 return Position::Append; 361 } else if (position_ == 0 && mayPosition_) { 362 return Position::Rewind; 363 } else { 364 return Position::AsIs; // processor-dependent & no common behavior 365 } 366 } 367 } 368 369 void OpenFile::CheckOpen(const Terminator &terminator) { 370 RUNTIME_CHECK(terminator, fd_ >= 0); 371 } 372 373 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) { 374 if (at == position_) { 375 return true; 376 } else if (RawSeek(at)) { 377 SetPosition(at); 378 return true; 379 } else { 380 handler.SignalError(IostatCannotReposition); 381 return false; 382 } 383 } 384 385 bool OpenFile::RawSeek(FileOffset at) { 386 #ifdef _LARGEFILE64_SOURCE 387 return ::lseek64(fd_, at, SEEK_SET) == at; 388 #else 389 return ::lseek(fd_, at, SEEK_SET) == at; 390 #endif 391 } 392 393 bool OpenFile::RawSeekToEnd() { 394 #ifdef _LARGEFILE64_SOURCE 395 std::int64_t at{::lseek64(fd_, 0, SEEK_END)}; 396 #else 397 std::int64_t at{::lseek(fd_, 0, SEEK_END)}; 398 #endif 399 if (at >= 0) { 400 knownSize_ = at; 401 return true; 402 } else { 403 return false; 404 } 405 } 406 407 int OpenFile::PendingResult(const Terminator &terminator, int iostat) { 408 int id{nextId_++}; 409 pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_)); 410 return id; 411 } 412 413 void OpenFile::CloseFd(IoErrorHandler &handler) { 414 if (fd_ >= 0) { 415 if (fd_ <= 2) { 416 // don't actually close a standard file descriptor, we might need it 417 } else { 418 if (::close(fd_) != 0) { 419 handler.SignalErrno(); 420 } 421 } 422 fd_ = -1; 423 } 424 } 425 426 bool IsATerminal(int fd) { return ::isatty(fd); } 427 428 #ifdef WIN32 429 // Access flags are normally defined in unistd.h, which unavailable under 430 // Windows. Instead, define the flags as documented at 431 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess 432 #define F_OK 00 433 #define W_OK 02 434 #define R_OK 04 435 #endif 436 437 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; } 438 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; } 439 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; } 440 bool MayReadAndWrite(const char *path) { 441 return ::access(path, R_OK | W_OK) == 0; 442 } 443 444 std::int64_t SizeInBytes(const char *path) { 445 #ifndef _WIN32 446 struct stat buf; 447 if (::stat(path, &buf) == 0) { 448 return buf.st_size; 449 } 450 #else // TODO: _WIN32 451 #endif 452 // No Fortran compiler signals an error 453 return -1; 454 } 455 456 } // namespace Fortran::runtime::io 457