//===-- runtime/external-unit.cpp -----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Implemenation of ExternalFileUnit for RT_USE_PSEUDO_FILE_UNIT=0. // //===----------------------------------------------------------------------===// #include "io-error.h" #include "lock.h" #include "tools.h" #include "unit-map.h" #include "unit.h" // NOTE: the header files above may define OpenMP declare target // variables, so they have to be included unconditionally // so that the offload entries are consistent between host and device. #if !defined(RT_USE_PSEUDO_FILE_UNIT) #include #include namespace Fortran::runtime::io { // The per-unit data structures are created on demand so that Fortran I/O // should work without a Fortran main program. static Lock unitMapLock; static Lock createOpenLock; static UnitMap *unitMap{nullptr}; void FlushOutputOnCrash(const Terminator &terminator) { if (!defaultOutput && !errorOutput) { return; } IoErrorHandler handler{terminator}; handler.HasIoStat(); // prevent nested crash if flush has error CriticalSection critical{unitMapLock}; if (defaultOutput) { defaultOutput->FlushOutput(handler); } if (errorOutput) { errorOutput->FlushOutput(handler); } } ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { return GetUnitMap().LookUp(unit); } ExternalFileUnit *ExternalFileUnit::LookUpOrCreate( int unit, const Terminator &terminator, bool &wasExtant) { return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); } ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit, Direction dir, Fortran::common::optional isUnformatted, IoErrorHandler &handler) { // Make sure that the returned anonymous unit has been opened, // not just created in the unitMap. CriticalSection critical{createOpenLock}; bool exists{false}; ExternalFileUnit *result{GetUnitMap().LookUpOrCreate(unit, handler, exists)}; if (result && !exists) { common::optional action; if (dir == Direction::Output) { action = Action::ReadWrite; } if (!result->OpenAnonymousUnit( dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace, action, Position::Rewind, Convert::Unknown, handler)) { // fort.N isn't a writable file if (ExternalFileUnit * closed{LookUpForClose(result->unitNumber())}) { closed->DestroyClosed(); } result = nullptr; } else { result->isUnformatted = isUnformatted; } } return result; } ExternalFileUnit *ExternalFileUnit::LookUp( const char *path, std::size_t pathLen) { return GetUnitMap().LookUp(path, pathLen); } ExternalFileUnit &ExternalFileUnit::CreateNew( int unit, const Terminator &terminator) { bool wasExtant{false}; ExternalFileUnit *result{ GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)}; RUNTIME_CHECK(terminator, result && !wasExtant); return *result; } ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { return GetUnitMap().LookUpForClose(unit); } ExternalFileUnit &ExternalFileUnit::NewUnit( const Terminator &terminator, bool forChildIo) { ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)}; unit.createdForInternalChildIo_ = forChildIo; return unit; } bool ExternalFileUnit::OpenUnit(Fortran::common::optional status, Fortran::common::optional action, Position position, OwningPtr &&newPath, std::size_t newPathLength, Convert convert, IoErrorHandler &handler) { if (convert == Convert::Unknown) { convert = executionEnvironment.conversion; } swapEndianness_ = convert == Convert::Swap || (convert == Convert::LittleEndian && !isHostLittleEndian) || (convert == Convert::BigEndian && isHostLittleEndian); bool impliedClose{false}; if (IsConnected()) { bool isSamePath{newPath.get() && path() && pathLength() == newPathLength && std::memcmp(path(), newPath.get(), newPathLength) == 0}; if (status && *status != OpenStatus::Old && isSamePath) { handler.SignalError("OPEN statement for connected unit may not have " "explicit STATUS= other than 'OLD'"); return impliedClose; } if (!newPath.get() || isSamePath) { // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE= newPath.reset(); return impliedClose; } // Otherwise, OPEN on open unit with new FILE= implies CLOSE DoImpliedEndfile(handler); FlushOutput(handler); TruncateFrame(0, handler); Close(CloseStatus::Keep, handler); impliedClose = true; } if (newPath.get() && newPathLength > 0) { if (const auto *already{ GetUnitMap().LookUp(newPath.get(), newPathLength)}) { handler.SignalError(IostatOpenAlreadyConnected, "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d", unitNumber_, static_cast(newPathLength), newPath.get(), already->unitNumber_); return impliedClose; } } set_path(std::move(newPath), newPathLength); Open(status.value_or(OpenStatus::Unknown), action, position, handler); if (handler.InError()) { return impliedClose; } auto totalBytes{knownSize()}; if (access == Access::Direct) { if (!openRecl) { handler.SignalError(IostatOpenBadRecl, "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known", unitNumber()); } else if (*openRecl <= 0) { handler.SignalError(IostatOpenBadRecl, "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid", unitNumber(), static_cast(*openRecl)); } else if (totalBytes && (*totalBytes % *openRecl != 0)) { handler.SignalError(IostatOpenBadRecl, "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " "even divisor of the file size %jd", unitNumber(), static_cast(*openRecl), static_cast(*totalBytes)); } recordLength = openRecl; } endfileRecordNumber.reset(); currentRecordNumber = 1; if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) { endfileRecordNumber = 1 + (*totalBytes / *openRecl); } if (position == Position::Append) { if (totalBytes) { frameOffsetInFile_ = *totalBytes; } if (access != Access::Stream) { if (!endfileRecordNumber) { // Fake it so that we can backspace relative from the end endfileRecordNumber = std::numeric_limits::max() - 2; } currentRecordNumber = *endfileRecordNumber; } } return impliedClose; } bool ExternalFileUnit::OpenAnonymousUnit( Fortran::common::optional status, Fortran::common::optional action, Position position, Convert convert, IoErrorHandler &handler) { // I/O to an unconnected unit reads/creates a local file, e.g. fort.7 std::size_t pathMaxLen{32}; auto path{SizedNew{handler}(pathMaxLen)}; std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_); OpenUnit(status, action, position, std::move(path), std::strlen(path.get()), convert, handler); return IsConnected(); } void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { DoImpliedEndfile(handler); FlushOutput(handler); Close(status, handler); } void ExternalFileUnit::DestroyClosed() { GetUnitMap().DestroyClosed(*this); // destroys *this } Iostat ExternalFileUnit::SetDirection(Direction direction) { if (direction == Direction::Input) { if (mayRead()) { direction_ = Direction::Input; return IostatOk; } else { return IostatReadFromWriteOnly; } } else { if (mayWrite()) { if (direction_ == Direction::Input) { // Don't retain any input data from previous record, like a // variable-length unformatted record footer, in the frame, // since we're going start writing frames. frameOffsetInFile_ += recordOffsetInFrame_; recordOffsetInFrame_ = 0; } direction_ = Direction::Output; return IostatOk; } else { return IostatWriteToReadOnly; } } } UnitMap &ExternalFileUnit::CreateUnitMap() { Terminator terminator{__FILE__, __LINE__}; IoErrorHandler handler{terminator}; UnitMap &newUnitMap{*New{terminator}().release()}; bool wasExtant{false}; ExternalFileUnit &out{*newUnitMap.LookUpOrCreate( FORTRAN_DEFAULT_OUTPUT_UNIT, terminator, wasExtant)}; RUNTIME_CHECK(terminator, !wasExtant); out.Predefine(1); handler.SignalError(out.SetDirection(Direction::Output)); out.isUnformatted = false; defaultOutput = &out; ExternalFileUnit &in{*newUnitMap.LookUpOrCreate( FORTRAN_DEFAULT_INPUT_UNIT, terminator, wasExtant)}; RUNTIME_CHECK(terminator, !wasExtant); in.Predefine(0); handler.SignalError(in.SetDirection(Direction::Input)); in.isUnformatted = false; defaultInput = ∈ ExternalFileUnit &error{ *newUnitMap.LookUpOrCreate(FORTRAN_ERROR_UNIT, terminator, wasExtant)}; RUNTIME_CHECK(terminator, !wasExtant); error.Predefine(2); handler.SignalError(error.SetDirection(Direction::Output)); error.isUnformatted = false; errorOutput = &error; return newUnitMap; } // A back-up atexit() handler for programs that don't terminate with a main // program END or a STOP statement or other Fortran-initiated program shutdown, // such as programs with a C main() that terminate normally. It flushes all // external I/O units. It is registered once the first time that any external // I/O is attempted. static void CloseAllExternalUnits() { IoErrorHandler handler{"Fortran program termination"}; ExternalFileUnit::CloseAll(handler); } UnitMap &ExternalFileUnit::GetUnitMap() { if (unitMap) { return *unitMap; } { CriticalSection critical{unitMapLock}; if (unitMap) { return *unitMap; } unitMap = &CreateUnitMap(); } std::atexit(CloseAllExternalUnits); return *unitMap; } void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { CriticalSection critical{unitMapLock}; if (unitMap) { unitMap->CloseAll(handler); FreeMemoryAndNullify(unitMap); } defaultOutput = nullptr; defaultInput = nullptr; errorOutput = nullptr; } void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { CriticalSection critical{unitMapLock}; if (unitMap) { unitMap->FlushAll(handler); } } int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) { if (!mayAsynchronous()) { handler.SignalError(IostatBadAsynchronous); return -1; } else { for (int j{0}; 64 * j < maxAsyncIds; ++j) { if (auto least{asyncIdAvailable_[j].LeastElement()}) { asyncIdAvailable_[j].reset(*least); return 64 * j + static_cast(*least); } } handler.SignalError(IostatTooManyAsyncOps); return -1; } } bool ExternalFileUnit::Wait(int id) { if (static_cast(id) >= maxAsyncIds || asyncIdAvailable_[id / 64].test(id % 64)) { return false; } else { if (id == 0) { // means "all IDs" for (int j{0}; 64 * j < maxAsyncIds; ++j) { asyncIdAvailable_[j].set(); } asyncIdAvailable_[0].reset(0); } else { asyncIdAvailable_[id / 64].set(id % 64); } return true; } } } // namespace Fortran::runtime::io #endif // !defined(RT_USE_PSEUDO_FILE_UNIT)