| //===-- 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<char> &&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<UnitMap>{terminator}().release(); |
| 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<std::int64_t>(bytes))}; |
| if (furthestAfter > recordLength.value_or(furthestAfter)) { |
| handler.SignalError(IostatRecordWriteOverrun); |
| return false; |
| } |
| WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); |
| if (positionInRecord > furthestPositionInRecord) { |
| std::memset(Frame() + furthestPositionInRecord, ' ', |
| positionInRecord - furthestPositionInRecord); |
| } |
| std::memcpy(Frame() + positionInRecord, data, bytes); |
| positionInRecord += bytes; |
| furthestPositionInRecord = furthestAfter; |
| return true; |
| } |
| |
| std::optional<char32_t> 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<std::size_t>(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; |
| 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<std::monostate>(); |
| 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<std::intmax_t>(currentRecordNumber), |
| static_cast<std::intmax_t>(frameOffsetInFile_), |
| static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(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<const char *>( |
| 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<int>(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<std::size_t>(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<std::int64_t>(0, at - chunk); |
| } |
| std::size_t want{static_cast<std::size_t>(start - frameOffsetInFile_)}; |
| std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)}; |
| RUNTIME_CHECK(handler, got >= want); |
| } |
| } |
| std::size_t want{ |
| static_cast<std::size_t>(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 |