Files
clang-p2996/flang/runtime/unit.cpp
Jean Perier 2e65c8e8db [flang] Allow write after non advancing read in IO runtime
1. To avoid overwriting the part of the record read in the non advancing read,
the furtherPositionInRecord field must be set to the max of the
furtherPositionInRecord and the positionInRecord at the beginning of the
IO write.

2. To allow any further read to succeed after the write, the unit
beganReadingRecord_ must be set to false when resetting the recordLength
during the write, otherwise, recordLength will not be computed in further
read and an assert is hit (at unit.cpp(398)).

The added unit test exercises both of these scenarios.

Differential Revision: https://reviews.llvm.org/D113740
2021-11-16 14:53:39 +01:00

803 lines
28 KiB
C++

//===-- runtime/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
//
//===----------------------------------------------------------------------===//
#include "unit.h"
#include "environment.h"
#include "io-error.h"
#include "lock.h"
#include "unit-map.h"
#include <cstdio>
#include <limits>
#include <utility>
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 *defaultInput{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->FlushOutput(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::LookUpOrCreateAnonymous(int unit,
Direction dir, std::optional<bool> isUnformatted,
const Terminator &terminator) {
bool exists{false};
ExternalFileUnit &result{
GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
if (!exists) {
IoErrorHandler handler{terminator};
result.OpenAnonymousUnit(
dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
Action::ReadWrite, Position::Rewind, Convert::Native, handler);
result.isUnformatted = isUnformatted;
}
return result;
}
ExternalFileUnit *ExternalFileUnit::LookUp(const char *path) {
return GetUnitMap().LookUp(path);
}
ExternalFileUnit &ExternalFileUnit::CreateNew(
int unit, const Terminator &terminator) {
bool wasExtant{false};
ExternalFileUnit &result{
GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !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;
}
void ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
std::optional<Action> action, Position position, OwningPtr<char> &&newPath,
std::size_t newPathLength, Convert convert, IoErrorHandler &handler) {
if (executionEnvironment.conversion != Convert::Unknown) {
convert = executionEnvironment.conversion;
}
swapEndianness_ = convert == Convert::Swap ||
(convert == Convert::LittleEndian && !isHostLittleEndian) ||
(convert == Convert::BigEndian && isHostLittleEndian);
if (IsOpen()) {
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;
}
if (!newPath.get() || isSamePath) {
// OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
newPath.reset();
return;
}
// Otherwise, OPEN on open unit with new FILE= implies CLOSE
DoImpliedEndfile(handler);
FlushOutput(handler);
Close(CloseStatus::Keep, handler);
}
set_path(std::move(newPath), newPathLength);
Open(status.value_or(OpenStatus::Unknown), action, position, handler);
auto totalBytes{knownSize()};
if (access == Access::Direct) {
if (!isFixedRecordLength || !recordLength) {
handler.SignalError(IostatOpenBadRecl,
"OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
unitNumber());
} else if (*recordLength <= 0) {
handler.SignalError(IostatOpenBadRecl,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
unitNumber(), static_cast<std::intmax_t>(*recordLength));
} else if (totalBytes && (*totalBytes % *recordLength != 0)) {
handler.SignalError(IostatOpenBadAppend,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
"even divisor of the file size %jd",
unitNumber(), static_cast<std::intmax_t>(*recordLength),
static_cast<std::intmax_t>(*totalBytes));
}
}
endfileRecordNumber.reset();
currentRecordNumber = 1;
if (totalBytes && recordLength && *recordLength) {
endfileRecordNumber = 1 + (*totalBytes / *recordLength);
}
if (position == Position::Append) {
if (!endfileRecordNumber) {
// Fake it so that we can backspace relative from the end
endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
}
currentRecordNumber = *endfileRecordNumber;
}
}
void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status,
std::optional<Action> 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<char>{handler}(pathMaxLen)};
std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
convert, handler);
}
void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
DoImpliedEndfile(handler);
FlushOutput(handler);
Close(status, handler);
}
void ExternalFileUnit::DestroyClosed() {
GetUnitMap().DestroyClosed(*this); // destroys *this
}
bool ExternalFileUnit::SetDirection(
Direction direction, IoErrorHandler &handler) {
if (direction == Direction::Input) {
if (mayRead()) {
direction_ = Direction::Input;
return true;
} else {
handler.SignalError(IostatReadFromWriteOnly,
"READ(UNIT=%d) with ACTION='WRITE'", unitNumber());
return false;
}
} else {
if (mayWrite()) {
direction_ = Direction::Output;
return true;
} else {
handler.SignalError(IostatWriteToReadOnly,
"WRITE(UNIT=%d) with ACTION='READ'", unitNumber());
return false;
}
}
}
UnitMap &ExternalFileUnit::GetUnitMap() {
if (unitMap) {
return *unitMap;
}
CriticalSection critical{unitMapLock};
if (unitMap) {
return *unitMap;
}
Terminator terminator{__FILE__, __LINE__};
IoErrorHandler handler{terminator};
UnitMap *newUnitMap{New<UnitMap>{terminator}().release()};
bool wasExtant{false};
ExternalFileUnit &out{newUnitMap->LookUpOrCreate(6, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !wasExtant);
out.Predefine(1);
out.SetDirection(Direction::Output, handler);
defaultOutput = &out;
ExternalFileUnit &in{newUnitMap->LookUpOrCreate(5, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !wasExtant);
in.Predefine(0);
in.SetDirection(Direction::Input, handler);
defaultInput = &in;
// TODO: Set UTF-8 mode from the environment
unitMap = newUnitMap;
return *unitMap;
}
void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
CriticalSection critical{unitMapLock};
if (unitMap) {
unitMap->CloseAll(handler);
FreeMemoryAndNullify(unitMap);
}
defaultOutput = nullptr;
}
void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
CriticalSection critical{unitMapLock};
if (unitMap) {
unitMap->FlushAll(handler);
}
}
static void SwapEndianness(
char *data, std::size_t bytes, std::size_t elementBytes) {
if (elementBytes > 1) {
auto half{elementBytes >> 1};
for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
for (std::size_t k{0}; k < half; ++k) {
std::swap(data[j + k], data[j + elementBytes - 1 - k]);
}
}
}
}
bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
std::size_t elementBytes, IoErrorHandler &handler) {
auto furthestAfter{std::max(furthestPositionInRecord,
positionInRecord + static_cast<std::int64_t>(bytes))};
if (recordLength) {
// It is possible for recordLength to have a value now for a
// variable-length output record if the previous operation
// was a BACKSPACE or non advancing input statement.
if (!isFixedRecordLength) {
recordLength.reset();
beganReadingRecord_ = false;
} else if (furthestAfter > *recordLength) {
handler.SignalError(IostatRecordWriteOverrun,
"Attempt to write %zd bytes to position %jd in a fixed-size record "
"of %jd bytes",
bytes, static_cast<std::intmax_t>(positionInRecord),
static_cast<std::intmax_t>(*recordLength));
return false;
}
}
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
if (positionInRecord > furthestPositionInRecord) {
std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
positionInRecord - furthestPositionInRecord);
}
char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
std::memcpy(to, data, bytes);
if (swapEndianness_) {
SwapEndianness(to, bytes, elementBytes);
}
positionInRecord += bytes;
furthestPositionInRecord = furthestAfter;
return true;
}
bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
std::size_t elementBytes, IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input);
auto furthestAfter{std::max(furthestPositionInRecord,
positionInRecord + static_cast<std::int64_t>(bytes))};
if (furthestAfter > recordLength.value_or(furthestAfter)) {
handler.SignalError(IostatRecordReadOverrun,
"Attempt to read %zd bytes at position %jd in a record of %jd bytes",
bytes, static_cast<std::intmax_t>(positionInRecord),
static_cast<std::intmax_t>(*recordLength));
return false;
}
auto need{recordOffsetInFrame_ + furthestAfter};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got >= need) {
std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
if (swapEndianness_) {
SwapEndianness(data, bytes, elementBytes);
}
positionInRecord += bytes;
furthestPositionInRecord = furthestAfter;
return true;
} else {
// EOF or error: can be handled & has been signaled
endfileRecordNumber = currentRecordNumber;
return false;
}
}
std::size_t ExternalFileUnit::GetNextInputBytes(
const char *&p, IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input);
p = FrameNextInput(handler, 1);
return p ? recordLength.value_or(positionInRecord + 1) - positionInRecord : 0;
}
std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
IoErrorHandler &handler) {
const char *p{nullptr};
std::size_t bytes{GetNextInputBytes(p, handler)};
if (bytes == 0) {
return std::nullopt;
} else {
// TODO: UTF-8 decoding; may have to get more bytes in a loop
return *p;
}
}
const char *ExternalFileUnit::FrameNextInput(
IoErrorHandler &handler, std::size_t bytes) {
RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);
if (static_cast<std::int64_t>(positionInRecord + bytes) <=
recordLength.value_or(positionInRecord + bytes)) {
auto at{recordOffsetInFrame_ + positionInRecord};
auto need{static_cast<std::size_t>(at + bytes)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
SetSequentialVariableFormattedRecordLength();
if (got >= need) {
return Frame() + at;
}
handler.SignalEnd();
endfileRecordNumber = currentRecordNumber;
}
return nullptr;
}
bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() {
if (recordLength || access != Access::Sequential) {
return true;
} else if (FrameLength() > recordOffsetInFrame_) {
const char *record{Frame() + recordOffsetInFrame_};
std::size_t bytes{FrameLength() - recordOffsetInFrame_};
if (const char *nl{
reinterpret_cast<const char *>(std::memchr(record, '\n', bytes))}) {
recordLength = nl - record;
if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
--*recordLength;
}
return true;
}
}
return false;
}
void ExternalFileUnit::SetLeftTabLimit() {
leftTabLimit = furthestPositionInRecord;
positionInRecord = furthestPositionInRecord;
}
bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input);
if (!beganReadingRecord_) {
beganReadingRecord_ = true;
if (access == Access::Sequential) {
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
handler.SignalEnd();
} else if (isFixedRecordLength && access == Access::Direct) {
RUNTIME_CHECK(handler, recordLength.has_value());
auto need{
static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got < need) {
handler.SignalEnd();
}
} else {
RUNTIME_CHECK(handler, isUnformatted.has_value());
if (isUnformatted.value_or(false)) {
BeginSequentialVariableUnformattedInputRecord(handler);
} else { // formatted
BeginSequentialVariableFormattedInputRecord(handler);
}
}
}
}
RUNTIME_CHECK(handler,
access != Access::Sequential || recordLength.has_value() ||
handler.InError());
return !handler.InError();
}
void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
beganReadingRecord_ = false;
if (handler.InError() && handler.GetIoStat() != IostatEor) {
// avoid bogus crashes in END/ERR circumstances
} else if (access == Access::Sequential) {
RUNTIME_CHECK(handler, recordLength.has_value());
recordOffsetInFrame_ += *recordLength;
if (isFixedRecordLength && access == Access::Direct) {
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = 0;
} else {
RUNTIME_CHECK(handler, isUnformatted.has_value());
recordLength.reset();
if (isUnformatted.value_or(false)) {
// Retain footer in frame for more efficient BACKSPACE
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = sizeof(std::uint32_t);
} else { // formatted
if (FrameLength() > recordOffsetInFrame_ &&
Frame()[recordOffsetInFrame_] == '\r') {
++recordOffsetInFrame_;
}
if (FrameLength() >= recordOffsetInFrame_ &&
Frame()[recordOffsetInFrame_] == '\n') {
++recordOffsetInFrame_;
}
if (!pinnedFrame || mayPosition()) {
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = 0;
}
}
}
}
++currentRecordNumber;
BeginRecord();
}
bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
if (direction_ == Direction::Input) {
FinishReadingRecord(handler);
return BeginReadingRecord(handler);
} else { // Direction::Output
bool ok{true};
RUNTIME_CHECK(handler, isUnformatted.has_value());
if (isFixedRecordLength && recordLength &&
furthestPositionInRecord < *recordLength) {
// Pad remainder of fixed length record
WriteFrame(
frameOffsetInFile_, recordOffsetInFrame_ + *recordLength, handler);
std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
isUnformatted.value_or(false) ? 0 : ' ',
*recordLength - furthestPositionInRecord);
furthestPositionInRecord = *recordLength;
}
if (!(isFixedRecordLength && access == Access::Direct)) {
positionInRecord = furthestPositionInRecord;
if (isUnformatted.value_or(false)) {
// Append the length of a sequential unformatted variable-length record
// as its footer, then overwrite the reserved first four bytes of the
// record with its length as its header. These four bytes were skipped
// over in BeginUnformattedIO<Output>().
// TODO: Break very large records up into subrecords with negative
// headers &/or footers
std::uint32_t length;
length = furthestPositionInRecord - sizeof length;
ok = ok &&
Emit(reinterpret_cast<const char *>(&length), sizeof length,
sizeof length, handler);
positionInRecord = 0;
ok = ok &&
Emit(reinterpret_cast<const char *>(&length), sizeof length,
sizeof length, handler);
} else {
// Terminate formatted variable length record
ok = ok && Emit("\n", 1, 1, handler); // TODO: Windows CR+LF
}
}
CommitWrites();
impliedEndfile_ = true;
++currentRecordNumber;
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
endfileRecordNumber.reset();
}
return ok;
}
}
void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
if (access != Access::Sequential) {
handler.SignalError(IostatBackspaceNonSequential,
"BACKSPACE(UNIT=%d) on non-sequential file", unitNumber());
} else {
if (endfileRecordNumber && currentRecordNumber > *endfileRecordNumber) {
// BACKSPACE after explicit ENDFILE
currentRecordNumber = *endfileRecordNumber;
} else {
DoImpliedEndfile(handler);
if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
--currentRecordNumber;
if (isFixedRecordLength && access == Access::Direct) {
BackspaceFixedRecord(handler);
} else {
RUNTIME_CHECK(handler, isUnformatted.has_value());
if (isUnformatted.value_or(false)) {
BackspaceVariableUnformattedRecord(handler);
} else {
BackspaceVariableFormattedRecord(handler);
}
}
}
}
BeginRecord();
}
}
void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {
if (!mayPosition()) {
auto frameAt{FrameAt()};
if (frameOffsetInFile_ >= frameAt &&
frameOffsetInFile_ <
static_cast<std::int64_t>(frameAt + FrameLength())) {
// A Flush() that's about to happen to a non-positionable file
// needs to advance frameOffsetInFile_ to prevent attempts at
// impossible seeks
CommitWrites();
}
}
Flush(handler);
}
void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
if (isTerminal()) {
FlushOutput(handler);
}
}
void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
if (access != Access::Sequential) {
handler.SignalError(IostatEndfileNonSequential,
"ENDFILE(UNIT=%d) on non-sequential file", unitNumber());
} else if (!mayWrite()) {
handler.SignalError(IostatEndfileUnwritable,
"ENDFILE(UNIT=%d) on read-only file", unitNumber());
} else if (endfileRecordNumber &&
currentRecordNumber > *endfileRecordNumber) {
// ENDFILE after ENDFILE
} else {
DoEndfile(handler);
// Explicit ENDFILE leaves position *after* the endfile record
RUNTIME_CHECK(handler, endfileRecordNumber.has_value());
currentRecordNumber = *endfileRecordNumber + 1;
}
}
void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
if (access == Access::Direct) {
handler.SignalError(IostatRewindNonSequential,
"REWIND(UNIT=%d) on non-sequential file", unitNumber());
} else {
DoImpliedEndfile(handler);
SetPosition(0);
currentRecordNumber = 1;
}
}
void ExternalFileUnit::EndIoStatement() {
io_.reset();
u_.emplace<std::monostate>();
lock_.Drop();
}
void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
IoErrorHandler &handler) {
std::int32_t header{0}, footer{0};
std::size_t need{recordOffsetInFrame_ + sizeof header};
std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
// Try to emit informative errors to help debug corrupted files.
const char *error{nullptr};
if (got < need) {
if (got == recordOffsetInFrame_) {
handler.SignalEnd();
} else {
error = "Unformatted variable-length sequential file input failed at "
"record #%jd (file offset %jd): truncated record header";
}
} else {
std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
recordLength = sizeof header + header; // does not include footer
need = recordOffsetInFrame_ + *recordLength + sizeof footer;
got = ReadFrame(frameOffsetInFile_, need, handler);
if (got < need) {
error = "Unformatted variable-length sequential file input failed at "
"record #%jd (file offset %jd): hit EOF reading record with "
"length %jd bytes";
} else {
std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength,
sizeof footer);
if (footer != header) {
error = "Unformatted variable-length sequential file input failed at "
"record #%jd (file offset %jd): record header has length %jd "
"that does not match record footer (%jd)";
}
}
}
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));
// TODO: error recovery
}
positionInRecord = sizeof header;
}
void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
IoErrorHandler &handler) {
if (this == defaultInput && defaultOutput) {
defaultOutput->FlushOutput(handler);
}
std::size_t length{0};
do {
std::size_t need{length + 1};
length =
ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -
recordOffsetInFrame_;
if (length < need) {
if (length > 0) {
// final record w/o \n
recordLength = length;
} else {
handler.SignalEnd();
}
break;
}
} while (!SetSequentialVariableFormattedRecordLength());
}
void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
RUNTIME_CHECK(handler, recordLength.has_value());
if (frameOffsetInFile_ < *recordLength) {
handler.SignalError(IostatBackspaceAtFirstRecord);
} else {
frameOffsetInFile_ -= *recordLength;
}
}
void ExternalFileUnit::BackspaceVariableUnformattedRecord(
IoErrorHandler &handler) {
std::int32_t header{0}, footer{0};
auto headerBytes{static_cast<std::int64_t>(sizeof header)};
frameOffsetInFile_ += recordOffsetInFrame_;
recordOffsetInFrame_ = 0;
if (frameOffsetInFile_ <= headerBytes) {
handler.SignalError(IostatBackspaceAtFirstRecord);
return;
}
// 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 NextSequentialVariableUnformattedInputRecord().
std::size_t got{
ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
RUNTIME_CHECK(handler, got >= sizeof footer);
std::memcpy(&footer, Frame(), sizeof footer);
recordLength = footer;
RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes);
frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
if (frameOffsetInFile_ >= headerBytes) {
frameOffsetInFile_ -= headerBytes;
recordOffsetInFrame_ = headerBytes;
}
auto need{static_cast<std::size_t>(
recordOffsetInFrame_ + sizeof header + *recordLength)};
got = ReadFrame(frameOffsetInFile_, need, handler);
RUNTIME_CHECK(handler, got >= need);
std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
RUNTIME_CHECK(handler, header == *recordLength);
}
// 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::BackspaceVariableFormattedRecord(
IoErrorHandler &handler) {
// File offset of previous record's newline
auto prevNL{
frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
if (prevNL < 0) {
handler.SignalError(IostatBackspaceAtFirstRecord);
return;
}
while (true) {
if (frameOffsetInFile_ < prevNL) {
if (const char *p{
FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
recordOffsetInFrame_ = p - Frame() + 1;
recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
break;
}
}
if (frameOffsetInFile_ == 0) {
recordOffsetInFrame_ = 0;
recordLength = prevNL;
break;
}
frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
RUNTIME_CHECK(handler, got >= need);
}
RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n');
if (*recordLength > 0 &&
Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
--*recordLength;
}
}
void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
if (impliedEndfile_) {
impliedEndfile_ = false;
if (access == Access::Sequential && mayPosition()) {
DoEndfile(handler);
}
}
}
void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
endfileRecordNumber = currentRecordNumber;
Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler);
BeginRecord();
impliedEndfile_ = false;
}
void ExternalFileUnit::CommitWrites() {
frameOffsetInFile_ +=
recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
recordOffsetInFrame_ = 0;
BeginRecord();
}
ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) {
OwningPtr<ChildIo> current{std::move(child_)};
Terminator &terminator{parent.GetIoErrorHandler()};
OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))};
child_.reset(next.release());
return *child_;
}
void ExternalFileUnit::PopChildIo(ChildIo &child) {
if (child_.get() != &child) {
child.parent().GetIoErrorHandler().Crash(
"ChildIo being popped is not top of stack");
}
child_.reset(child.AcquirePrevious().release()); // deletes top child
}
void ChildIo::EndIoStatement() {
io_.reset();
u_.emplace<std::monostate>();
}
bool ChildIo::CheckFormattingAndDirection(Terminator &terminator,
const char *what, bool unformatted, Direction direction) {
bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
bool parentIsFormatted{parentIsInput
? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
nullptr
: parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
nullptr};
bool parentIsUnformatted{!parentIsFormatted};
if (unformatted != parentIsUnformatted) {
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
parentIsUnformatted ? "unformatted" : "formatted");
return false;
} else if (parentIsInput != (direction == Direction::Input)) {
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
parentIsInput ? "input" : "output");
return false;
} else {
return true;
}
}
} // namespace Fortran::runtime::io