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