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