489 lines · cpp
1//===-- lib/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-exception6//7//===----------------------------------------------------------------------===//8 9#include "flang-rt/runtime/file.h"10#include "flang-rt/runtime/memory.h"11#include "flang-rt/runtime/tools.h"12#include "flang/Runtime/magic-numbers.h"13#include <algorithm>14#include <cerrno>15#include <cstring>16#include <fcntl.h>17#include <stdlib.h>18#include <sys/stat.h>19#ifdef _WIN3220#include "flang/Common/windows-include.h"21#include <io.h>22#else23#include <unistd.h>24#endif25 26namespace Fortran::runtime::io {27 28void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {29 path_ = std::move(path);30 pathLength_ = bytes;31}32 33static int openfile_mkstemp(IoErrorHandler &handler) {34#ifdef _WIN3235 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-gettempfilenamea38 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(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,49 _S_IREAD | _S_IWRITE)};50#else51 char path[]{"/tmp/Fortran-Scratch-XXXXXX"};52 int fd{::mkstemp(path)};53#endif54 if (fd < 0) {55 handler.SignalErrno();56 }57#ifndef _WIN3258 ::unlink(path);59#endif60 return fd;61}62 63void OpenFile::Open(OpenStatus status, common::optional<Action> action,64 Position position, IoErrorHandler &handler) {65 if (fd_ >= 0 &&66 (status == OpenStatus::Old || status == OpenStatus::Unknown)) {67 if (position == Position::Rewind) {68 Seek(0, handler);69 } else if (position == Position::Append) {70 SeekToEnd(handler);71 }72 openPosition_ = position; // for INQUIRE(POSITION=)73 return;74 }75 CloseFd(handler);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("FILE= is required");88 return;89 }90 int flags{0};91#ifdef _WIN3292 // We emit explicit CR+LF line endings and cope with them on input93 // for formatted files, since we can't yet always know now at OPEN94 // time whether the file is formatted or not.95 flags |= O_BINARY;96#endif97 if (status != OpenStatus::Old) {98 flags |= O_CREAT;99 }100 if (status == OpenStatus::New) {101 flags |= O_EXCL;102 } else if (status == OpenStatus::Replace) {103 flags |= O_TRUNC;104 }105 if (!action) {106 // Try to open read/write, back off to read-only or even write-only107 // on failure108 fd_ = ::open(path_.get(), flags | O_RDWR, 0600);109 if (fd_ >= 0) {110 action = Action::ReadWrite;111 } else {112 fd_ = ::open(path_.get(), flags | O_RDONLY, 0600);113 if (fd_ >= 0) {114 action = Action::Read;115 } else {116 action = Action::Write;117 }118 }119 }120 if (fd_ < 0) {121 switch (*action) {122 case Action::Read:123 flags |= O_RDONLY;124 break;125 case Action::Write:126 flags |= O_WRONLY;127 break;128 case Action::ReadWrite:129 flags |= O_RDWR;130 break;131 }132 fd_ = ::open(path_.get(), flags, 0600);133 if (fd_ < 0) {134 handler.SignalErrno();135 }136 }137 }138 RUNTIME_CHECK(handler, action.has_value());139 pending_.reset();140 if (fd_ >= 0 && position == Position::Append) {141 SeekToEnd(handler);142 }143 isTerminal_ = fd_ >= 0 && IsATerminal(fd_);144 mayRead_ = *action != Action::Write;145 mayWrite_ = *action != Action::Read;146 if (status == OpenStatus::Old || status == OpenStatus::Unknown) {147 knownSize_.reset();148#ifndef _WIN32149 struct stat buf;150 if (fd_ >= 0 && ::fstat(fd_, &buf) == 0) {151 mayPosition_ = S_ISREG(buf.st_mode);152 knownSize_ = buf.st_size;153 }154#else // TODO: _WIN32155 mayPosition_ = true;156#endif157 } else {158 knownSize_ = 0;159 mayPosition_ = true;160 }161 openPosition_ = position; // for INQUIRE(POSITION=)162}163 164void OpenFile::Predefine(int fd) {165 fd_ = fd;166 path_.reset();167 pathLength_ = 0;168 position_ = 0;169 knownSize_.reset();170 nextId_ = 0;171 pending_.reset();172 isTerminal_ = fd == 2 || IsATerminal(fd_);173 mayRead_ = fd == 0;174 mayWrite_ = fd != 0;175 mayPosition_ = false;176#ifdef _WIN32177 isWindowsTextFile_ = true;178#endif179}180 181void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {182 pending_.reset();183 knownSize_.reset();184 switch (status) {185 case CloseStatus::Keep:186 break;187 case CloseStatus::Delete:188 if (path_.get()) {189 ::unlink(path_.get());190 }191 break;192 }193 path_.reset();194 CloseFd(handler);195}196 197std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,198 std::size_t maxBytes, IoErrorHandler &handler) {199 if (maxBytes == 0) {200 return 0;201 }202 CheckOpen(handler);203 if (!Seek(at, handler)) {204 return 0;205 }206 minBytes = std::min(minBytes, maxBytes);207 std::size_t got{0};208 while (got < minBytes) {209 auto chunk{::read(fd_, buffer + got, maxBytes - got)};210 if (chunk == 0) {211 break;212 } else if (chunk < 0) {213 auto err{errno};214 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {215 handler.SignalError(err);216 break;217 }218 } else {219 SetPosition(position_ + chunk);220 got += chunk;221 }222 }223 return got;224}225 226std::size_t OpenFile::Write(FileOffset at, const char *buffer,227 std::size_t bytes, IoErrorHandler &handler) {228 if (bytes == 0) {229 return 0;230 }231 CheckOpen(handler);232 if (!Seek(at, handler)) {233 return 0;234 }235 std::size_t put{0};236 while (put < bytes) {237 auto chunk{::write(fd_, buffer + put, bytes - put)};238 if (chunk >= 0) {239 SetPosition(position_ + chunk);240 put += chunk;241 } else {242 auto err{errno};243 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {244 handler.SignalError(err);245 break;246 }247 }248 }249 if (knownSize_ && position_ > *knownSize_) {250 knownSize_ = position_;251 }252 return put;253}254 255inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {256#ifdef _WIN32257 return ::_chsize(fd, at);258#else259 return ::ftruncate(fd, at);260#endif261}262 263void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {264 CheckOpen(handler);265 if (!knownSize_ || *knownSize_ != at) {266 if (openfile_ftruncate(fd_, at) != 0) {267 handler.SignalErrno();268 }269 knownSize_ = at;270 }271}272 273// The operation is performed immediately; the results are saved274// to be claimed by a later WAIT statement.275// TODO: True asynchronicity276int OpenFile::ReadAsynchronously(277 FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {278 CheckOpen(handler);279 int iostat{0};280 for (std::size_t got{0}; got < bytes;) {281#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L282 auto chunk{::pread(fd_, buffer + got, bytes - got, at)};283#else284 auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};285#endif286 if (chunk == 0) {287 iostat = FORTRAN_RUNTIME_IOSTAT_END;288 break;289 }290 if (chunk < 0) {291 auto err{errno};292 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {293 iostat = err;294 break;295 }296 } else {297 at += chunk;298 got += chunk;299 }300 }301 return PendingResult(handler, iostat);302}303 304// TODO: True asynchronicity305int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,306 std::size_t bytes, IoErrorHandler &handler) {307 CheckOpen(handler);308 int iostat{0};309 for (std::size_t put{0}; put < bytes;) {310#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L311 auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};312#else313 auto chunk{314 Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};315#endif316 if (chunk >= 0) {317 at += chunk;318 put += chunk;319 } else {320 auto err{errno};321 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {322 iostat = err;323 break;324 }325 }326 }327 return PendingResult(handler, iostat);328}329 330void OpenFile::Wait(int id, IoErrorHandler &handler) {331 common::optional<int> ioStat;332 Pending *prev{nullptr};333 for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {334 if (p->id == id) {335 ioStat = p->ioStat;336 if (prev) {337 prev->next.reset(p->next.release());338 } else {339 pending_.reset(p->next.release());340 }341 break;342 }343 }344 if (ioStat) {345 handler.SignalError(*ioStat);346 }347}348 349void OpenFile::WaitAll(IoErrorHandler &handler) {350 while (true) {351 int ioStat;352 if (pending_) {353 ioStat = pending_->ioStat;354 pending_.reset(pending_->next.release());355 } else {356 return;357 }358 handler.SignalError(ioStat);359 }360}361 362Position OpenFile::InquirePosition(FileOffset offset) const {363 if (openPosition_) { // from OPEN statement364 return *openPosition_;365 } else { // unit has been repositioned since opening366 if (offset == knownSize_.value_or(offset + 1)) {367 return Position::Append;368 } else if (offset == 0 && mayPosition_) {369 return Position::Rewind;370 } else {371 return Position::AsIs; // processor-dependent & no common behavior372 }373 }374}375 376void OpenFile::CheckOpen(const Terminator &terminator) {377 RUNTIME_CHECK(terminator, fd_ >= 0);378}379 380bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {381 if (at == position_) {382 return true;383 } else if (RawSeek(at)) {384 SetPosition(at);385 return true;386 } else {387 handler.SignalError(IostatCannotReposition);388 return false;389 }390}391 392bool OpenFile::RawSeek(FileOffset at) {393#ifdef _LARGEFILE64_SOURCE394 return ::lseek64(fd_, at, SEEK_SET) == at;395#else396 return ::lseek(fd_, at, SEEK_SET) == at;397#endif398}399 400bool OpenFile::SeekToEnd(IoErrorHandler &handler) {401#ifdef _LARGEFILE64_SOURCE402 std::int64_t at{::lseek64(fd_, 0, SEEK_END)};403#else404 std::int64_t at{::lseek(fd_, 0, SEEK_END)};405#endif406 if (at >= 0) {407 knownSize_ = at;408 SetPosition(at);409 return true;410 } else {411 handler.SignalError(IostatOpenBadAppend);412 return false;413 }414}415 416int OpenFile::PendingResult(const Terminator &terminator, int iostat) {417 int id{nextId_++};418 pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));419 return id;420}421 422void OpenFile::CloseFd(IoErrorHandler &handler) {423 if (fd_ >= 0) {424 if (fd_ <= 2) {425 // don't actually close a standard file descriptor, we might need it426 } else {427 if (::close(fd_) != 0) {428 handler.SignalErrno();429 }430 }431 fd_ = -1;432 }433}434 435#if !defined(RT_DEVICE_COMPILATION)436bool IsATerminal(int fd) { return ::isatty(fd); }437 438#if defined(_WIN32) && !defined(F_OK)439// Access flags are normally defined in unistd.h, which unavailable under440// Windows. Instead, define the flags as documented at441// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess442// On Mingw, io.h does define these same constants - so check whether they443// already are defined before defining these.444#define F_OK 00445#define W_OK 02446#define R_OK 04447#endif448 449bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }450bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }451bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }452bool MayReadAndWrite(const char *path) {453 return ::access(path, R_OK | W_OK) == 0;454}455 456std::int64_t SizeInBytes(const char *path) {457#ifndef _WIN32458 struct stat buf;459 if (::stat(path, &buf) == 0) {460 return buf.st_size;461 }462#else // TODO: _WIN32463#endif464 // No Fortran compiler signals an error465 return -1;466}467#else // defined(RT_DEVICE_COMPILATION)468RT_API_ATTRS bool IsATerminal(int fd) {469 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);470}471RT_API_ATTRS bool IsExtant(const char *path) {472 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);473}474RT_API_ATTRS bool MayRead(const char *path) {475 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);476}477RT_API_ATTRS bool MayWrite(const char *path) {478 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);479}480RT_API_ATTRS bool MayReadAndWrite(const char *path) {481 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);482}483RT_API_ATTRS std::int64_t SizeInBytes(const char *path) {484 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);485}486#endif // defined(RT_DEVICE_COMPILATION)487 488} // namespace Fortran::runtime::io489