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 //===----------------------------------------------------------------------===//
18 namespace Fortran::runtime::io
{
20 // The per-unit data structures are created on demand so that Fortran I/O
21 // should work without a Fortran main program.
22 static Lock unitMapLock
;
23 static UnitMap
*unitMap
{nullptr};
24 static ExternalFileUnit
*defaultInput
{nullptr}; // unit 5
25 static ExternalFileUnit
*defaultOutput
{nullptr}; // unit 6
26 static ExternalFileUnit
*errorOutput
{nullptr}; // unit 0 extension
28 void FlushOutputOnCrash(const Terminator
&terminator
) {
29 if (!defaultOutput
&& !errorOutput
) {
32 IoErrorHandler handler
{terminator
};
33 handler
.HasIoStat(); // prevent nested crash if flush has error
34 CriticalSection critical
{unitMapLock
};
36 defaultOutput
->FlushOutput(handler
);
39 errorOutput
->FlushOutput(handler
);
43 ExternalFileUnit
*ExternalFileUnit::LookUp(int unit
) {
44 return GetUnitMap().LookUp(unit
);
47 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreate(
48 int unit
, const Terminator
&terminator
, bool &wasExtant
) {
49 return GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
);
52 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreateAnonymous(int unit
,
53 Direction dir
, std::optional
<bool> isUnformatted
,
54 const Terminator
&terminator
) {
56 ExternalFileUnit
*result
{
57 GetUnitMap().LookUpOrCreate(unit
, terminator
, exists
)};
58 if (result
&& !exists
) {
59 IoErrorHandler handler
{terminator
};
60 result
->OpenAnonymousUnit(
61 dir
== Direction::Input
? OpenStatus::Unknown
: OpenStatus::Replace
,
62 Action::ReadWrite
, Position::Rewind
, Convert::Unknown
, handler
);
63 result
->isUnformatted
= isUnformatted
;
68 ExternalFileUnit
*ExternalFileUnit::LookUp(
69 const char *path
, std::size_t pathLen
) {
70 return GetUnitMap().LookUp(path
, pathLen
);
73 ExternalFileUnit
&ExternalFileUnit::CreateNew(
74 int unit
, const Terminator
&terminator
) {
75 bool wasExtant
{false};
76 ExternalFileUnit
*result
{
77 GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
)};
78 RUNTIME_CHECK(terminator
, result
&& !wasExtant
);
82 ExternalFileUnit
*ExternalFileUnit::LookUpForClose(int unit
) {
83 return GetUnitMap().LookUpForClose(unit
);
86 ExternalFileUnit
&ExternalFileUnit::NewUnit(
87 const Terminator
&terminator
, bool forChildIo
) {
88 ExternalFileUnit
&unit
{GetUnitMap().NewUnit(terminator
)};
89 unit
.createdForInternalChildIo_
= forChildIo
;
93 bool ExternalFileUnit::OpenUnit(std::optional
<OpenStatus
> status
,
94 std::optional
<Action
> action
, Position position
, OwningPtr
<char> &&newPath
,
95 std::size_t newPathLength
, Convert convert
, IoErrorHandler
&handler
) {
96 if (convert
== Convert::Unknown
) {
97 convert
= executionEnvironment
.conversion
;
99 swapEndianness_
= convert
== Convert::Swap
||
100 (convert
== Convert::LittleEndian
&& !isHostLittleEndian
) ||
101 (convert
== Convert::BigEndian
&& isHostLittleEndian
);
102 bool impliedClose
{false};
104 bool isSamePath
{newPath
.get() && path() && pathLength() == newPathLength
&&
105 std::memcmp(path(), newPath
.get(), newPathLength
) == 0};
106 if (status
&& *status
!= OpenStatus::Old
&& isSamePath
) {
107 handler
.SignalError("OPEN statement for connected unit may not have "
108 "explicit STATUS= other than 'OLD'");
111 if (!newPath
.get() || isSamePath
) {
112 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
116 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
117 DoImpliedEndfile(handler
);
118 FlushOutput(handler
);
119 TruncateFrame(0, handler
);
120 Close(CloseStatus::Keep
, handler
);
123 if (newPath
.get() && newPathLength
> 0) {
124 if (const auto *already
{
125 GetUnitMap().LookUp(newPath
.get(), newPathLength
)}) {
126 handler
.SignalError(IostatOpenAlreadyConnected
,
127 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
128 unitNumber_
, static_cast<int>(newPathLength
), newPath
.get(),
129 already
->unitNumber_
);
133 set_path(std::move(newPath
), newPathLength
);
134 Open(status
.value_or(OpenStatus::Unknown
), action
, position
, handler
);
135 auto totalBytes
{knownSize()};
136 if (access
== Access::Direct
) {
138 handler
.SignalError(IostatOpenBadRecl
,
139 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
141 } else if (*openRecl
<= 0) {
142 handler
.SignalError(IostatOpenBadRecl
,
143 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
144 unitNumber(), static_cast<std::intmax_t>(*openRecl
));
145 } else if (totalBytes
&& (*totalBytes
% *openRecl
!= 0)) {
146 handler
.SignalError(IostatOpenBadRecl
,
147 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
148 "even divisor of the file size %jd",
149 unitNumber(), static_cast<std::intmax_t>(*openRecl
),
150 static_cast<std::intmax_t>(*totalBytes
));
152 recordLength
= openRecl
;
154 endfileRecordNumber
.reset();
155 currentRecordNumber
= 1;
156 if (totalBytes
&& access
== Access::Direct
&& openRecl
.value_or(0) > 0) {
157 endfileRecordNumber
= 1 + (*totalBytes
/ *openRecl
);
159 if (position
== Position::Append
) {
161 frameOffsetInFile_
= *totalBytes
;
163 if (access
!= Access::Stream
) {
164 if (!endfileRecordNumber
) {
165 // Fake it so that we can backspace relative from the end
166 endfileRecordNumber
= std::numeric_limits
<std::int64_t>::max() - 2;
168 currentRecordNumber
= *endfileRecordNumber
;
174 void ExternalFileUnit::OpenAnonymousUnit(std::optional
<OpenStatus
> status
,
175 std::optional
<Action
> action
, Position position
, Convert convert
,
176 IoErrorHandler
&handler
) {
177 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
178 std::size_t pathMaxLen
{32};
179 auto path
{SizedNew
<char>{handler
}(pathMaxLen
)};
180 std::snprintf(path
.get(), pathMaxLen
, "fort.%d", unitNumber_
);
181 OpenUnit(status
, action
, position
, std::move(path
), std::strlen(path
.get()),
185 void ExternalFileUnit::CloseUnit(CloseStatus status
, IoErrorHandler
&handler
) {
186 DoImpliedEndfile(handler
);
187 FlushOutput(handler
);
188 Close(status
, handler
);
191 void ExternalFileUnit::DestroyClosed() {
192 GetUnitMap().DestroyClosed(*this); // destroys *this
195 Iostat
ExternalFileUnit::SetDirection(Direction direction
) {
196 if (direction
== Direction::Input
) {
198 direction_
= Direction::Input
;
201 return IostatReadFromWriteOnly
;
205 direction_
= Direction::Output
;
208 return IostatWriteToReadOnly
;
213 UnitMap
&ExternalFileUnit::CreateUnitMap() {
214 Terminator terminator
{__FILE__
, __LINE__
};
215 IoErrorHandler handler
{terminator
};
216 UnitMap
&newUnitMap
{*New
<UnitMap
>{terminator
}().release()};
218 bool wasExtant
{false};
219 ExternalFileUnit
&out
{*newUnitMap
.LookUpOrCreate(6, terminator
, wasExtant
)};
220 RUNTIME_CHECK(terminator
, !wasExtant
);
222 handler
.SignalError(out
.SetDirection(Direction::Output
));
223 out
.isUnformatted
= false;
224 defaultOutput
= &out
;
226 ExternalFileUnit
&in
{*newUnitMap
.LookUpOrCreate(5, terminator
, wasExtant
)};
227 RUNTIME_CHECK(terminator
, !wasExtant
);
229 handler
.SignalError(in
.SetDirection(Direction::Input
));
230 in
.isUnformatted
= false;
233 ExternalFileUnit
&error
{*newUnitMap
.LookUpOrCreate(0, terminator
, wasExtant
)};
234 RUNTIME_CHECK(terminator
, !wasExtant
);
236 handler
.SignalError(error
.SetDirection(Direction::Output
));
237 error
.isUnformatted
= false;
238 errorOutput
= &error
;
243 // A back-up atexit() handler for programs that don't terminate with a main
244 // program END or a STOP statement or other Fortran-initiated program shutdown,
245 // such as programs with a C main() that terminate normally. It flushes all
246 // external I/O units. It is registered once the first time that any external
248 static void CloseAllExternalUnits() {
249 IoErrorHandler handler
{"Fortran program termination"};
250 ExternalFileUnit::CloseAll(handler
);
253 UnitMap
&ExternalFileUnit::GetUnitMap() {
258 CriticalSection critical
{unitMapLock
};
262 unitMap
= &CreateUnitMap();
264 std::atexit(CloseAllExternalUnits
);
268 void ExternalFileUnit::CloseAll(IoErrorHandler
&handler
) {
269 CriticalSection critical
{unitMapLock
};
271 unitMap
->CloseAll(handler
);
272 FreeMemoryAndNullify(unitMap
);
274 defaultOutput
= nullptr;
275 defaultInput
= nullptr;
276 errorOutput
= nullptr;
279 void ExternalFileUnit::FlushAll(IoErrorHandler
&handler
) {
280 CriticalSection critical
{unitMapLock
};
282 unitMap
->FlushAll(handler
);
286 static inline void SwapEndianness(
287 char *data
, std::size_t bytes
, std::size_t elementBytes
) {
288 if (elementBytes
> 1) {
289 auto half
{elementBytes
>> 1};
290 for (std::size_t j
{0}; j
+ elementBytes
<= bytes
; j
+= elementBytes
) {
291 for (std::size_t k
{0}; k
< half
; ++k
) {
292 std::swap(data
[j
+ k
], data
[j
+ elementBytes
- 1 - k
]);
298 bool ExternalFileUnit::Emit(const char *data
, std::size_t bytes
,
299 std::size_t elementBytes
, IoErrorHandler
&handler
) {
300 auto furthestAfter
{std::max(furthestPositionInRecord
,
301 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
303 // Check for fixed-length record overrun, but allow for
304 // sequential record termination.
307 if (access
== Access::Sequential
) {
308 if (isUnformatted
.value_or(false)) {
309 // record header + footer
310 header
= static_cast<int>(sizeof(std::uint32_t));
314 if (!isWindowsTextFile()) {
315 ++extra
; // carriage return (CR)
318 ++extra
; // newline (LF)
321 if (furthestAfter
> extra
+ *openRecl
) {
322 handler
.SignalError(IostatRecordWriteOverrun
,
323 "Attempt to write %zd bytes to position %jd in a fixed-size record "
325 bytes
, static_cast<std::intmax_t>(positionInRecord
- header
),
326 static_cast<std::intmax_t>(*openRecl
));
331 // It is possible for recordLength to have a value now for a
332 // variable-length output record if the previous operation
333 // was a BACKSPACE or non advancing input statement.
334 recordLength
.reset();
335 beganReadingRecord_
= false;
337 if (IsAfterEndfile()) {
338 handler
.SignalError(IostatWriteAfterEndfile
);
341 CheckDirectAccess(handler
);
342 WriteFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ furthestAfter
, handler
);
343 if (positionInRecord
> furthestPositionInRecord
) {
344 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
, ' ',
345 positionInRecord
- furthestPositionInRecord
);
347 char *to
{Frame() + recordOffsetInFrame_
+ positionInRecord
};
348 std::memcpy(to
, data
, bytes
);
349 if (swapEndianness_
) {
350 SwapEndianness(to
, bytes
, elementBytes
);
352 positionInRecord
+= bytes
;
353 furthestPositionInRecord
= furthestAfter
;
357 bool ExternalFileUnit::Receive(char *data
, std::size_t bytes
,
358 std::size_t elementBytes
, IoErrorHandler
&handler
) {
359 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
360 auto furthestAfter
{std::max(furthestPositionInRecord
,
361 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
362 if (furthestAfter
> recordLength
.value_or(furthestAfter
)) {
363 handler
.SignalError(IostatRecordReadOverrun
,
364 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
365 bytes
, static_cast<std::intmax_t>(positionInRecord
),
366 static_cast<std::intmax_t>(*recordLength
));
369 auto need
{recordOffsetInFrame_
+ furthestAfter
};
370 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
372 std::memcpy(data
, Frame() + recordOffsetInFrame_
+ positionInRecord
, bytes
);
373 if (swapEndianness_
) {
374 SwapEndianness(data
, bytes
, elementBytes
);
376 positionInRecord
+= bytes
;
377 furthestPositionInRecord
= furthestAfter
;
380 HitEndOnRead(handler
);
385 std::size_t ExternalFileUnit::GetNextInputBytes(
386 const char *&p
, IoErrorHandler
&handler
) {
387 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
388 std::size_t length
{1};
389 if (auto recl
{EffectiveRecordLength()}) {
390 if (positionInRecord
< *recl
) {
391 length
= *recl
- positionInRecord
;
397 p
= FrameNextInput(handler
, length
);
398 return p
? length
: 0;
401 const char *ExternalFileUnit::FrameNextInput(
402 IoErrorHandler
&handler
, std::size_t bytes
) {
403 RUNTIME_CHECK(handler
, isUnformatted
.has_value() && !*isUnformatted
);
404 if (static_cast<std::int64_t>(positionInRecord
+ bytes
) <=
405 recordLength
.value_or(positionInRecord
+ bytes
)) {
406 auto at
{recordOffsetInFrame_
+ positionInRecord
};
407 auto need
{static_cast<std::size_t>(at
+ bytes
)};
408 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
409 SetVariableFormattedRecordLength();
413 HitEndOnRead(handler
);
418 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
419 if (recordLength
|| access
== Access::Direct
) {
421 } else if (FrameLength() > recordOffsetInFrame_
) {
422 const char *record
{Frame() + recordOffsetInFrame_
};
423 std::size_t bytes
{FrameLength() - recordOffsetInFrame_
};
424 if (const char *nl
{FindCharacter(record
, '\n', bytes
)}) {
425 recordLength
= nl
- record
;
426 if (*recordLength
> 0 && record
[*recordLength
- 1] == '\r') {
435 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler
&handler
) {
436 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
437 if (!beganReadingRecord_
) {
438 beganReadingRecord_
= true;
439 if (access
== Access::Direct
) {
440 CheckDirectAccess(handler
);
441 auto need
{static_cast<std::size_t>(recordOffsetInFrame_
+ *openRecl
)};
442 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
444 recordLength
= openRecl
;
446 recordLength
.reset();
447 HitEndOnRead(handler
);
450 recordLength
.reset();
454 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
455 if (*isUnformatted
) {
456 if (access
== Access::Sequential
) {
457 BeginSequentialVariableUnformattedInputRecord(handler
);
459 } else { // formatted sequential or stream
460 BeginVariableFormattedInputRecord(handler
);
465 RUNTIME_CHECK(handler
,
466 recordLength
.has_value() || !IsRecordFile() || handler
.InError());
467 return !handler
.InError();
470 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler
&handler
) {
471 RUNTIME_CHECK(handler
, direction_
== Direction::Input
&& beganReadingRecord_
);
472 beganReadingRecord_
= false;
473 if (handler
.GetIoStat() == IostatEnd
||
474 (IsRecordFile() && !recordLength
.has_value())) {
475 // Avoid bogus crashes in END/ERR circumstances; but
476 // still increment the current record number so that
477 // an attempted read of an endfile record, followed by
478 // a BACKSPACE, will still be at EOF.
479 ++currentRecordNumber
;
480 } else if (IsRecordFile()) {
481 recordOffsetInFrame_
+= *recordLength
;
482 if (access
!= Access::Direct
) {
483 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
484 recordLength
.reset();
485 if (isUnformatted
.value_or(false)) {
486 // Retain footer in frame for more efficient BACKSPACE
487 frameOffsetInFile_
+= recordOffsetInFrame_
;
488 recordOffsetInFrame_
= sizeof(std::uint32_t);
489 } else { // formatted
490 if (FrameLength() > recordOffsetInFrame_
&&
491 Frame()[recordOffsetInFrame_
] == '\r') {
492 ++recordOffsetInFrame_
;
494 if (FrameLength() > recordOffsetInFrame_
&&
495 Frame()[recordOffsetInFrame_
] == '\n') {
496 ++recordOffsetInFrame_
;
498 if (!pinnedFrame
|| mayPosition()) {
499 frameOffsetInFile_
+= recordOffsetInFrame_
;
500 recordOffsetInFrame_
= 0;
504 ++currentRecordNumber
;
505 } else { // unformatted stream
506 furthestPositionInRecord
=
507 std::max(furthestPositionInRecord
, positionInRecord
);
508 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
513 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler
&handler
) {
514 if (direction_
== Direction::Input
) {
515 FinishReadingRecord(handler
);
516 return BeginReadingRecord(handler
);
517 } else { // Direction::Output
519 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
520 positionInRecord
= furthestPositionInRecord
;
521 if (access
== Access::Direct
) {
522 if (furthestPositionInRecord
<
523 openRecl
.value_or(furthestPositionInRecord
)) {
524 // Pad remainder of fixed length record
526 frameOffsetInFile_
, recordOffsetInFrame_
+ *openRecl
, handler
);
527 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
,
528 isUnformatted
.value_or(false) ? 0 : ' ',
529 *openRecl
- furthestPositionInRecord
);
530 furthestPositionInRecord
= *openRecl
;
532 } else if (*isUnformatted
) {
533 if (access
== Access::Sequential
) {
534 // Append the length of a sequential unformatted variable-length record
535 // as its footer, then overwrite the reserved first four bytes of the
536 // record with its length as its header. These four bytes were skipped
537 // over in BeginUnformattedIO<Output>().
538 // TODO: Break very large records up into subrecords with negative
539 // headers &/or footers
540 std::uint32_t length
;
541 length
= furthestPositionInRecord
- sizeof length
;
543 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
544 sizeof length
, handler
);
545 positionInRecord
= 0;
547 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
548 sizeof length
, handler
);
550 // Unformatted stream: nothing to do
552 } else if (handler
.GetIoStat() != IostatOk
&&
553 furthestPositionInRecord
== 0) {
554 // Error in formatted variable length record, and no output yet; do
555 // nothing, like most other Fortran compilers do.
558 // Terminate formatted variable length record
559 const char *lineEnding
{"\n"};
560 std::size_t lineEndingBytes
{1};
562 if (!isWindowsTextFile()) {
567 ok
= ok
&& Emit(lineEnding
, lineEndingBytes
, 1, handler
);
569 leftTabLimit
.reset();
570 if (IsAfterEndfile()) {
574 ++currentRecordNumber
;
575 if (access
!= Access::Direct
) {
576 impliedEndfile_
= IsRecordFile();
578 endfileRecordNumber
.reset();
585 void ExternalFileUnit::BackspaceRecord(IoErrorHandler
&handler
) {
586 if (access
== Access::Direct
|| !IsRecordFile()) {
587 handler
.SignalError(IostatBackspaceNonSequential
,
588 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
591 if (IsAfterEndfile()) {
592 // BACKSPACE after explicit ENDFILE
593 currentRecordNumber
= *endfileRecordNumber
;
594 } else if (leftTabLimit
) {
595 // BACKSPACE after non-advancing I/O
596 leftTabLimit
.reset();
598 DoImpliedEndfile(handler
);
599 if (frameOffsetInFile_
+ recordOffsetInFrame_
> 0) {
600 --currentRecordNumber
;
601 if (openRecl
&& access
== Access::Direct
) {
602 BackspaceFixedRecord(handler
);
604 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
605 if (isUnformatted
.value_or(false)) {
606 BackspaceVariableUnformattedRecord(handler
);
608 BackspaceVariableFormattedRecord(handler
);
617 void ExternalFileUnit::FlushOutput(IoErrorHandler
&handler
) {
618 if (!mayPosition()) {
619 auto frameAt
{FrameAt()};
620 if (frameOffsetInFile_
>= frameAt
&&
622 static_cast<std::int64_t>(frameAt
+ FrameLength())) {
623 // A Flush() that's about to happen to a non-positionable file
624 // needs to advance frameOffsetInFile_ to prevent attempts at
627 leftTabLimit
.reset();
633 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler
&handler
) {
635 FlushOutput(handler
);
639 void ExternalFileUnit::Endfile(IoErrorHandler
&handler
) {
640 if (access
== Access::Direct
) {
641 handler
.SignalError(IostatEndfileDirect
,
642 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
643 } else if (!mayWrite()) {
644 handler
.SignalError(IostatEndfileUnwritable
,
645 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
646 } else if (IsAfterEndfile()) {
647 // ENDFILE after ENDFILE
650 if (IsRecordFile() && access
!= Access::Direct
) {
651 // Explicit ENDFILE leaves position *after* the endfile record
652 RUNTIME_CHECK(handler
, endfileRecordNumber
.has_value());
653 currentRecordNumber
= *endfileRecordNumber
+ 1;
658 void ExternalFileUnit::Rewind(IoErrorHandler
&handler
) {
659 if (access
== Access::Direct
) {
660 handler
.SignalError(IostatRewindNonSequential
,
661 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
663 SetPosition(0, handler
);
664 currentRecordNumber
= 1;
665 leftTabLimit
.reset();
669 void ExternalFileUnit::SetPosition(std::int64_t pos
, IoErrorHandler
&handler
) {
670 DoImpliedEndfile(handler
);
671 frameOffsetInFile_
= pos
;
672 recordOffsetInFrame_
= 0;
673 if (access
== Access::Direct
) {
674 directAccessRecWasSet_
= true;
679 bool ExternalFileUnit::SetStreamPos(
680 std::int64_t oneBasedPos
, IoErrorHandler
&handler
) {
681 if (access
!= Access::Stream
) {
682 handler
.SignalError("POS= may not appear unless ACCESS='STREAM'");
685 if (oneBasedPos
< 1) { // POS=1 is beginning of file (12.6.2.11)
687 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos
));
690 SetPosition(oneBasedPos
- 1, handler
);
691 // We no longer know which record we're in. Set currentRecordNumber to
692 // a large value from whence we can both advance and backspace.
693 currentRecordNumber
= std::numeric_limits
<std::int64_t>::max() / 2;
694 endfileRecordNumber
.reset();
698 bool ExternalFileUnit::SetDirectRec(
699 std::int64_t oneBasedRec
, IoErrorHandler
&handler
) {
700 if (access
!= Access::Direct
) {
701 handler
.SignalError("REC= may not appear unless ACCESS='DIRECT'");
705 handler
.SignalError("RECL= was not specified");
708 if (oneBasedRec
< 1) {
710 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec
));
713 currentRecordNumber
= oneBasedRec
;
714 SetPosition((oneBasedRec
- 1) * *openRecl
, handler
);
718 void ExternalFileUnit::EndIoStatement() {
720 u_
.emplace
<std::monostate
>();
724 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
725 IoErrorHandler
&handler
) {
726 std::int32_t header
{0}, footer
{0};
727 std::size_t need
{recordOffsetInFrame_
+ sizeof header
};
728 std::size_t got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
729 // Try to emit informative errors to help debug corrupted files.
730 const char *error
{nullptr};
732 if (got
== recordOffsetInFrame_
) {
733 HitEndOnRead(handler
);
735 error
= "Unformatted variable-length sequential file input failed at "
736 "record #%jd (file offset %jd): truncated record header";
739 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
740 recordLength
= sizeof header
+ header
; // does not include footer
741 need
= recordOffsetInFrame_
+ *recordLength
+ sizeof footer
;
742 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
744 error
= "Unformatted variable-length sequential file input failed at "
745 "record #%jd (file offset %jd): hit EOF reading record with "
748 footer
= ReadHeaderOrFooter(recordOffsetInFrame_
+ *recordLength
);
749 if (footer
!= header
) {
750 error
= "Unformatted variable-length sequential file input failed at "
751 "record #%jd (file offset %jd): record header has length %jd "
752 "that does not match record footer (%jd)";
757 handler
.SignalError(error
, static_cast<std::intmax_t>(currentRecordNumber
),
758 static_cast<std::intmax_t>(frameOffsetInFile_
),
759 static_cast<std::intmax_t>(header
), static_cast<std::intmax_t>(footer
));
760 // TODO: error recovery
762 positionInRecord
= sizeof header
;
765 void ExternalFileUnit::BeginVariableFormattedInputRecord(
766 IoErrorHandler
&handler
) {
767 if (this == defaultInput
) {
769 defaultOutput
->FlushOutput(handler
);
772 errorOutput
->FlushOutput(handler
);
775 std::size_t length
{0};
777 std::size_t need
{length
+ 1};
779 ReadFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ need
, handler
) -
780 recordOffsetInFrame_
;
783 // final record w/o \n
784 recordLength
= length
;
785 unterminatedRecord
= true;
787 HitEndOnRead(handler
);
791 } while (!SetVariableFormattedRecordLength());
794 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler
&handler
) {
795 RUNTIME_CHECK(handler
, openRecl
.has_value());
796 if (frameOffsetInFile_
< *openRecl
) {
797 handler
.SignalError(IostatBackspaceAtFirstRecord
);
799 frameOffsetInFile_
-= *openRecl
;
803 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
804 IoErrorHandler
&handler
) {
805 std::int32_t header
{0};
806 auto headerBytes
{static_cast<std::int64_t>(sizeof header
)};
807 frameOffsetInFile_
+= recordOffsetInFrame_
;
808 recordOffsetInFrame_
= 0;
809 if (frameOffsetInFile_
<= headerBytes
) {
810 handler
.SignalError(IostatBackspaceAtFirstRecord
);
813 // Error conditions here cause crashes, not file format errors, because the
814 // validity of the file structure before the current record will have been
815 // checked informatively in NextSequentialVariableUnformattedInputRecord().
817 ReadFrame(frameOffsetInFile_
- headerBytes
, headerBytes
, handler
)};
818 if (static_cast<std::int64_t>(got
) < headerBytes
) {
819 handler
.SignalError(IostatShortRead
);
822 recordLength
= ReadHeaderOrFooter(0);
823 if (frameOffsetInFile_
< *recordLength
+ 2 * headerBytes
) {
824 handler
.SignalError(IostatBadUnformattedRecord
);
827 frameOffsetInFile_
-= *recordLength
+ 2 * headerBytes
;
828 if (frameOffsetInFile_
>= headerBytes
) {
829 frameOffsetInFile_
-= headerBytes
;
830 recordOffsetInFrame_
= headerBytes
;
832 auto need
{static_cast<std::size_t>(
833 recordOffsetInFrame_
+ sizeof header
+ *recordLength
)};
834 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
836 handler
.SignalError(IostatShortRead
);
839 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
840 if (header
!= *recordLength
) {
841 handler
.SignalError(IostatBadUnformattedRecord
);
846 // There's no portable memrchr(), unfortunately, and strrchr() would
847 // fail on a record with a NUL, so we have to do it the hard way.
848 static const char *FindLastNewline(const char *str
, std::size_t length
) {
849 for (const char *p
{str
+ length
}; p
>= str
; p
--) {
857 void ExternalFileUnit::BackspaceVariableFormattedRecord(
858 IoErrorHandler
&handler
) {
859 // File offset of previous record's newline
861 frameOffsetInFile_
+ static_cast<std::int64_t>(recordOffsetInFrame_
) - 1};
863 handler
.SignalError(IostatBackspaceAtFirstRecord
);
867 if (frameOffsetInFile_
< prevNL
) {
869 FindLastNewline(Frame(), prevNL
- 1 - frameOffsetInFile_
)}) {
870 recordOffsetInFrame_
= p
- Frame() + 1;
871 recordLength
= prevNL
- (frameOffsetInFile_
+ recordOffsetInFrame_
);
875 if (frameOffsetInFile_
== 0) {
876 recordOffsetInFrame_
= 0;
877 recordLength
= prevNL
;
880 frameOffsetInFile_
-= std::min
<std::int64_t>(frameOffsetInFile_
, 1024);
881 auto need
{static_cast<std::size_t>(prevNL
+ 1 - frameOffsetInFile_
)};
882 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
884 handler
.SignalError(IostatShortRead
);
888 if (Frame()[recordOffsetInFrame_
+ *recordLength
] != '\n') {
889 handler
.SignalError(IostatMissingTerminator
);
892 if (*recordLength
> 0 &&
893 Frame()[recordOffsetInFrame_
+ *recordLength
- 1] == '\r') {
898 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler
&handler
) {
899 if (!impliedEndfile_
&& direction_
== Direction::Output
&& IsRecordFile() &&
900 access
!= Access::Direct
&& leftTabLimit
) {
901 // Complete partial record after non-advancing write before
902 // positioning or closing the unit. Usually sets impliedEndfile_.
903 AdvanceRecord(handler
);
905 if (impliedEndfile_
) {
906 impliedEndfile_
= false;
907 if (access
!= Access::Direct
&& IsRecordFile() && mayPosition()) {
913 void ExternalFileUnit::DoEndfile(IoErrorHandler
&handler
) {
914 if (IsRecordFile() && access
!= Access::Direct
) {
915 furthestPositionInRecord
=
916 std::max(positionInRecord
, furthestPositionInRecord
);
918 // Last read/write was non-advancing, so AdvanceRecord() was not called.
919 leftTabLimit
.reset();
920 ++currentRecordNumber
;
922 endfileRecordNumber
= currentRecordNumber
;
924 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
925 recordOffsetInFrame_
= 0;
926 FlushOutput(handler
);
927 Truncate(frameOffsetInFile_
, handler
);
928 TruncateFrame(frameOffsetInFile_
, handler
);
930 impliedEndfile_
= false;
933 void ExternalFileUnit::CommitWrites() {
934 frameOffsetInFile_
+=
935 recordOffsetInFrame_
+ recordLength
.value_or(furthestPositionInRecord
);
936 recordOffsetInFrame_
= 0;
940 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler
&handler
) {
941 if (access
== Access::Direct
) {
942 RUNTIME_CHECK(handler
, openRecl
);
943 if (!directAccessRecWasSet_
) {
945 "No REC= was specified for a data transfer with ACCESS='DIRECT'");
952 void ExternalFileUnit::HitEndOnRead(IoErrorHandler
&handler
) {
954 if (IsRecordFile() && access
!= Access::Direct
) {
955 endfileRecordNumber
= currentRecordNumber
;
959 ChildIo
&ExternalFileUnit::PushChildIo(IoStatementState
&parent
) {
960 OwningPtr
<ChildIo
> current
{std::move(child_
)};
961 Terminator
&terminator
{parent
.GetIoErrorHandler()};
962 OwningPtr
<ChildIo
> next
{New
<ChildIo
>{terminator
}(parent
, std::move(current
))};
963 child_
.reset(next
.release());
967 void ExternalFileUnit::PopChildIo(ChildIo
&child
) {
968 if (child_
.get() != &child
) {
969 child
.parent().GetIoErrorHandler().Crash(
970 "ChildIo being popped is not top of stack");
972 child_
.reset(child
.AcquirePrevious().release()); // deletes top child
975 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler
&handler
) {
976 if (!mayAsynchronous()) {
977 handler
.SignalError(IostatBadAsynchronous
);
979 } else if (auto least
{asyncIdAvailable_
.LeastElement()}) {
980 asyncIdAvailable_
.reset(*least
);
981 return static_cast<int>(*least
);
983 handler
.SignalError(IostatTooManyAsyncOps
);
988 bool ExternalFileUnit::Wait(int id
) {
989 if (static_cast<std::size_t>(id
) >= asyncIdAvailable_
.size() ||
990 asyncIdAvailable_
.test(id
)) {
993 if (id
== 0) { // means "all IDs"
994 asyncIdAvailable_
.set();
995 asyncIdAvailable_
.reset(0);
997 asyncIdAvailable_
.set(id
);
1003 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset
) {
1005 char *wordPtr
{reinterpret_cast<char *>(&word
)};
1006 std::memcpy(wordPtr
, Frame() + frameOffset
, sizeof word
);
1007 if (swapEndianness_
) {
1008 SwapEndianness(wordPtr
, sizeof word
, sizeof word
);
1013 void ChildIo::EndIoStatement() {
1015 u_
.emplace
<std::monostate
>();
1018 Iostat
ChildIo::CheckFormattingAndDirection(
1019 bool unformatted
, Direction direction
) {
1020 bool parentIsInput
{!parent_
.get_if
<IoDirectionState
<Direction::Output
>>()};
1021 bool parentIsFormatted
{parentIsInput
1022 ? parent_
.get_if
<FormattedIoStatementState
<Direction::Input
>>() !=
1024 : parent_
.get_if
<FormattedIoStatementState
<Direction::Output
>>() !=
1026 bool parentIsUnformatted
{!parentIsFormatted
};
1027 if (unformatted
!= parentIsUnformatted
) {
1028 return unformatted
? IostatUnformattedChildOnFormattedParent
1029 : IostatFormattedChildOnUnformattedParent
;
1030 } else if (parentIsInput
!= (direction
== Direction::Input
)) {
1031 return parentIsInput
? IostatChildOutputToInputParent
1032 : IostatChildInputFromOutputParent
;
1038 } // namespace Fortran::runtime::io