1 //===-- runtime/unit.cpp --------------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
17 namespace Fortran::runtime::io
{
19 // The per-unit data structures are created on demand so that Fortran I/O
20 // should work without a Fortran main program.
21 static Lock unitMapLock
;
22 static UnitMap
*unitMap
{nullptr};
23 static ExternalFileUnit
*defaultInput
{nullptr}; // unit 5
24 static ExternalFileUnit
*defaultOutput
{nullptr}; // unit 6
25 static ExternalFileUnit
*errorOutput
{nullptr}; // unit 0 extension
27 void FlushOutputOnCrash(const Terminator
&terminator
) {
28 if (!defaultOutput
&& !errorOutput
) {
31 IoErrorHandler handler
{terminator
};
32 handler
.HasIoStat(); // prevent nested crash if flush has error
33 CriticalSection critical
{unitMapLock
};
35 defaultOutput
->FlushOutput(handler
);
38 errorOutput
->FlushOutput(handler
);
42 ExternalFileUnit
*ExternalFileUnit::LookUp(int unit
) {
43 return GetUnitMap().LookUp(unit
);
46 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreate(
47 int unit
, const Terminator
&terminator
, bool &wasExtant
) {
48 return GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
);
51 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreateAnonymous(int unit
,
52 Direction dir
, std::optional
<bool> isUnformatted
,
53 const Terminator
&terminator
) {
55 ExternalFileUnit
*result
{
56 GetUnitMap().LookUpOrCreate(unit
, terminator
, exists
)};
57 if (result
&& !exists
) {
58 IoErrorHandler handler
{terminator
};
59 result
->OpenAnonymousUnit(
60 dir
== Direction::Input
? OpenStatus::Unknown
: OpenStatus::Replace
,
61 Action::ReadWrite
, Position::Rewind
, Convert::Unknown
, handler
);
62 result
->isUnformatted
= isUnformatted
;
67 ExternalFileUnit
*ExternalFileUnit::LookUp(
68 const char *path
, std::size_t pathLen
) {
69 return GetUnitMap().LookUp(path
, pathLen
);
72 ExternalFileUnit
&ExternalFileUnit::CreateNew(
73 int unit
, const Terminator
&terminator
) {
74 bool wasExtant
{false};
75 ExternalFileUnit
*result
{
76 GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
)};
77 RUNTIME_CHECK(terminator
, result
&& !wasExtant
);
81 ExternalFileUnit
*ExternalFileUnit::LookUpForClose(int unit
) {
82 return GetUnitMap().LookUpForClose(unit
);
85 ExternalFileUnit
&ExternalFileUnit::NewUnit(
86 const Terminator
&terminator
, bool forChildIo
) {
87 ExternalFileUnit
&unit
{GetUnitMap().NewUnit(terminator
)};
88 unit
.createdForInternalChildIo_
= forChildIo
;
92 void ExternalFileUnit::OpenUnit(std::optional
<OpenStatus
> status
,
93 std::optional
<Action
> action
, Position position
, OwningPtr
<char> &&newPath
,
94 std::size_t newPathLength
, Convert convert
, IoErrorHandler
&handler
) {
95 if (convert
== Convert::Unknown
) {
96 convert
= executionEnvironment
.conversion
;
98 swapEndianness_
= convert
== Convert::Swap
||
99 (convert
== Convert::LittleEndian
&& !isHostLittleEndian
) ||
100 (convert
== Convert::BigEndian
&& isHostLittleEndian
);
102 bool isSamePath
{newPath
.get() && path() && pathLength() == newPathLength
&&
103 std::memcmp(path(), newPath
.get(), newPathLength
) == 0};
104 if (status
&& *status
!= OpenStatus::Old
&& isSamePath
) {
105 handler
.SignalError("OPEN statement for connected unit may not have "
106 "explicit STATUS= other than 'OLD'");
109 if (!newPath
.get() || isSamePath
) {
110 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
114 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
115 DoImpliedEndfile(handler
);
116 FlushOutput(handler
);
117 TruncateFrame(0, handler
);
118 Close(CloseStatus::Keep
, handler
);
120 if (newPath
.get() && newPathLength
> 0) {
121 if (const auto *already
{
122 GetUnitMap().LookUp(newPath
.get(), newPathLength
)}) {
123 handler
.SignalError(IostatOpenAlreadyConnected
,
124 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
125 unitNumber_
, static_cast<int>(newPathLength
), newPath
.get(),
126 already
->unitNumber_
);
130 set_path(std::move(newPath
), newPathLength
);
131 Open(status
.value_or(OpenStatus::Unknown
), action
, position
, handler
);
132 auto totalBytes
{knownSize()};
133 if (access
== Access::Direct
) {
135 handler
.SignalError(IostatOpenBadRecl
,
136 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
138 } else if (*openRecl
<= 0) {
139 handler
.SignalError(IostatOpenBadRecl
,
140 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
141 unitNumber(), static_cast<std::intmax_t>(*openRecl
));
142 } else if (totalBytes
&& (*totalBytes
% *openRecl
!= 0)) {
143 handler
.SignalError(IostatOpenBadRecl
,
144 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
145 "even divisor of the file size %jd",
146 unitNumber(), static_cast<std::intmax_t>(*openRecl
),
147 static_cast<std::intmax_t>(*totalBytes
));
149 recordLength
= openRecl
;
151 endfileRecordNumber
.reset();
152 currentRecordNumber
= 1;
153 if (totalBytes
&& access
== Access::Direct
&& openRecl
.value_or(0) > 0) {
154 endfileRecordNumber
= 1 + (*totalBytes
/ *openRecl
);
156 if (position
== Position::Append
) {
158 frameOffsetInFile_
= *totalBytes
;
160 if (access
!= Access::Stream
) {
161 if (!endfileRecordNumber
) {
162 // Fake it so that we can backspace relative from the end
163 endfileRecordNumber
= std::numeric_limits
<std::int64_t>::max() - 2;
165 currentRecordNumber
= *endfileRecordNumber
;
170 void ExternalFileUnit::OpenAnonymousUnit(std::optional
<OpenStatus
> status
,
171 std::optional
<Action
> action
, Position position
, Convert convert
,
172 IoErrorHandler
&handler
) {
173 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
174 std::size_t pathMaxLen
{32};
175 auto path
{SizedNew
<char>{handler
}(pathMaxLen
)};
176 std::snprintf(path
.get(), pathMaxLen
, "fort.%d", unitNumber_
);
177 OpenUnit(status
, action
, position
, std::move(path
), std::strlen(path
.get()),
181 void ExternalFileUnit::CloseUnit(CloseStatus status
, IoErrorHandler
&handler
) {
182 DoImpliedEndfile(handler
);
183 FlushOutput(handler
);
184 Close(status
, handler
);
187 void ExternalFileUnit::DestroyClosed() {
188 GetUnitMap().DestroyClosed(*this); // destroys *this
191 Iostat
ExternalFileUnit::SetDirection(Direction direction
) {
192 if (direction
== Direction::Input
) {
194 direction_
= Direction::Input
;
197 return IostatReadFromWriteOnly
;
201 direction_
= Direction::Output
;
204 return IostatWriteToReadOnly
;
209 UnitMap
&ExternalFileUnit::CreateUnitMap() {
210 Terminator terminator
{__FILE__
, __LINE__
};
211 IoErrorHandler handler
{terminator
};
212 UnitMap
&newUnitMap
{*New
<UnitMap
>{terminator
}().release()};
214 bool wasExtant
{false};
215 ExternalFileUnit
&out
{*newUnitMap
.LookUpOrCreate(6, terminator
, wasExtant
)};
216 RUNTIME_CHECK(terminator
, !wasExtant
);
218 handler
.SignalError(out
.SetDirection(Direction::Output
));
219 out
.isUnformatted
= false;
220 defaultOutput
= &out
;
222 ExternalFileUnit
&in
{*newUnitMap
.LookUpOrCreate(5, terminator
, wasExtant
)};
223 RUNTIME_CHECK(terminator
, !wasExtant
);
225 handler
.SignalError(in
.SetDirection(Direction::Input
));
226 in
.isUnformatted
= false;
229 ExternalFileUnit
&error
{*newUnitMap
.LookUpOrCreate(0, terminator
, wasExtant
)};
230 RUNTIME_CHECK(terminator
, !wasExtant
);
232 handler
.SignalError(error
.SetDirection(Direction::Output
));
233 error
.isUnformatted
= false;
234 errorOutput
= &error
;
239 // A back-up atexit() handler for programs that don't terminate with a main
240 // program END or a STOP statement or other Fortran-initiated program shutdown,
241 // such as programs with a C main() that terminate normally. It flushes all
242 // external I/O units. It is registered once the first time that any external
244 static void CloseAllExternalUnits() {
245 IoErrorHandler handler
{"Fortran program termination"};
246 ExternalFileUnit::CloseAll(handler
);
249 UnitMap
&ExternalFileUnit::GetUnitMap() {
254 CriticalSection critical
{unitMapLock
};
258 unitMap
= &CreateUnitMap();
260 std::atexit(CloseAllExternalUnits
);
264 void ExternalFileUnit::CloseAll(IoErrorHandler
&handler
) {
265 CriticalSection critical
{unitMapLock
};
267 unitMap
->CloseAll(handler
);
268 FreeMemoryAndNullify(unitMap
);
270 defaultOutput
= nullptr;
271 defaultInput
= nullptr;
272 errorOutput
= nullptr;
275 void ExternalFileUnit::FlushAll(IoErrorHandler
&handler
) {
276 CriticalSection critical
{unitMapLock
};
278 unitMap
->FlushAll(handler
);
282 static inline void SwapEndianness(
283 char *data
, std::size_t bytes
, std::size_t elementBytes
) {
284 if (elementBytes
> 1) {
285 auto half
{elementBytes
>> 1};
286 for (std::size_t j
{0}; j
+ elementBytes
<= bytes
; j
+= elementBytes
) {
287 for (std::size_t k
{0}; k
< half
; ++k
) {
288 std::swap(data
[j
+ k
], data
[j
+ elementBytes
- 1 - k
]);
294 bool ExternalFileUnit::Emit(const char *data
, std::size_t bytes
,
295 std::size_t elementBytes
, IoErrorHandler
&handler
) {
296 auto furthestAfter
{std::max(furthestPositionInRecord
,
297 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
299 // Check for fixed-length record overrun, but allow for
300 // sequential record termination.
303 if (access
== Access::Sequential
) {
304 if (isUnformatted
.value_or(false)) {
305 // record header + footer
306 header
= static_cast<int>(sizeof(std::uint32_t));
310 if (!isWindowsTextFile()) {
311 ++extra
; // carriage return (CR)
314 ++extra
; // newline (LF)
317 if (furthestAfter
> extra
+ *openRecl
) {
318 handler
.SignalError(IostatRecordWriteOverrun
,
319 "Attempt to write %zd bytes to position %jd in a fixed-size record "
321 bytes
, static_cast<std::intmax_t>(positionInRecord
- header
),
322 static_cast<std::intmax_t>(*openRecl
));
327 // It is possible for recordLength to have a value now for a
328 // variable-length output record if the previous operation
329 // was a BACKSPACE or non advancing input statement.
330 recordLength
.reset();
331 beganReadingRecord_
= false;
333 if (IsAfterEndfile()) {
334 handler
.SignalError(IostatWriteAfterEndfile
);
337 CheckDirectAccess(handler
);
338 WriteFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ furthestAfter
, handler
);
339 if (positionInRecord
> furthestPositionInRecord
) {
340 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
, ' ',
341 positionInRecord
- furthestPositionInRecord
);
343 char *to
{Frame() + recordOffsetInFrame_
+ positionInRecord
};
344 std::memcpy(to
, data
, bytes
);
345 if (swapEndianness_
) {
346 SwapEndianness(to
, bytes
, elementBytes
);
348 positionInRecord
+= bytes
;
349 furthestPositionInRecord
= furthestAfter
;
353 bool ExternalFileUnit::Receive(char *data
, std::size_t bytes
,
354 std::size_t elementBytes
, IoErrorHandler
&handler
) {
355 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
356 auto furthestAfter
{std::max(furthestPositionInRecord
,
357 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
358 if (furthestAfter
> recordLength
.value_or(furthestAfter
)) {
359 handler
.SignalError(IostatRecordReadOverrun
,
360 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
361 bytes
, static_cast<std::intmax_t>(positionInRecord
),
362 static_cast<std::intmax_t>(*recordLength
));
365 auto need
{recordOffsetInFrame_
+ furthestAfter
};
366 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
368 std::memcpy(data
, Frame() + recordOffsetInFrame_
+ positionInRecord
, bytes
);
369 if (swapEndianness_
) {
370 SwapEndianness(data
, bytes
, elementBytes
);
372 positionInRecord
+= bytes
;
373 furthestPositionInRecord
= furthestAfter
;
376 HitEndOnRead(handler
);
381 std::size_t ExternalFileUnit::GetNextInputBytes(
382 const char *&p
, IoErrorHandler
&handler
) {
383 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
384 std::size_t length
{1};
385 if (auto recl
{EffectiveRecordLength()}) {
386 if (positionInRecord
< *recl
) {
387 length
= *recl
- positionInRecord
;
393 p
= FrameNextInput(handler
, length
);
394 return p
? length
: 0;
397 const char *ExternalFileUnit::FrameNextInput(
398 IoErrorHandler
&handler
, std::size_t bytes
) {
399 RUNTIME_CHECK(handler
, isUnformatted
.has_value() && !*isUnformatted
);
400 if (static_cast<std::int64_t>(positionInRecord
+ bytes
) <=
401 recordLength
.value_or(positionInRecord
+ bytes
)) {
402 auto at
{recordOffsetInFrame_
+ positionInRecord
};
403 auto need
{static_cast<std::size_t>(at
+ bytes
)};
404 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
405 SetVariableFormattedRecordLength();
409 HitEndOnRead(handler
);
414 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
415 if (recordLength
|| access
== Access::Direct
) {
417 } else if (FrameLength() > recordOffsetInFrame_
) {
418 const char *record
{Frame() + recordOffsetInFrame_
};
419 std::size_t bytes
{FrameLength() - recordOffsetInFrame_
};
421 reinterpret_cast<const char *>(std::memchr(record
, '\n', bytes
))}) {
422 recordLength
= nl
- record
;
423 if (*recordLength
> 0 && record
[*recordLength
- 1] == '\r') {
432 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler
&handler
) {
433 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
434 if (!beganReadingRecord_
) {
435 beganReadingRecord_
= true;
436 if (access
== Access::Direct
) {
437 CheckDirectAccess(handler
);
438 auto need
{static_cast<std::size_t>(recordOffsetInFrame_
+ *openRecl
)};
439 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
441 recordLength
= openRecl
;
443 recordLength
.reset();
444 HitEndOnRead(handler
);
447 recordLength
.reset();
451 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
452 if (*isUnformatted
) {
453 if (access
== Access::Sequential
) {
454 BeginSequentialVariableUnformattedInputRecord(handler
);
456 } else { // formatted sequential or stream
457 BeginVariableFormattedInputRecord(handler
);
462 RUNTIME_CHECK(handler
,
463 recordLength
.has_value() || !IsRecordFile() || handler
.InError());
464 return !handler
.InError();
467 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler
&handler
) {
468 RUNTIME_CHECK(handler
, direction_
== Direction::Input
&& beganReadingRecord_
);
469 beganReadingRecord_
= false;
470 if (handler
.GetIoStat() == IostatEnd
||
471 (IsRecordFile() && !recordLength
.has_value())) {
472 // Avoid bogus crashes in END/ERR circumstances; but
473 // still increment the current record number so that
474 // an attempted read of an endfile record, followed by
475 // a BACKSPACE, will still be at EOF.
476 ++currentRecordNumber
;
477 } else if (IsRecordFile()) {
478 recordOffsetInFrame_
+= *recordLength
;
479 if (access
!= Access::Direct
) {
480 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
481 recordLength
.reset();
482 if (isUnformatted
.value_or(false)) {
483 // Retain footer in frame for more efficient BACKSPACE
484 frameOffsetInFile_
+= recordOffsetInFrame_
;
485 recordOffsetInFrame_
= sizeof(std::uint32_t);
486 } else { // formatted
487 if (FrameLength() > recordOffsetInFrame_
&&
488 Frame()[recordOffsetInFrame_
] == '\r') {
489 ++recordOffsetInFrame_
;
491 if (FrameLength() > recordOffsetInFrame_
&&
492 Frame()[recordOffsetInFrame_
] == '\n') {
493 ++recordOffsetInFrame_
;
495 if (!pinnedFrame
|| mayPosition()) {
496 frameOffsetInFile_
+= recordOffsetInFrame_
;
497 recordOffsetInFrame_
= 0;
501 ++currentRecordNumber
;
502 } else { // unformatted stream
503 furthestPositionInRecord
=
504 std::max(furthestPositionInRecord
, positionInRecord
);
505 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
510 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler
&handler
) {
511 if (direction_
== Direction::Input
) {
512 FinishReadingRecord(handler
);
513 return BeginReadingRecord(handler
);
514 } else { // Direction::Output
516 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
517 positionInRecord
= furthestPositionInRecord
;
518 if (access
== Access::Direct
) {
519 if (furthestPositionInRecord
<
520 openRecl
.value_or(furthestPositionInRecord
)) {
521 // Pad remainder of fixed length record
523 frameOffsetInFile_
, recordOffsetInFrame_
+ *openRecl
, handler
);
524 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
,
525 isUnformatted
.value_or(false) ? 0 : ' ',
526 *openRecl
- furthestPositionInRecord
);
527 furthestPositionInRecord
= *openRecl
;
529 } else if (*isUnformatted
) {
530 if (access
== Access::Sequential
) {
531 // Append the length of a sequential unformatted variable-length record
532 // as its footer, then overwrite the reserved first four bytes of the
533 // record with its length as its header. These four bytes were skipped
534 // over in BeginUnformattedIO<Output>().
535 // TODO: Break very large records up into subrecords with negative
536 // headers &/or footers
537 std::uint32_t length
;
538 length
= furthestPositionInRecord
- sizeof length
;
540 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
541 sizeof length
, handler
);
542 positionInRecord
= 0;
544 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
545 sizeof length
, handler
);
547 // Unformatted stream: nothing to do
549 } else if (handler
.GetIoStat() != IostatOk
&&
550 furthestPositionInRecord
== 0) {
551 // Error in formatted variable length record, and no output yet; do
552 // nothing, like most other Fortran compilers do.
555 // Terminate formatted variable length record
556 const char *lineEnding
{"\n"};
557 std::size_t lineEndingBytes
{1};
559 if (!isWindowsTextFile()) {
564 ok
= ok
&& Emit(lineEnding
, lineEndingBytes
, 1, handler
);
566 leftTabLimit
.reset();
567 if (IsAfterEndfile()) {
571 ++currentRecordNumber
;
572 if (access
!= Access::Direct
) {
573 impliedEndfile_
= IsRecordFile();
575 endfileRecordNumber
.reset();
582 void ExternalFileUnit::BackspaceRecord(IoErrorHandler
&handler
) {
583 if (access
== Access::Direct
|| !IsRecordFile()) {
584 handler
.SignalError(IostatBackspaceNonSequential
,
585 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
588 if (IsAfterEndfile()) {
589 // BACKSPACE after explicit ENDFILE
590 currentRecordNumber
= *endfileRecordNumber
;
591 } else if (leftTabLimit
) {
592 // BACKSPACE after non-advancing I/O
593 leftTabLimit
.reset();
595 DoImpliedEndfile(handler
);
596 if (frameOffsetInFile_
+ recordOffsetInFrame_
> 0) {
597 --currentRecordNumber
;
598 if (openRecl
&& access
== Access::Direct
) {
599 BackspaceFixedRecord(handler
);
601 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
602 if (isUnformatted
.value_or(false)) {
603 BackspaceVariableUnformattedRecord(handler
);
605 BackspaceVariableFormattedRecord(handler
);
614 void ExternalFileUnit::FlushOutput(IoErrorHandler
&handler
) {
615 if (!mayPosition()) {
616 auto frameAt
{FrameAt()};
617 if (frameOffsetInFile_
>= frameAt
&&
619 static_cast<std::int64_t>(frameAt
+ FrameLength())) {
620 // A Flush() that's about to happen to a non-positionable file
621 // needs to advance frameOffsetInFile_ to prevent attempts at
629 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler
&handler
) {
631 FlushOutput(handler
);
635 void ExternalFileUnit::Endfile(IoErrorHandler
&handler
) {
636 if (access
== Access::Direct
) {
637 handler
.SignalError(IostatEndfileDirect
,
638 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
639 } else if (!mayWrite()) {
640 handler
.SignalError(IostatEndfileUnwritable
,
641 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
642 } else if (IsAfterEndfile()) {
643 // ENDFILE after ENDFILE
646 if (IsRecordFile() && access
!= Access::Direct
) {
647 // Explicit ENDFILE leaves position *after* the endfile record
648 RUNTIME_CHECK(handler
, endfileRecordNumber
.has_value());
649 currentRecordNumber
= *endfileRecordNumber
+ 1;
654 void ExternalFileUnit::Rewind(IoErrorHandler
&handler
) {
655 if (access
== Access::Direct
) {
656 handler
.SignalError(IostatRewindNonSequential
,
657 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
659 SetPosition(0, handler
);
660 currentRecordNumber
= 1;
661 leftTabLimit
.reset();
665 void ExternalFileUnit::SetPosition(std::int64_t pos
, IoErrorHandler
&handler
) {
666 DoImpliedEndfile(handler
);
667 frameOffsetInFile_
= pos
;
668 recordOffsetInFrame_
= 0;
669 if (access
== Access::Direct
) {
670 directAccessRecWasSet_
= true;
675 bool ExternalFileUnit::SetStreamPos(
676 std::int64_t oneBasedPos
, IoErrorHandler
&handler
) {
677 if (access
!= Access::Stream
) {
678 handler
.SignalError("POS= may not appear unless ACCESS='STREAM'");
681 if (oneBasedPos
< 1) { // POS=1 is beginning of file (12.6.2.11)
683 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos
));
686 SetPosition(oneBasedPos
- 1, handler
);
687 // We no longer know which record we're in. Set currentRecordNumber to
688 // a large value from whence we can both advance and backspace.
689 currentRecordNumber
= std::numeric_limits
<std::int64_t>::max() / 2;
690 endfileRecordNumber
.reset();
694 bool ExternalFileUnit::SetDirectRec(
695 std::int64_t oneBasedRec
, IoErrorHandler
&handler
) {
696 if (access
!= Access::Direct
) {
697 handler
.SignalError("REC= may not appear unless ACCESS='DIRECT'");
701 handler
.SignalError("RECL= was not specified");
704 if (oneBasedRec
< 1) {
706 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec
));
709 currentRecordNumber
= oneBasedRec
;
710 SetPosition((oneBasedRec
- 1) * *openRecl
, handler
);
714 void ExternalFileUnit::EndIoStatement() {
716 u_
.emplace
<std::monostate
>();
720 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
721 IoErrorHandler
&handler
) {
722 std::int32_t header
{0}, footer
{0};
723 std::size_t need
{recordOffsetInFrame_
+ sizeof header
};
724 std::size_t got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
725 // Try to emit informative errors to help debug corrupted files.
726 const char *error
{nullptr};
728 if (got
== recordOffsetInFrame_
) {
729 HitEndOnRead(handler
);
731 error
= "Unformatted variable-length sequential file input failed at "
732 "record #%jd (file offset %jd): truncated record header";
735 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
736 recordLength
= sizeof header
+ header
; // does not include footer
737 need
= recordOffsetInFrame_
+ *recordLength
+ sizeof footer
;
738 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
740 error
= "Unformatted variable-length sequential file input failed at "
741 "record #%jd (file offset %jd): hit EOF reading record with "
744 footer
= ReadHeaderOrFooter(recordOffsetInFrame_
+ *recordLength
);
745 if (footer
!= header
) {
746 error
= "Unformatted variable-length sequential file input failed at "
747 "record #%jd (file offset %jd): record header has length %jd "
748 "that does not match record footer (%jd)";
753 handler
.SignalError(error
, static_cast<std::intmax_t>(currentRecordNumber
),
754 static_cast<std::intmax_t>(frameOffsetInFile_
),
755 static_cast<std::intmax_t>(header
), static_cast<std::intmax_t>(footer
));
756 // TODO: error recovery
758 positionInRecord
= sizeof header
;
761 void ExternalFileUnit::BeginVariableFormattedInputRecord(
762 IoErrorHandler
&handler
) {
763 if (this == defaultInput
) {
765 defaultOutput
->FlushOutput(handler
);
768 errorOutput
->FlushOutput(handler
);
771 std::size_t length
{0};
773 std::size_t need
{length
+ 1};
775 ReadFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ need
, handler
) -
776 recordOffsetInFrame_
;
779 // final record w/o \n
780 recordLength
= length
;
781 unterminatedRecord
= true;
783 HitEndOnRead(handler
);
787 } while (!SetVariableFormattedRecordLength());
790 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler
&handler
) {
791 RUNTIME_CHECK(handler
, openRecl
.has_value());
792 if (frameOffsetInFile_
< *openRecl
) {
793 handler
.SignalError(IostatBackspaceAtFirstRecord
);
795 frameOffsetInFile_
-= *openRecl
;
799 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
800 IoErrorHandler
&handler
) {
801 std::int32_t header
{0};
802 auto headerBytes
{static_cast<std::int64_t>(sizeof header
)};
803 frameOffsetInFile_
+= recordOffsetInFrame_
;
804 recordOffsetInFrame_
= 0;
805 if (frameOffsetInFile_
<= headerBytes
) {
806 handler
.SignalError(IostatBackspaceAtFirstRecord
);
809 // Error conditions here cause crashes, not file format errors, because the
810 // validity of the file structure before the current record will have been
811 // checked informatively in NextSequentialVariableUnformattedInputRecord().
813 ReadFrame(frameOffsetInFile_
- headerBytes
, headerBytes
, handler
)};
814 if (static_cast<std::int64_t>(got
) < headerBytes
) {
815 handler
.SignalError(IostatShortRead
);
818 recordLength
= ReadHeaderOrFooter(0);
819 if (frameOffsetInFile_
< *recordLength
+ 2 * headerBytes
) {
820 handler
.SignalError(IostatBadUnformattedRecord
);
823 frameOffsetInFile_
-= *recordLength
+ 2 * headerBytes
;
824 if (frameOffsetInFile_
>= headerBytes
) {
825 frameOffsetInFile_
-= headerBytes
;
826 recordOffsetInFrame_
= headerBytes
;
828 auto need
{static_cast<std::size_t>(
829 recordOffsetInFrame_
+ sizeof header
+ *recordLength
)};
830 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
832 handler
.SignalError(IostatShortRead
);
835 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
836 if (header
!= *recordLength
) {
837 handler
.SignalError(IostatBadUnformattedRecord
);
842 // There's no portable memrchr(), unfortunately, and strrchr() would
843 // fail on a record with a NUL, so we have to do it the hard way.
844 static const char *FindLastNewline(const char *str
, std::size_t length
) {
845 for (const char *p
{str
+ length
}; p
-- > str
;) {
853 void ExternalFileUnit::BackspaceVariableFormattedRecord(
854 IoErrorHandler
&handler
) {
855 // File offset of previous record's newline
857 frameOffsetInFile_
+ static_cast<std::int64_t>(recordOffsetInFrame_
) - 1};
859 handler
.SignalError(IostatBackspaceAtFirstRecord
);
863 if (frameOffsetInFile_
< prevNL
) {
865 FindLastNewline(Frame(), prevNL
- 1 - frameOffsetInFile_
)}) {
866 recordOffsetInFrame_
= p
- Frame() + 1;
867 recordLength
= prevNL
- (frameOffsetInFile_
+ recordOffsetInFrame_
);
871 if (frameOffsetInFile_
== 0) {
872 recordOffsetInFrame_
= 0;
873 recordLength
= prevNL
;
876 frameOffsetInFile_
-= std::min
<std::int64_t>(frameOffsetInFile_
, 1024);
877 auto need
{static_cast<std::size_t>(prevNL
+ 1 - frameOffsetInFile_
)};
878 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
880 handler
.SignalError(IostatShortRead
);
884 if (Frame()[recordOffsetInFrame_
+ *recordLength
] != '\n') {
885 handler
.SignalError(IostatMissingTerminator
);
888 if (*recordLength
> 0 &&
889 Frame()[recordOffsetInFrame_
+ *recordLength
- 1] == '\r') {
894 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler
&handler
) {
895 if (impliedEndfile_
) {
896 impliedEndfile_
= false;
897 if (access
!= Access::Direct
&& IsRecordFile() && mayPosition()) {
903 void ExternalFileUnit::DoEndfile(IoErrorHandler
&handler
) {
904 if (IsRecordFile() && access
!= Access::Direct
) {
905 furthestPositionInRecord
=
906 std::max(positionInRecord
, furthestPositionInRecord
);
907 if (furthestPositionInRecord
> 0) {
908 // Last read/write was non-advancing, so AdvanceRecord() was not called.
909 leftTabLimit
.reset();
910 ++currentRecordNumber
;
912 endfileRecordNumber
= currentRecordNumber
;
914 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
915 recordOffsetInFrame_
= 0;
916 FlushOutput(handler
);
917 Truncate(frameOffsetInFile_
, handler
);
918 TruncateFrame(frameOffsetInFile_
, handler
);
920 impliedEndfile_
= false;
923 void ExternalFileUnit::CommitWrites() {
924 frameOffsetInFile_
+=
925 recordOffsetInFrame_
+ recordLength
.value_or(furthestPositionInRecord
);
926 recordOffsetInFrame_
= 0;
930 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler
&handler
) {
931 if (access
== Access::Direct
) {
932 RUNTIME_CHECK(handler
, openRecl
);
933 if (!directAccessRecWasSet_
) {
935 "No REC= was specified for a data transfer with ACCESS='DIRECT'");
942 void ExternalFileUnit::HitEndOnRead(IoErrorHandler
&handler
) {
944 if (IsRecordFile() && access
!= Access::Direct
) {
945 endfileRecordNumber
= currentRecordNumber
;
949 ChildIo
&ExternalFileUnit::PushChildIo(IoStatementState
&parent
) {
950 OwningPtr
<ChildIo
> current
{std::move(child_
)};
951 Terminator
&terminator
{parent
.GetIoErrorHandler()};
952 OwningPtr
<ChildIo
> next
{New
<ChildIo
>{terminator
}(parent
, std::move(current
))};
953 child_
.reset(next
.release());
957 void ExternalFileUnit::PopChildIo(ChildIo
&child
) {
958 if (child_
.get() != &child
) {
959 child
.parent().GetIoErrorHandler().Crash(
960 "ChildIo being popped is not top of stack");
962 child_
.reset(child
.AcquirePrevious().release()); // deletes top child
965 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler
&handler
) {
966 if (!mayAsynchronous()) {
967 handler
.SignalError(IostatBadAsynchronous
);
969 } else if (auto least
{asyncIdAvailable_
.LeastElement()}) {
970 asyncIdAvailable_
.reset(*least
);
971 return static_cast<int>(*least
);
973 handler
.SignalError(IostatTooManyAsyncOps
);
978 bool ExternalFileUnit::Wait(int id
) {
979 if (static_cast<std::size_t>(id
) >= asyncIdAvailable_
.size() ||
980 asyncIdAvailable_
.test(id
)) {
983 if (id
== 0) { // means "all IDs"
984 asyncIdAvailable_
.set();
985 asyncIdAvailable_
.reset(0);
987 asyncIdAvailable_
.set(id
);
993 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset
) {
995 char *wordPtr
{reinterpret_cast<char *>(&word
)};
996 std::memcpy(wordPtr
, Frame() + frameOffset
, sizeof word
);
997 if (swapEndianness_
) {
998 SwapEndianness(wordPtr
, sizeof word
, sizeof word
);
1003 void ChildIo::EndIoStatement() {
1005 u_
.emplace
<std::monostate
>();
1008 Iostat
ChildIo::CheckFormattingAndDirection(
1009 bool unformatted
, Direction direction
) {
1010 bool parentIsInput
{!parent_
.get_if
<IoDirectionState
<Direction::Output
>>()};
1011 bool parentIsFormatted
{parentIsInput
1012 ? parent_
.get_if
<FormattedIoStatementState
<Direction::Input
>>() !=
1014 : parent_
.get_if
<FormattedIoStatementState
<Direction::Output
>>() !=
1016 bool parentIsUnformatted
{!parentIsFormatted
};
1017 if (unformatted
!= parentIsUnformatted
) {
1018 return unformatted
? IostatUnformattedChildOnFormattedParent
1019 : IostatFormattedChildOnUnformattedParent
;
1020 } else if (parentIsInput
!= (direction
== Direction::Input
)) {
1021 return parentIsInput
? IostatChildOutputToInputParent
1022 : IostatChildInputFromOutputParent
;
1028 } // namespace Fortran::runtime::io