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