The ISO Fortran standards don't say whether a WRITE to a formatted stream unit should truncate the unit if there has been any repositioning (via POS= control list specifiers) to an earlier point in the stream. But units with sequential records do truncate on writes after BACKSPACE and REWIND statements, and many compilers (including this one) truncate stream units too. Since some compilers don't truncate streams, this patch adds an environment variable FORT_TRUNCATE_STREAM that can be set to 0 to disable truncation and ease porting to flang-new of codes that depend on that behavior. Fixes https://github.com/llvm/llvm-project/issues/167569.
882 lines
30 KiB
C++
882 lines
30 KiB
C++
//===-- lib/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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implementation of ExternalFileUnit common for both
|
|
// RT_USE_PSEUDO_FILE_UNIT=0 and RT_USE_PSEUDO_FILE_UNIT=1.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "unit.h"
|
|
#include "flang-rt/runtime/io-error.h"
|
|
#include "flang-rt/runtime/lock.h"
|
|
#include "flang-rt/runtime/tools.h"
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
#ifndef FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
|
|
RT_OFFLOAD_VAR_GROUP_BEGIN
|
|
RT_VAR_ATTRS ExternalFileUnit *defaultInput{nullptr}; // unit 5
|
|
RT_VAR_ATTRS ExternalFileUnit *defaultOutput{nullptr}; // unit 6
|
|
RT_VAR_ATTRS ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
|
|
RT_OFFLOAD_VAR_GROUP_END
|
|
#endif // FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
|
|
|
|
RT_OFFLOAD_API_GROUP_BEGIN
|
|
|
|
static inline RT_API_ATTRS 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) {
|
|
RT_DIAG_PUSH
|
|
RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN
|
|
std::swap(data[j + k], data[j + elementBytes - 1 - k]);
|
|
RT_DIAG_POP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (openRecl) {
|
|
// Check for fixed-length record overrun, but allow for
|
|
// sequential record termination.
|
|
int extra{0};
|
|
int header{0};
|
|
if (access == Access::Sequential) {
|
|
if (isUnformatted.value_or(false)) {
|
|
// record header + footer
|
|
header = static_cast<int>(sizeof(std::uint32_t));
|
|
extra = 2 * header;
|
|
} else {
|
|
#ifdef _WIN32
|
|
if (!isWindowsTextFile()) {
|
|
++extra; // carriage return (CR)
|
|
}
|
|
#endif
|
|
++extra; // newline (LF)
|
|
}
|
|
}
|
|
if (furthestAfter > extra + *openRecl) {
|
|
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 - header),
|
|
static_cast<std::intmax_t>(*openRecl));
|
|
return false;
|
|
}
|
|
}
|
|
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.
|
|
recordLength.reset();
|
|
beganReadingRecord_ = false;
|
|
}
|
|
if (IsAfterEndfile()) {
|
|
handler.SignalError(IostatWriteAfterEndfile);
|
|
return false;
|
|
}
|
|
CheckDirectAccess(handler);
|
|
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
|
|
if (positionInRecord > furthestPositionInRecord) {
|
|
runtime::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
|
|
' ', positionInRecord - furthestPositionInRecord);
|
|
}
|
|
char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
|
|
runtime::memcpy(to, data, bytes);
|
|
if (swapEndianness_) {
|
|
SwapEndianness(to, bytes, elementBytes);
|
|
}
|
|
positionInRecord += bytes;
|
|
furthestPositionInRecord = furthestAfter;
|
|
anyWriteSinceLastPositioning_ = true;
|
|
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) {
|
|
runtime::memcpy(
|
|
data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
|
|
if (swapEndianness_) {
|
|
SwapEndianness(data, bytes, elementBytes);
|
|
}
|
|
positionInRecord += bytes;
|
|
furthestPositionInRecord = furthestAfter;
|
|
return true;
|
|
} else {
|
|
HitEndOnRead(handler);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::size_t ExternalFileUnit::GetNextInputBytes(
|
|
const char *&p, IoErrorHandler &handler) {
|
|
RUNTIME_CHECK(handler, direction_ == Direction::Input);
|
|
if (access == Access::Sequential &&
|
|
positionInRecord < recordLength.value_or(positionInRecord)) {
|
|
// Fast path for variable-length formatted input: the whole record
|
|
// must be in frame as a result of newline detection for record length.
|
|
p = Frame() + recordOffsetInFrame_ + positionInRecord;
|
|
return *recordLength - positionInRecord;
|
|
}
|
|
std::size_t length{1};
|
|
if (auto recl{EffectiveRecordLength()}) {
|
|
if (positionInRecord < *recl) {
|
|
length = *recl - positionInRecord;
|
|
} else {
|
|
p = nullptr;
|
|
return 0;
|
|
}
|
|
}
|
|
p = FrameNextInput(handler, length);
|
|
return p ? length : 0;
|
|
}
|
|
|
|
std::size_t ExternalFileUnit::ViewBytesInRecord(
|
|
const char *&p, bool forward) const {
|
|
p = nullptr;
|
|
auto recl{recordLength.value_or(positionInRecord)};
|
|
if (forward) {
|
|
if (positionInRecord < recl) {
|
|
p = Frame() + recordOffsetInFrame_ + positionInRecord;
|
|
return recl - positionInRecord;
|
|
}
|
|
} else {
|
|
if (positionInRecord <= recl) {
|
|
p = Frame() + recordOffsetInFrame_ + positionInRecord;
|
|
}
|
|
return positionInRecord - leftTabLimit.value_or(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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)};
|
|
SetVariableFormattedRecordLength();
|
|
if (got >= need) {
|
|
return Frame() + at;
|
|
}
|
|
HitEndOnRead(handler);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool ExternalFileUnit::SetVariableFormattedRecordLength() {
|
|
if (recordLength || access == Access::Direct) {
|
|
return true;
|
|
} else if (FrameLength() > recordOffsetInFrame_) {
|
|
const char *record{Frame() + recordOffsetInFrame_};
|
|
std::size_t bytes{FrameLength() - recordOffsetInFrame_};
|
|
if (const char *nl{FindCharacter(record, '\n', bytes)}) {
|
|
recordLength = nl - record;
|
|
if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
|
|
--*recordLength;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
|
|
RUNTIME_CHECK(handler, direction_ == Direction::Input);
|
|
if (!beganReadingRecord_) {
|
|
beganReadingRecord_ = true;
|
|
// Don't use IsAtEOF() to check for an EOF condition here, just detect
|
|
// it from a failed or short read from the file. IsAtEOF() could be
|
|
// wrong for formatted input if actual newline characters had been
|
|
// written in-band by previous WRITEs before a REWIND. In fact,
|
|
// now that we know that the unit is being used for input (again),
|
|
// it's best to reset endfileRecordNumber and ensure IsAtEOF() will
|
|
// now be true on return only if it gets set by HitEndOnRead().
|
|
endfileRecordNumber.reset();
|
|
if (access == Access::Direct) {
|
|
CheckDirectAccess(handler);
|
|
auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
|
|
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
|
|
if (got >= need) {
|
|
recordLength = openRecl;
|
|
} else {
|
|
recordLength.reset();
|
|
HitEndOnRead(handler);
|
|
}
|
|
} else {
|
|
if (anyWriteSinceLastPositioning_ && access == Access::Sequential) {
|
|
// Most Fortran implementations allow a READ after a WRITE;
|
|
// the read then just hits an EOF.
|
|
DoEndfile<false, Direction::Input>(handler);
|
|
}
|
|
recordLength.reset();
|
|
RUNTIME_CHECK(handler, isUnformatted.has_value());
|
|
if (*isUnformatted) {
|
|
if (access == Access::Sequential) {
|
|
BeginSequentialVariableUnformattedInputRecord(handler);
|
|
}
|
|
} else { // formatted sequential or stream
|
|
BeginVariableFormattedInputRecord(handler);
|
|
}
|
|
}
|
|
}
|
|
RUNTIME_CHECK(handler,
|
|
recordLength.has_value() || !IsRecordFile() || handler.InError());
|
|
return !handler.InError();
|
|
}
|
|
|
|
void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
|
|
RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
|
|
beganReadingRecord_ = false;
|
|
if (handler.GetIoStat() == IostatEnd ||
|
|
(IsRecordFile() && !recordLength.has_value())) {
|
|
// Avoid bogus crashes in END/ERR circumstances; but
|
|
// still increment the current record number so that
|
|
// an attempted read of an endfile record, followed by
|
|
// a BACKSPACE, will still be at EOF.
|
|
++currentRecordNumber;
|
|
} else if (IsRecordFile()) {
|
|
recordOffsetInFrame_ += *recordLength;
|
|
if (access != Access::Direct) {
|
|
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;
|
|
} else { // unformatted stream
|
|
furthestPositionInRecord =
|
|
std::max(furthestPositionInRecord, positionInRecord);
|
|
frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
|
|
recordOffsetInFrame_ = 0;
|
|
}
|
|
BeginRecord();
|
|
leftTabLimit.reset();
|
|
}
|
|
|
|
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());
|
|
positionInRecord = furthestPositionInRecord;
|
|
if (access == Access::Direct) {
|
|
if (furthestPositionInRecord <
|
|
openRecl.value_or(furthestPositionInRecord)) {
|
|
// Pad remainder of fixed length record
|
|
WriteFrame(
|
|
frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
|
|
runtime::memset(
|
|
Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
|
|
isUnformatted.value_or(false) ? 0 : ' ',
|
|
*openRecl - furthestPositionInRecord);
|
|
furthestPositionInRecord = *openRecl;
|
|
}
|
|
} else if (*isUnformatted) {
|
|
if (access == Access::Sequential) {
|
|
// 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 {
|
|
// Unformatted stream: nothing to do
|
|
}
|
|
} else if (handler.GetIoStat() != IostatOk &&
|
|
furthestPositionInRecord == 0) {
|
|
// Error in formatted variable length record, and no output yet; do
|
|
// nothing, like most other Fortran compilers do.
|
|
return true;
|
|
} else {
|
|
// Terminate formatted variable length record
|
|
const char *lineEnding{"\n"};
|
|
std::size_t lineEndingBytes{1};
|
|
#ifdef _WIN32
|
|
if (!isWindowsTextFile()) {
|
|
lineEnding = "\r\n";
|
|
lineEndingBytes = 2;
|
|
}
|
|
#endif
|
|
ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler);
|
|
}
|
|
leftTabLimit.reset();
|
|
if (IsAfterEndfile()) {
|
|
return false;
|
|
}
|
|
CommitWrites();
|
|
++currentRecordNumber;
|
|
if (access != Access::Direct) {
|
|
impliedEndfile_ = IsRecordFile();
|
|
if (IsAtEOF()) {
|
|
endfileRecordNumber.reset();
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
|
|
if (access == Access::Direct || !IsRecordFile()) {
|
|
handler.SignalError(IostatBackspaceNonSequential,
|
|
"BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
|
|
unitNumber());
|
|
} else {
|
|
if (IsAfterEndfile()) {
|
|
// BACKSPACE after explicit ENDFILE
|
|
currentRecordNumber = *endfileRecordNumber;
|
|
} else if (leftTabLimit && direction_ == Direction::Input) {
|
|
// BACKSPACE after non-advancing input
|
|
leftTabLimit.reset();
|
|
} else {
|
|
DoImpliedEndfile(handler);
|
|
if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
|
|
--currentRecordNumber;
|
|
if (openRecl && access == Access::Direct) {
|
|
BackspaceFixedRecord(handler);
|
|
} else {
|
|
RUNTIME_CHECK(handler, isUnformatted.has_value());
|
|
if (isUnformatted.value_or(false)) {
|
|
BackspaceVariableUnformattedRecord(handler);
|
|
} else {
|
|
BackspaceVariableFormattedRecord(handler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BeginRecord();
|
|
anyWriteSinceLastPositioning_ = false;
|
|
}
|
|
}
|
|
|
|
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();
|
|
leftTabLimit.reset();
|
|
}
|
|
}
|
|
Flush(handler);
|
|
}
|
|
|
|
void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
|
|
if (isTerminal()) {
|
|
FlushOutput(handler);
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
|
|
if (access == Access::Direct) {
|
|
handler.SignalError(IostatEndfileDirect,
|
|
"ENDFILE(UNIT=%d) on direct-access file", unitNumber());
|
|
} else if (!mayWrite()) {
|
|
handler.SignalError(IostatEndfileUnwritable,
|
|
"ENDFILE(UNIT=%d) on read-only file", unitNumber());
|
|
} else if (IsAfterEndfile()) {
|
|
// ENDFILE after ENDFILE
|
|
} else {
|
|
DoEndfile(handler);
|
|
if (IsRecordFile() && access != Access::Direct) {
|
|
// 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;
|
|
anyWriteSinceLastPositioning_ = false;
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::SetPosition(std::int64_t pos) {
|
|
frameOffsetInFile_ = pos;
|
|
recordOffsetInFrame_ = 0;
|
|
if (access == Access::Direct) {
|
|
directAccessRecWasSet_ = true;
|
|
}
|
|
BeginRecord();
|
|
beganReadingRecord_ = false; // for positioning after nonadvancing input
|
|
leftTabLimit.reset();
|
|
}
|
|
|
|
void ExternalFileUnit::Sought(std::int64_t zeroBasedPos) {
|
|
SetPosition(zeroBasedPos);
|
|
if (zeroBasedPos == 0) {
|
|
currentRecordNumber = 1;
|
|
} else {
|
|
// We no longer know which record we're in. Set currentRecordNumber to
|
|
// a large value from whence we can both advance and backspace.
|
|
currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;
|
|
endfileRecordNumber.reset();
|
|
}
|
|
}
|
|
|
|
bool ExternalFileUnit::SetStreamPos(
|
|
std::int64_t oneBasedPos, IoErrorHandler &handler) {
|
|
if (access != Access::Stream) {
|
|
handler.SignalError("POS= may not appear unless ACCESS='STREAM'");
|
|
return false;
|
|
}
|
|
if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)
|
|
handler.SignalError(
|
|
"POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos));
|
|
return false;
|
|
}
|
|
// A backwards POS= implies truncation after writing, at least in
|
|
// Intel and NAG.
|
|
if (static_cast<std::size_t>(oneBasedPos - 1) <
|
|
frameOffsetInFile_ + recordOffsetInFrame_) {
|
|
DoImpliedEndfile(handler);
|
|
}
|
|
Sought(oneBasedPos - 1);
|
|
return true;
|
|
}
|
|
|
|
// GNU FSEEK extension
|
|
RT_API_ATTRS bool ExternalFileUnit::Fseek(std::int64_t zeroBasedPos,
|
|
enum FseekWhence whence, IoErrorHandler &handler) {
|
|
if (whence == FseekEnd) {
|
|
Flush(handler); // updates knownSize_
|
|
if (auto size{knownSize()}) {
|
|
zeroBasedPos += *size;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (whence == FseekCurrent) {
|
|
zeroBasedPos += InquirePos() - 1;
|
|
}
|
|
if (zeroBasedPos >= 0) {
|
|
Sought(zeroBasedPos);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ExternalFileUnit::SetDirectRec(
|
|
std::int64_t oneBasedRec, IoErrorHandler &handler) {
|
|
if (access != Access::Direct) {
|
|
handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");
|
|
return false;
|
|
}
|
|
if (!openRecl) {
|
|
handler.SignalError("RECL= was not specified");
|
|
return false;
|
|
}
|
|
if (oneBasedRec < 1) {
|
|
handler.SignalError(
|
|
"REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec));
|
|
return false;
|
|
}
|
|
currentRecordNumber = oneBasedRec;
|
|
SetPosition((oneBasedRec - 1) * *openRecl);
|
|
return true;
|
|
}
|
|
|
|
void ExternalFileUnit::EndIoStatement() {
|
|
io_.reset();
|
|
u_.emplace<std::monostate>();
|
|
lock_.Drop();
|
|
}
|
|
|
|
void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
|
|
IoErrorHandler &handler) {
|
|
RUNTIME_CHECK(handler, access == Access::Sequential);
|
|
std::uint32_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_) {
|
|
HitEndOnRead(handler);
|
|
} else {
|
|
error = "Unformatted variable-length sequential file input failed at "
|
|
"record #%jd (file offset %jd): truncated record header";
|
|
}
|
|
} else {
|
|
header = ReadHeaderOrFooter(recordOffsetInFrame_);
|
|
recordLength = sizeof header + header; // does not include footer
|
|
need = recordOffsetInFrame_ + *recordLength + sizeof footer;
|
|
got = ReadFrame(frameOffsetInFile_, need, handler);
|
|
if (got >= need) {
|
|
footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);
|
|
}
|
|
if (frameOffsetInFile_ == 0 && recordOffsetInFrame_ == 0 &&
|
|
(got < need || footer != header)) {
|
|
// Maybe an omitted or incorrect byte swap flag setting?
|
|
// Try it the other way, since this is the first record.
|
|
// (N.B. Won't work on files starting with empty records, but there's
|
|
// no good way to know later if all preceding records were empty.)
|
|
swapEndianness_ = !swapEndianness_;
|
|
std::uint32_t header2{ReadHeaderOrFooter(0)};
|
|
std::size_t recordLength2{sizeof header2 + header2};
|
|
std::size_t need2{recordLength2 + sizeof footer};
|
|
std::size_t got2{ReadFrame(0, need2, handler)};
|
|
if (got2 >= need2) {
|
|
std::uint32_t footer2{ReadHeaderOrFooter(recordLength2)};
|
|
if (footer2 == header2) {
|
|
error = "Unformatted variable-length sequential file input "
|
|
"failed on the first record, probably due to a need "
|
|
"for byte order data conversion; consider adding "
|
|
"CONVERT='SWAP' to the OPEN statement or adding "
|
|
"FORT_CONVERT=SWAP to the execution environment";
|
|
}
|
|
}
|
|
swapEndianness_ = !swapEndianness_;
|
|
}
|
|
if (error) {
|
|
} else 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 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::BeginVariableFormattedInputRecord(
|
|
IoErrorHandler &handler) {
|
|
if (this == defaultInput) {
|
|
if (defaultOutput) {
|
|
defaultOutput->FlushOutput(handler);
|
|
}
|
|
if (errorOutput) {
|
|
errorOutput->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;
|
|
unterminatedRecord = true;
|
|
} else {
|
|
HitEndOnRead(handler);
|
|
}
|
|
break;
|
|
}
|
|
} while (!SetVariableFormattedRecordLength());
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
|
|
RUNTIME_CHECK(handler, openRecl.has_value());
|
|
if (frameOffsetInFile_ < *openRecl) {
|
|
handler.SignalError(IostatBackspaceAtFirstRecord);
|
|
} else {
|
|
frameOffsetInFile_ -= *openRecl;
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceVariableUnformattedRecord(
|
|
IoErrorHandler &handler) {
|
|
std::uint32_t header{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)};
|
|
if (static_cast<std::int64_t>(got) < headerBytes) {
|
|
handler.SignalError(IostatShortRead);
|
|
return;
|
|
}
|
|
recordLength = ReadHeaderOrFooter(0);
|
|
if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
|
|
handler.SignalError(IostatBadUnformattedRecord);
|
|
return;
|
|
}
|
|
frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
|
|
auto need{static_cast<std::size_t>(
|
|
recordOffsetInFrame_ + sizeof header + *recordLength)};
|
|
got = ReadFrame(frameOffsetInFile_, need, handler);
|
|
if (got < need) {
|
|
handler.SignalError(IostatShortRead);
|
|
return;
|
|
}
|
|
header = ReadHeaderOrFooter(recordOffsetInFrame_);
|
|
if (header != *recordLength) {
|
|
handler.SignalError(IostatBadUnformattedRecord);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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 RT_API_ATTRS const char *FindLastNewline(
|
|
const char *str, std::size_t length) {
|
|
for (const char *p{str + length}; p >= str; p--) {
|
|
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)};
|
|
if (got < need) {
|
|
handler.SignalError(IostatShortRead);
|
|
return;
|
|
}
|
|
}
|
|
if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {
|
|
handler.SignalError(IostatMissingTerminator);
|
|
return;
|
|
}
|
|
if (*recordLength > 0 &&
|
|
Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
|
|
--*recordLength;
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
|
|
if (access != Access::Direct) {
|
|
if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) {
|
|
// Flush a partial record after non-advancing output
|
|
impliedEndfile_ = true;
|
|
}
|
|
if (impliedEndfile_ && mayPosition()) {
|
|
DoEndfile(handler);
|
|
}
|
|
}
|
|
impliedEndfile_ = false;
|
|
}
|
|
|
|
template <bool ANY_DIR, Direction DIR>
|
|
void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
|
|
if (IsRecordFile() && access != Access::Direct) {
|
|
furthestPositionInRecord =
|
|
std::max(positionInRecord, furthestPositionInRecord);
|
|
if (leftTabLimit) { // last I/O was non-advancing
|
|
if (access == Access::Sequential && direction_ == Direction::Output) {
|
|
if constexpr (ANY_DIR || DIR == Direction::Output) {
|
|
// When DoEndfile() is called from BeginReadingRecord(),
|
|
// this call to AdvanceRecord() may appear as a recursion
|
|
// though it may never happen. Expose the call only
|
|
// under the constexpr direction check.
|
|
AdvanceRecord(handler);
|
|
} else {
|
|
// This check always fails if we are here.
|
|
RUNTIME_CHECK(handler, direction_ != Direction::Output);
|
|
}
|
|
} else { // Access::Stream or input
|
|
leftTabLimit.reset();
|
|
++currentRecordNumber;
|
|
}
|
|
}
|
|
endfileRecordNumber = currentRecordNumber;
|
|
}
|
|
frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
|
|
recordOffsetInFrame_ = 0;
|
|
FlushOutput(handler);
|
|
if (access != Access::Stream || executionEnvironment.truncateStream) {
|
|
// Stream output after positioning truncates with some compilers.
|
|
Truncate(frameOffsetInFile_, handler);
|
|
TruncateFrame(frameOffsetInFile_, handler);
|
|
}
|
|
BeginRecord();
|
|
impliedEndfile_ = false;
|
|
anyWriteSinceLastPositioning_ = false;
|
|
}
|
|
|
|
template void ExternalFileUnit::DoEndfile(IoErrorHandler &handler);
|
|
template void ExternalFileUnit::DoEndfile<false, Direction::Output>(
|
|
IoErrorHandler &handler);
|
|
template void ExternalFileUnit::DoEndfile<false, Direction::Input>(
|
|
IoErrorHandler &handler);
|
|
|
|
void ExternalFileUnit::CommitWrites() {
|
|
frameOffsetInFile_ +=
|
|
recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
|
|
recordOffsetInFrame_ = 0;
|
|
BeginRecord();
|
|
}
|
|
|
|
bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) {
|
|
if (access == Access::Direct) {
|
|
RUNTIME_CHECK(handler, openRecl);
|
|
if (!directAccessRecWasSet_) {
|
|
handler.SignalError(
|
|
"No REC= was specified for a data transfer with ACCESS='DIRECT'");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) {
|
|
handler.SignalEnd();
|
|
if (IsRecordFile() && access != Access::Direct) {
|
|
endfileRecordNumber = currentRecordNumber;
|
|
}
|
|
}
|
|
|
|
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());
|
|
leftTabLimit = positionInRecord;
|
|
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
|
|
}
|
|
|
|
std::uint32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
|
|
std::uint32_t word;
|
|
char *wordPtr{reinterpret_cast<char *>(&word)};
|
|
runtime::memcpy(wordPtr, Frame() + frameOffset, sizeof word);
|
|
if (swapEndianness_) {
|
|
SwapEndianness(wordPtr, sizeof word, sizeof word);
|
|
}
|
|
return word;
|
|
}
|
|
|
|
void ChildIo::EndIoStatement() {
|
|
io_.reset();
|
|
u_.emplace<std::monostate>();
|
|
}
|
|
|
|
Iostat ChildIo::CheckFormattingAndDirection(
|
|
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) {
|
|
return unformatted ? IostatUnformattedChildOnFormattedParent
|
|
: IostatFormattedChildOnUnformattedParent;
|
|
} else if (parentIsInput != (direction == Direction::Input)) {
|
|
return parentIsInput ? IostatChildOutputToInputParent
|
|
: IostatChildInputFromOutputParent;
|
|
} else {
|
|
return IostatOk;
|
|
}
|
|
}
|
|
|
|
RT_OFFLOAD_API_GROUP_END
|
|
} // namespace Fortran::runtime::io
|