blob: 2eee142081a52607d3af7211d3b7b58210d7a7ce [file] [log] [blame]
//===-- 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