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