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