//===-- runtime/unit.cpp ----------------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #include "unit.h" #include "io-error.h" #include "lock.h" #include "unit-map.h" 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 UnitMap *unitMap{nullptr}; static ExternalFileUnit *defaultOutput{nullptr}; void FlushOutputOnCrash(const Terminator &terminator) { if (!defaultOutput) { return; } CriticalSection critical{unitMapLock}; if (defaultOutput) { IoErrorHandler handler{terminator}; handler.HasIoStat(); // prevent nested crash if flush has error defaultOutput->Flush(handler); } } ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { return GetUnitMap().LookUp(unit); } ExternalFileUnit &ExternalFileUnit::LookUpOrCrash( int unit, const Terminator &terminator) { ExternalFileUnit *file{LookUp(unit)}; if (!file) { terminator.Crash("Not an open I/O unit number: %d", unit); } return *file; } ExternalFileUnit &ExternalFileUnit::LookUpOrCreate( int unit, const Terminator &terminator, bool *wasExtant) { return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); } ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { return GetUnitMap().LookUpForClose(unit); } int ExternalFileUnit::NewUnit(const Terminator &terminator) { return GetUnitMap().NewUnit(terminator).unitNumber(); } void ExternalFileUnit::OpenUnit(OpenStatus status, Position position, OwningPtr &&newPath, std::size_t newPathLength, IoErrorHandler &handler) { if (IsOpen()) { if (status == OpenStatus::Old && (!newPath.get() || (path() && pathLength() == newPathLength && std::memcmp(path(), newPath.get(), newPathLength) == 0))) { // OPEN of existing unit, STATUS='OLD', not new FILE= newPath.reset(); return; } // Otherwise, OPEN on open unit with new FILE= implies CLOSE Flush(handler); Close(CloseStatus::Keep, handler); } set_path(std::move(newPath), newPathLength); Open(status, position, handler); } void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { Flush(handler); Close(status, handler); } void ExternalFileUnit::DestroyClosed() { GetUnitMap().DestroyClosed(*this); // destroys *this } UnitMap &ExternalFileUnit::GetUnitMap() { if (unitMap) { return *unitMap; } CriticalSection critical{unitMapLock}; if (unitMap) { return *unitMap; } Terminator terminator{__FILE__, __LINE__}; unitMap = &New{}(terminator); ExternalFileUnit &out{ExternalFileUnit::LookUpOrCreate(6, terminator)}; out.Predefine(1); out.set_mayRead(false); out.set_mayWrite(true); out.set_mayPosition(false); defaultOutput = &out; ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)}; in.Predefine(0); in.set_mayRead(true); in.set_mayWrite(false); in.set_mayPosition(false); // TODO: Set UTF-8 mode from the environment return *unitMap; } void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { CriticalSection critical{unitMapLock}; if (unitMap) { unitMap->CloseAll(handler); FreeMemoryAndNullify(unitMap); } defaultOutput = nullptr; } bool ExternalFileUnit::Emit( const char *data, std::size_t bytes, IoErrorHandler &handler) { auto furthestAfter{std::max(furthestPositionInRecord, positionInRecord + static_cast(bytes))}; if (furthestAfter > recordLength.value_or(furthestAfter)) { handler.SignalError(IostatRecordWriteOverrun); return false; } WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); std::memcpy(Frame() + positionInRecord, data, bytes); positionInRecord += bytes; furthestPositionInRecord = furthestAfter; return true; } std::optional ExternalFileUnit::GetCurrentChar( IoErrorHandler &handler) { isReading_ = true; // TODO: manage read/write transitions if (isUnformatted) { handler.Crash("GetCurrentChar() called for unformatted input"); return std::nullopt; } std::size_t chunk{256}; // for stream input if (recordLength.has_value()) { if (positionInRecord >= *recordLength) { return std::nullopt; } chunk = *recordLength - positionInRecord; } auto at{recordOffsetInFrame_ + positionInRecord}; std::size_t need{static_cast(at + 1)}; std::size_t want{need + chunk}; auto got{ReadFrame(frameOffsetInFile_, want, handler)}; if (got <= need) { endfileRecordNumber = currentRecordNumber; handler.SignalEnd(); return std::nullopt; } const char *p{Frame() + at}; if (isUTF8) { // TODO: UTF-8 decoding } return *p; } void ExternalFileUnit::SetLeftTabLimit() { leftTabLimit = furthestPositionInRecord; positionInRecord = furthestPositionInRecord; } bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { bool ok{true}; if (isReading_) { if (access == Access::Sequential) { if (isUnformatted) { NextSequentialUnformattedInputRecord(handler); } else { NextSequentialFormattedInputRecord(handler); } } } else if (!isUnformatted) { if (recordLength.has_value()) { // fill fixed-size record if (furthestPositionInRecord < *recordLength) { WriteFrame(frameOffsetInFile_, *recordLength, handler); std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', *recordLength - furthestPositionInRecord); } } else { positionInRecord = furthestPositionInRecord + 1; ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; recordOffsetInFrame_ = 0; } } ++currentRecordNumber; positionInRecord = 0; furthestPositionInRecord = 0; leftTabLimit.reset(); return ok; } void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { if (!isReading_) { handler.Crash("ExternalFileUnit::BackspaceRecord() called during writing"); // TODO: create endfile record, &c. } if (access == Access::Sequential) { if (isUnformatted) { BackspaceSequentialUnformattedRecord(handler); } else { BackspaceSequentialFormattedRecord(handler); } } else { // TODO } positionInRecord = 0; furthestPositionInRecord = 0; leftTabLimit.reset(); } void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { if (isTerminal()) { Flush(handler); } } void ExternalFileUnit::EndIoStatement() { frameOffsetInFile_ += recordOffsetInFrame_; recordOffsetInFrame_ = 0; io_.reset(); u_.emplace(); lock_.Drop(); } void ExternalFileUnit::NextSequentialUnformattedInputRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; // Retain previous footer (if any) in frame for more efficient BACKSPACE std::size_t retain{sizeof header}; if (recordLength) { // not first record - advance to next ++currentRecordNumber; if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { handler.SignalEnd(); return; } frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength + 2 * sizeof header; recordOffsetInFrame_ = 0; } else { retain = 0; } std::size_t need{retain + sizeof header}; std::size_t got{ReadFrame(frameOffsetInFile_ - retain, need, handler)}; // Try to emit informative errors to help debug corrupted files. const char *error{nullptr}; if (got < need) { if (got == retain) { handler.SignalEnd(); } else { error = "Unformatted sequential file input failed at record #%jd (file " "offset %jd): truncated record header"; } } else { std::memcpy(&header, Frame() + retain, sizeof header); need = retain + header + 2 * sizeof header; got = ReadFrame(frameOffsetInFile_ - retain, need + sizeof header /* next one */, handler); if (got < need) { error = "Unformatted sequential file input failed at record #%jd (file " "offset %jd): hit EOF reading record with length %jd bytes"; } else { const char *start{Frame() + retain + sizeof header}; std::memcpy(&footer, start + header, sizeof footer); if (footer != header) { error = "Unformatted sequential file input failed at record #%jd (file " "offset %jd): record header has length %jd that does not match " "record footer (%jd)"; } else { recordLength = header; } } } if (error) { handler.SignalError(error, static_cast(currentRecordNumber), static_cast(frameOffsetInFile_), static_cast(header), static_cast(footer)); } positionInRecord = sizeof header; } void ExternalFileUnit::NextSequentialFormattedInputRecord( IoErrorHandler &handler) { static constexpr std::size_t chunk{256}; std::size_t length{0}; if (recordLength.has_value()) { // not first record - advance to next ++currentRecordNumber; if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { handler.SignalEnd(); return; } if (Frame()[*recordLength] == '\r') { ++*recordLength; } recordOffsetInFrame_ += *recordLength + 1; } while (true) { std::size_t got{ReadFrame( frameOffsetInFile_, recordOffsetInFrame_ + length + chunk, handler)}; if (got <= recordOffsetInFrame_ + length) { handler.SignalEnd(); break; } const char *frame{Frame() + recordOffsetInFrame_}; if (const char *nl{reinterpret_cast( std::memchr(frame + length, '\n', chunk))}) { recordLength = nl - (frame + length) + 1; if (*recordLength > 0 && frame[*recordLength - 1] == '\r') { --*recordLength; } return; } length += got; } } void ExternalFileUnit::BackspaceSequentialUnformattedRecord( IoErrorHandler &handler) { std::int32_t header{0}, footer{0}; RUNTIME_CHECK(handler, currentRecordNumber > 1); --currentRecordNumber; int overhead{static_cast(2 * sizeof header)}; // Error conditions here cause crashes, not file format errors, because the // validity of the file structure before the current record will have been // checked informatively in NextSequentialUnformattedInputRecord(). RUNTIME_CHECK(handler, frameOffsetInFile_ >= overhead); std::size_t got{ ReadFrame(frameOffsetInFile_ - sizeof footer, sizeof footer, handler)}; RUNTIME_CHECK(handler, got >= sizeof footer); std::memcpy(&footer, Frame(), sizeof footer); RUNTIME_CHECK(handler, frameOffsetInFile_ >= footer + overhead); frameOffsetInFile_ -= footer + 2 * sizeof footer; auto extra{std::max(sizeof footer, frameOffsetInFile_)}; std::size_t want{extra + footer + 2 * sizeof footer}; got = ReadFrame(frameOffsetInFile_ - extra, want, handler); RUNTIME_CHECK(handler, got >= want); std::memcpy(&header, Frame() + extra, sizeof header); RUNTIME_CHECK(handler, header == footer); positionInRecord = sizeof header; recordLength = footer; } // There's no portable memrchr(), unfortunately, and strrchr() would // fail on a record with a NUL, so we have to do it the hard way. static const char *FindLastNewline(const char *str, std::size_t length) { for (const char *p{str + length}; p-- > str;) { if (*p == '\n') { return p; } } return nullptr; } void ExternalFileUnit::BackspaceSequentialFormattedRecord( IoErrorHandler &handler) { std::int64_t start{frameOffsetInFile_ + recordOffsetInFrame_}; --currentRecordNumber; RUNTIME_CHECK(handler, currentRecordNumber > 0); if (currentRecordNumber == 1) { // To simplify the code below, treat a backspace to the first record // as a special case; RUNTIME_CHECK(handler, start > 0); *recordLength = start - 1; frameOffsetInFile_ = 0; recordOffsetInFrame_ = 0; ReadFrame(0, *recordLength + 1, handler); } else { RUNTIME_CHECK(handler, start > 1); std::int64_t at{start - 2}; // byte before previous record's newline while (true) { if (const char *p{ FindLastNewline(Frame(), at - frameOffsetInFile_ + 1)}) { // This is the newline that ends the record before the previous one. recordOffsetInFrame_ = p - Frame() + 1; *recordLength = start - 1 - (frameOffsetInFile_ + recordOffsetInFrame_); break; } RUNTIME_CHECK(handler, frameOffsetInFile_ > 0); at = frameOffsetInFile_ - 1; if (auto bytesBefore{BytesBufferedBeforeFrame()}) { frameOffsetInFile_ = FrameAt() - bytesBefore; } else { static constexpr int chunk{1024}; frameOffsetInFile_ = std::max(0, at - chunk); } std::size_t want{static_cast(start - frameOffsetInFile_)}; std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)}; RUNTIME_CHECK(handler, got >= want); } } std::size_t want{ static_cast(recordOffsetInFrame_ + *recordLength + 1)}; RUNTIME_CHECK(handler, FrameLength() >= want); RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); if (*recordLength > 0 && Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { --*recordLength; } } } // namespace Fortran::runtime::io