diff --git a/flang/runtime/file.h b/flang/runtime/file.h --- a/flang/runtime/file.h +++ b/flang/runtime/file.h @@ -21,6 +21,7 @@ enum class OpenStatus { Old, New, Scratch, Replace, Unknown }; enum class CloseStatus { Keep, Delete }; enum class Position { AsIs, Rewind, Append }; +enum class Action { Read, Write, ReadWrite }; class OpenFile { public: @@ -30,19 +31,16 @@ void set_path(OwningPtr &&, std::size_t bytes); std::size_t pathLength() const { return pathLength_; } bool mayRead() const { return mayRead_; } - void set_mayRead(bool yes) { mayRead_ = yes; } bool mayWrite() const { return mayWrite_; } - void set_mayWrite(bool yes) { mayWrite_ = yes; } + bool mayPosition() const { return mayPosition_; } bool mayAsynchronous() const { return mayAsynchronous_; } void set_mayAsynchronous(bool yes) { mayAsynchronous_ = yes; } - bool mayPosition() const { return mayPosition_; } - void set_mayPosition(bool yes) { mayPosition_ = yes; } FileOffset position() const { return position_; } bool isTerminal() const { return isTerminal_; } std::optional knownSize() const { return knownSize_; } bool IsOpen() const { return fd_ >= 0; } - void Open(OpenStatus, Position, IoErrorHandler &); + void Open(OpenStatus, std::optional, Position, IoErrorHandler &); void Predefine(int fd); void Close(CloseStatus, IoErrorHandler &); diff --git a/flang/runtime/file.cpp b/flang/runtime/file.cpp --- a/flang/runtime/file.cpp +++ b/flang/runtime/file.cpp @@ -57,63 +57,86 @@ return fd; } -void OpenFile::Open( - OpenStatus status, Position position, IoErrorHandler &handler) { - int flags{mayRead_ ? mayWrite_ ? O_RDWR : O_RDONLY : O_WRONLY}; - switch (status) { - case OpenStatus::Old: - if (fd_ >= 0) { - return; +void OpenFile::Open(OpenStatus status, std::optional action, + Position position, IoErrorHandler &handler) { + if (fd_ >= 0 && + (status == OpenStatus::Old || status == OpenStatus::Unknown)) { + return; + } + if (fd_ >= 0) { + if (fd_ <= 2) { + // don't actually close a standard file descriptor, we might need it + } else { + if (::close(fd_) != 0) { + handler.SignalErrno(); + } } - knownSize_.reset(); - break; - case OpenStatus::New: - flags |= O_CREAT | O_EXCL; - knownSize_ = 0; - break; - case OpenStatus::Scratch: + fd_ = -1; + } + if (status == OpenStatus::Scratch) { if (path_.get()) { handler.SignalError("FILE= must not appear with STATUS='SCRATCH'"); path_.reset(); } + if (!action) { + action = Action::ReadWrite; + } fd_ = openfile_mkstemp(handler); - knownSize_ = 0; - return; - case OpenStatus::Replace: - flags |= O_CREAT | O_TRUNC; - knownSize_ = 0; - break; - case OpenStatus::Unknown: - if (fd_ >= 0) { + } else { + if (!path_.get()) { + handler.SignalError( + "FILE= is required unless STATUS='OLD' and unit is connected"); return; } - flags |= O_CREAT; - knownSize_.reset(); - break; - } - // If we reach this point, we're opening a new file. - // TODO: Fortran shouldn't create a new file until the first WRITE. - if (fd_ >= 0) { - if (fd_ <= 2) { - // don't actually close a standard file descriptor, we might need it - } else if (::close(fd_) != 0) { - handler.SignalErrno(); + int flags{0}; + if (status != OpenStatus::Old) { + flags |= O_CREAT; + } + if (status == OpenStatus::New) { + flags |= O_EXCL; + } else if (status == OpenStatus::Replace) { + flags |= O_TRUNC; + } + if (!action) { + // Try to open read/write, back off to read-only on failure + fd_ = ::open(path_.get(), flags | O_RDWR, 0600); + if (fd_ >= 0) { + action = Action::ReadWrite; + } else { + action = Action::Read; + } + } + if (fd_ < 0) { + switch (*action) { + case Action::Read: + flags |= O_RDONLY; + break; + case Action::Write: + flags |= O_WRONLY; + break; + case Action::ReadWrite: + flags |= O_RDWR; + break; + } + fd_ = ::open(path_.get(), flags, 0600); + if (fd_ < 0) { + handler.SignalErrno(); + } } } - if (!path_.get()) { - handler.SignalError( - "FILE= is required unless STATUS='OLD' and unit is connected"); - return; - } - fd_ = ::open(path_.get(), flags, 0600); - if (fd_ < 0) { - handler.SignalErrno(); - } + RUNTIME_CHECK(handler, action.has_value()); pending_.reset(); if (position == Position::Append && !RawSeekToEnd()) { handler.SignalErrno(); } isTerminal_ = ::isatty(fd_) == 1; + mayRead_ = *action != Action::Write; + mayWrite_ = *action != Action::Read; + if (status == OpenStatus::Old || status == OpenStatus::Unknown) { + knownSize_.reset(); + } else { + knownSize_ = 0; + } } void OpenFile::Predefine(int fd) { @@ -124,6 +147,9 @@ knownSize_.reset(); nextId_ = 0; pending_.reset(); + mayRead_ = fd == 0; + mayWrite_ = fd != 0; + mayPosition_ = false; } void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) { diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -539,31 +539,31 @@ io.GetIoErrorHandler().Crash( "SetAction() called when not in an OPEN statement"); } - bool mayRead{true}; - bool mayWrite{true}; + std::optional action; static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { case 0: - mayWrite = false; + action = Action::Read; break; case 1: - mayRead = false; + action = Action::Write; break; case 2: + action = Action::ReadWrite; break; default: open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'", static_cast(length), keyword); return false; } - if (mayRead != open->unit().mayRead() || - mayWrite != open->unit().mayWrite()) { - if (open->wasExtant()) { + RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value()); + if (open->wasExtant()) { + if ((*action != Action::Write) != open->unit().mayRead() || + (*action != Action::Read) != open->unit().mayWrite()) { open->SignalError("ACTION= may not be changed on an open unit"); } - open->unit().set_mayRead(mayRead); - open->unit().set_mayWrite(mayWrite); } + open->set_action(*action); return true; } diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -294,15 +294,17 @@ : ExternalIoStatementBase{unit, sourceFile, sourceLine}, wasExtant_{ wasExtant} {} bool wasExtant() const { return wasExtant_; } - void set_status(OpenStatus status) { status_ = status; } + void set_status(OpenStatus status) { status_ = status; } // STATUS= void set_path(const char *, std::size_t, int kind); // FILE= void set_position(Position position) { position_ = position; } // POSITION= + void set_action(Action action) { action_ = action; } // ACTION= int EndIoStatement(); private: bool wasExtant_; OpenStatus status_{OpenStatus::Unknown}; Position position_{Position::AsIs}; + std::optional action_; OwningPtr path_; std::size_t pathLength_; }; diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -165,7 +165,8 @@ if (wasExtant_ && status_ != OpenStatus::Old) { SignalError("OPEN statement for connected unit must have STATUS='OLD'"); } - unit().OpenUnit(status_, position_, std::move(path_), pathLength_, *this); + unit().OpenUnit( + status_, action_, position_, std::move(path_), pathLength_, *this); return ExternalIoStatementBase::EndIoStatement(); } diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -48,8 +48,8 @@ static void CloseAll(IoErrorHandler &); static void FlushAll(IoErrorHandler &); - void OpenUnit(OpenStatus, Position, OwningPtr &&path, - std::size_t pathLength, IoErrorHandler &); + void OpenUnit(OpenStatus, std::optional, Position, + OwningPtr &&path, std::size_t pathLength, IoErrorHandler &); void CloseUnit(CloseStatus, IoErrorHandler &); void DestroyClosed(); diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -64,7 +64,8 @@ IoErrorHandler handler{terminator}; result.OpenUnit( dir == Direction::Input ? OpenStatus::Old : OpenStatus::Replace, - Position::Rewind, std::move(path), std::strlen(path.get()), handler); + Action::ReadWrite, Position::Rewind, std::move(path), + std::strlen(path.get()), handler); result.isUnformatted = isUnformatted; } return result; @@ -87,8 +88,8 @@ return GetUnitMap().NewUnit(terminator).unitNumber(); } -void ExternalFileUnit::OpenUnit(OpenStatus status, Position position, - OwningPtr &&newPath, std::size_t newPathLength, +void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional action, + Position position, OwningPtr &&newPath, std::size_t newPathLength, IoErrorHandler &handler) { if (IsOpen()) { if (status == OpenStatus::Old && @@ -105,7 +106,7 @@ Close(CloseStatus::Keep, handler); } set_path(std::move(newPath), newPathLength); - Open(status, position, handler); + Open(status, action, position, handler); auto totalBytes{knownSize()}; if (access == Access::Direct) { if (!isFixedRecordLength || !recordLength) { @@ -186,16 +187,10 @@ unitMap = New{terminator}().release(); ExternalFileUnit &out{ExternalFileUnit::CreateNew(6, terminator)}; out.Predefine(1); - out.set_mayRead(false); - out.set_mayWrite(true); - out.set_mayPosition(false); out.SetDirection(Direction::Output, handler); defaultOutput = &out; ExternalFileUnit &in{ExternalFileUnit::CreateNew(5, terminator)}; in.Predefine(0); - in.set_mayRead(true); - in.set_mayWrite(false); - in.set_mayPosition(false); in.SetDirection(Direction::Input, handler); defaultInput = ∈ // TODO: Set UTF-8 mode from the environment