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