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 Lock createOpenLock
;
24 static UnitMap
*unitMap
{nullptr};
25 static ExternalFileUnit
*defaultInput
{nullptr}; // unit 5
26 static ExternalFileUnit
*defaultOutput
{nullptr}; // unit 6
27 static ExternalFileUnit
*errorOutput
{nullptr}; // unit 0 extension
29 void FlushOutputOnCrash(const Terminator
&terminator
) {
30 if (!defaultOutput
&& !errorOutput
) {
33 IoErrorHandler handler
{terminator
};
34 handler
.HasIoStat(); // prevent nested crash if flush has error
35 CriticalSection critical
{unitMapLock
};
37 defaultOutput
->FlushOutput(handler
);
40 errorOutput
->FlushOutput(handler
);
44 ExternalFileUnit
*ExternalFileUnit::LookUp(int unit
) {
45 return GetUnitMap().LookUp(unit
);
48 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreate(
49 int unit
, const Terminator
&terminator
, bool &wasExtant
) {
50 return GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
);
53 ExternalFileUnit
*ExternalFileUnit::LookUpOrCreateAnonymous(int unit
,
54 Direction dir
, std::optional
<bool> isUnformatted
,
55 const Terminator
&terminator
) {
56 // Make sure that the returned anonymous unit has been opened
57 // not just created in the unitMap.
58 CriticalSection critical
{createOpenLock
};
60 ExternalFileUnit
*result
{
61 GetUnitMap().LookUpOrCreate(unit
, terminator
, exists
)};
62 if (result
&& !exists
) {
63 IoErrorHandler handler
{terminator
};
64 result
->OpenAnonymousUnit(
65 dir
== Direction::Input
? OpenStatus::Unknown
: OpenStatus::Replace
,
66 Action::ReadWrite
, Position::Rewind
, Convert::Unknown
, handler
);
67 result
->isUnformatted
= isUnformatted
;
72 ExternalFileUnit
*ExternalFileUnit::LookUp(
73 const char *path
, std::size_t pathLen
) {
74 return GetUnitMap().LookUp(path
, pathLen
);
77 ExternalFileUnit
&ExternalFileUnit::CreateNew(
78 int unit
, const Terminator
&terminator
) {
79 bool wasExtant
{false};
80 ExternalFileUnit
*result
{
81 GetUnitMap().LookUpOrCreate(unit
, terminator
, wasExtant
)};
82 RUNTIME_CHECK(terminator
, result
&& !wasExtant
);
86 ExternalFileUnit
*ExternalFileUnit::LookUpForClose(int unit
) {
87 return GetUnitMap().LookUpForClose(unit
);
90 ExternalFileUnit
&ExternalFileUnit::NewUnit(
91 const Terminator
&terminator
, bool forChildIo
) {
92 ExternalFileUnit
&unit
{GetUnitMap().NewUnit(terminator
)};
93 unit
.createdForInternalChildIo_
= forChildIo
;
97 bool ExternalFileUnit::OpenUnit(std::optional
<OpenStatus
> status
,
98 std::optional
<Action
> action
, Position position
, OwningPtr
<char> &&newPath
,
99 std::size_t newPathLength
, Convert convert
, IoErrorHandler
&handler
) {
100 if (convert
== Convert::Unknown
) {
101 convert
= executionEnvironment
.conversion
;
103 swapEndianness_
= convert
== Convert::Swap
||
104 (convert
== Convert::LittleEndian
&& !isHostLittleEndian
) ||
105 (convert
== Convert::BigEndian
&& isHostLittleEndian
);
106 bool impliedClose
{false};
108 bool isSamePath
{newPath
.get() && path() && pathLength() == newPathLength
&&
109 std::memcmp(path(), newPath
.get(), newPathLength
) == 0};
110 if (status
&& *status
!= OpenStatus::Old
&& isSamePath
) {
111 handler
.SignalError("OPEN statement for connected unit may not have "
112 "explicit STATUS= other than 'OLD'");
115 if (!newPath
.get() || isSamePath
) {
116 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
120 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
121 DoImpliedEndfile(handler
);
122 FlushOutput(handler
);
123 TruncateFrame(0, handler
);
124 Close(CloseStatus::Keep
, handler
);
127 if (newPath
.get() && newPathLength
> 0) {
128 if (const auto *already
{
129 GetUnitMap().LookUp(newPath
.get(), newPathLength
)}) {
130 handler
.SignalError(IostatOpenAlreadyConnected
,
131 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
132 unitNumber_
, static_cast<int>(newPathLength
), newPath
.get(),
133 already
->unitNumber_
);
137 set_path(std::move(newPath
), newPathLength
);
138 Open(status
.value_or(OpenStatus::Unknown
), action
, position
, handler
);
139 auto totalBytes
{knownSize()};
140 if (access
== Access::Direct
) {
142 handler
.SignalError(IostatOpenBadRecl
,
143 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
145 } else if (*openRecl
<= 0) {
146 handler
.SignalError(IostatOpenBadRecl
,
147 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
148 unitNumber(), static_cast<std::intmax_t>(*openRecl
));
149 } else if (totalBytes
&& (*totalBytes
% *openRecl
!= 0)) {
150 handler
.SignalError(IostatOpenBadRecl
,
151 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
152 "even divisor of the file size %jd",
153 unitNumber(), static_cast<std::intmax_t>(*openRecl
),
154 static_cast<std::intmax_t>(*totalBytes
));
156 recordLength
= openRecl
;
158 endfileRecordNumber
.reset();
159 currentRecordNumber
= 1;
160 if (totalBytes
&& access
== Access::Direct
&& openRecl
.value_or(0) > 0) {
161 endfileRecordNumber
= 1 + (*totalBytes
/ *openRecl
);
163 if (position
== Position::Append
) {
165 frameOffsetInFile_
= *totalBytes
;
167 if (access
!= Access::Stream
) {
168 if (!endfileRecordNumber
) {
169 // Fake it so that we can backspace relative from the end
170 endfileRecordNumber
= std::numeric_limits
<std::int64_t>::max() - 2;
172 currentRecordNumber
= *endfileRecordNumber
;
178 void ExternalFileUnit::OpenAnonymousUnit(std::optional
<OpenStatus
> status
,
179 std::optional
<Action
> action
, Position position
, Convert convert
,
180 IoErrorHandler
&handler
) {
181 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
182 std::size_t pathMaxLen
{32};
183 auto path
{SizedNew
<char>{handler
}(pathMaxLen
)};
184 std::snprintf(path
.get(), pathMaxLen
, "fort.%d", unitNumber_
);
185 OpenUnit(status
, action
, position
, std::move(path
), std::strlen(path
.get()),
189 void ExternalFileUnit::CloseUnit(CloseStatus status
, IoErrorHandler
&handler
) {
190 DoImpliedEndfile(handler
);
191 FlushOutput(handler
);
192 Close(status
, handler
);
195 void ExternalFileUnit::DestroyClosed() {
196 GetUnitMap().DestroyClosed(*this); // destroys *this
199 Iostat
ExternalFileUnit::SetDirection(Direction direction
) {
200 if (direction
== Direction::Input
) {
202 direction_
= Direction::Input
;
205 return IostatReadFromWriteOnly
;
209 direction_
= Direction::Output
;
212 return IostatWriteToReadOnly
;
217 UnitMap
&ExternalFileUnit::CreateUnitMap() {
218 Terminator terminator
{__FILE__
, __LINE__
};
219 IoErrorHandler handler
{terminator
};
220 UnitMap
&newUnitMap
{*New
<UnitMap
>{terminator
}().release()};
222 bool wasExtant
{false};
223 ExternalFileUnit
&out
{*newUnitMap
.LookUpOrCreate(6, terminator
, wasExtant
)};
224 RUNTIME_CHECK(terminator
, !wasExtant
);
226 handler
.SignalError(out
.SetDirection(Direction::Output
));
227 out
.isUnformatted
= false;
228 defaultOutput
= &out
;
230 ExternalFileUnit
&in
{*newUnitMap
.LookUpOrCreate(5, terminator
, wasExtant
)};
231 RUNTIME_CHECK(terminator
, !wasExtant
);
233 handler
.SignalError(in
.SetDirection(Direction::Input
));
234 in
.isUnformatted
= false;
237 ExternalFileUnit
&error
{*newUnitMap
.LookUpOrCreate(0, terminator
, wasExtant
)};
238 RUNTIME_CHECK(terminator
, !wasExtant
);
240 handler
.SignalError(error
.SetDirection(Direction::Output
));
241 error
.isUnformatted
= false;
242 errorOutput
= &error
;
247 // A back-up atexit() handler for programs that don't terminate with a main
248 // program END or a STOP statement or other Fortran-initiated program shutdown,
249 // such as programs with a C main() that terminate normally. It flushes all
250 // external I/O units. It is registered once the first time that any external
252 static void CloseAllExternalUnits() {
253 IoErrorHandler handler
{"Fortran program termination"};
254 ExternalFileUnit::CloseAll(handler
);
257 UnitMap
&ExternalFileUnit::GetUnitMap() {
262 CriticalSection critical
{unitMapLock
};
266 unitMap
= &CreateUnitMap();
268 std::atexit(CloseAllExternalUnits
);
272 void ExternalFileUnit::CloseAll(IoErrorHandler
&handler
) {
273 CriticalSection critical
{unitMapLock
};
275 unitMap
->CloseAll(handler
);
276 FreeMemoryAndNullify(unitMap
);
278 defaultOutput
= nullptr;
279 defaultInput
= nullptr;
280 errorOutput
= nullptr;
283 void ExternalFileUnit::FlushAll(IoErrorHandler
&handler
) {
284 CriticalSection critical
{unitMapLock
};
286 unitMap
->FlushAll(handler
);
290 static inline void SwapEndianness(
291 char *data
, std::size_t bytes
, std::size_t elementBytes
) {
292 if (elementBytes
> 1) {
293 auto half
{elementBytes
>> 1};
294 for (std::size_t j
{0}; j
+ elementBytes
<= bytes
; j
+= elementBytes
) {
295 for (std::size_t k
{0}; k
< half
; ++k
) {
296 std::swap(data
[j
+ k
], data
[j
+ elementBytes
- 1 - k
]);
302 bool ExternalFileUnit::Emit(const char *data
, std::size_t bytes
,
303 std::size_t elementBytes
, IoErrorHandler
&handler
) {
304 auto furthestAfter
{std::max(furthestPositionInRecord
,
305 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
307 // Check for fixed-length record overrun, but allow for
308 // sequential record termination.
311 if (access
== Access::Sequential
) {
312 if (isUnformatted
.value_or(false)) {
313 // record header + footer
314 header
= static_cast<int>(sizeof(std::uint32_t));
318 if (!isWindowsTextFile()) {
319 ++extra
; // carriage return (CR)
322 ++extra
; // newline (LF)
325 if (furthestAfter
> extra
+ *openRecl
) {
326 handler
.SignalError(IostatRecordWriteOverrun
,
327 "Attempt to write %zd bytes to position %jd in a fixed-size record "
329 bytes
, static_cast<std::intmax_t>(positionInRecord
- header
),
330 static_cast<std::intmax_t>(*openRecl
));
335 // It is possible for recordLength to have a value now for a
336 // variable-length output record if the previous operation
337 // was a BACKSPACE or non advancing input statement.
338 recordLength
.reset();
339 beganReadingRecord_
= false;
341 if (IsAfterEndfile()) {
342 handler
.SignalError(IostatWriteAfterEndfile
);
345 CheckDirectAccess(handler
);
346 WriteFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ furthestAfter
, handler
);
347 if (positionInRecord
> furthestPositionInRecord
) {
348 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
, ' ',
349 positionInRecord
- furthestPositionInRecord
);
351 char *to
{Frame() + recordOffsetInFrame_
+ positionInRecord
};
352 std::memcpy(to
, data
, bytes
);
353 if (swapEndianness_
) {
354 SwapEndianness(to
, bytes
, elementBytes
);
356 positionInRecord
+= bytes
;
357 furthestPositionInRecord
= furthestAfter
;
361 bool ExternalFileUnit::Receive(char *data
, std::size_t bytes
,
362 std::size_t elementBytes
, IoErrorHandler
&handler
) {
363 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
364 auto furthestAfter
{std::max(furthestPositionInRecord
,
365 positionInRecord
+ static_cast<std::int64_t>(bytes
))};
366 if (furthestAfter
> recordLength
.value_or(furthestAfter
)) {
367 handler
.SignalError(IostatRecordReadOverrun
,
368 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
369 bytes
, static_cast<std::intmax_t>(positionInRecord
),
370 static_cast<std::intmax_t>(*recordLength
));
373 auto need
{recordOffsetInFrame_
+ furthestAfter
};
374 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
376 std::memcpy(data
, Frame() + recordOffsetInFrame_
+ positionInRecord
, bytes
);
377 if (swapEndianness_
) {
378 SwapEndianness(data
, bytes
, elementBytes
);
380 positionInRecord
+= bytes
;
381 furthestPositionInRecord
= furthestAfter
;
384 HitEndOnRead(handler
);
389 std::size_t ExternalFileUnit::GetNextInputBytes(
390 const char *&p
, IoErrorHandler
&handler
) {
391 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
392 std::size_t length
{1};
393 if (auto recl
{EffectiveRecordLength()}) {
394 if (positionInRecord
< *recl
) {
395 length
= *recl
- positionInRecord
;
401 p
= FrameNextInput(handler
, length
);
402 return p
? length
: 0;
405 const char *ExternalFileUnit::FrameNextInput(
406 IoErrorHandler
&handler
, std::size_t bytes
) {
407 RUNTIME_CHECK(handler
, isUnformatted
.has_value() && !*isUnformatted
);
408 if (static_cast<std::int64_t>(positionInRecord
+ bytes
) <=
409 recordLength
.value_or(positionInRecord
+ bytes
)) {
410 auto at
{recordOffsetInFrame_
+ positionInRecord
};
411 auto need
{static_cast<std::size_t>(at
+ bytes
)};
412 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
413 SetVariableFormattedRecordLength();
417 HitEndOnRead(handler
);
422 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
423 if (recordLength
|| access
== Access::Direct
) {
425 } else if (FrameLength() > recordOffsetInFrame_
) {
426 const char *record
{Frame() + recordOffsetInFrame_
};
427 std::size_t bytes
{FrameLength() - recordOffsetInFrame_
};
428 if (const char *nl
{FindCharacter(record
, '\n', bytes
)}) {
429 recordLength
= nl
- record
;
430 if (*recordLength
> 0 && record
[*recordLength
- 1] == '\r') {
439 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler
&handler
) {
440 RUNTIME_CHECK(handler
, direction_
== Direction::Input
);
441 if (!beganReadingRecord_
) {
442 beganReadingRecord_
= true;
443 if (access
== Access::Direct
) {
444 CheckDirectAccess(handler
);
445 auto need
{static_cast<std::size_t>(recordOffsetInFrame_
+ *openRecl
)};
446 auto got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
448 recordLength
= openRecl
;
450 recordLength
.reset();
451 HitEndOnRead(handler
);
454 recordLength
.reset();
458 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
459 if (*isUnformatted
) {
460 if (access
== Access::Sequential
) {
461 BeginSequentialVariableUnformattedInputRecord(handler
);
463 } else { // formatted sequential or stream
464 BeginVariableFormattedInputRecord(handler
);
469 RUNTIME_CHECK(handler
,
470 recordLength
.has_value() || !IsRecordFile() || handler
.InError());
471 return !handler
.InError();
474 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler
&handler
) {
475 RUNTIME_CHECK(handler
, direction_
== Direction::Input
&& beganReadingRecord_
);
476 beganReadingRecord_
= false;
477 if (handler
.GetIoStat() == IostatEnd
||
478 (IsRecordFile() && !recordLength
.has_value())) {
479 // Avoid bogus crashes in END/ERR circumstances; but
480 // still increment the current record number so that
481 // an attempted read of an endfile record, followed by
482 // a BACKSPACE, will still be at EOF.
483 ++currentRecordNumber
;
484 } else if (IsRecordFile()) {
485 recordOffsetInFrame_
+= *recordLength
;
486 if (access
!= Access::Direct
) {
487 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
488 recordLength
.reset();
489 if (isUnformatted
.value_or(false)) {
490 // Retain footer in frame for more efficient BACKSPACE
491 frameOffsetInFile_
+= recordOffsetInFrame_
;
492 recordOffsetInFrame_
= sizeof(std::uint32_t);
493 } else { // formatted
494 if (FrameLength() > recordOffsetInFrame_
&&
495 Frame()[recordOffsetInFrame_
] == '\r') {
496 ++recordOffsetInFrame_
;
498 if (FrameLength() > recordOffsetInFrame_
&&
499 Frame()[recordOffsetInFrame_
] == '\n') {
500 ++recordOffsetInFrame_
;
502 if (!pinnedFrame
|| mayPosition()) {
503 frameOffsetInFile_
+= recordOffsetInFrame_
;
504 recordOffsetInFrame_
= 0;
508 ++currentRecordNumber
;
509 } else { // unformatted stream
510 furthestPositionInRecord
=
511 std::max(furthestPositionInRecord
, positionInRecord
);
512 frameOffsetInFile_
+= recordOffsetInFrame_
+ furthestPositionInRecord
;
517 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler
&handler
) {
518 if (direction_
== Direction::Input
) {
519 FinishReadingRecord(handler
);
520 return BeginReadingRecord(handler
);
521 } else { // Direction::Output
523 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
524 positionInRecord
= furthestPositionInRecord
;
525 if (access
== Access::Direct
) {
526 if (furthestPositionInRecord
<
527 openRecl
.value_or(furthestPositionInRecord
)) {
528 // Pad remainder of fixed length record
530 frameOffsetInFile_
, recordOffsetInFrame_
+ *openRecl
, handler
);
531 std::memset(Frame() + recordOffsetInFrame_
+ furthestPositionInRecord
,
532 isUnformatted
.value_or(false) ? 0 : ' ',
533 *openRecl
- furthestPositionInRecord
);
534 furthestPositionInRecord
= *openRecl
;
536 } else if (*isUnformatted
) {
537 if (access
== Access::Sequential
) {
538 // Append the length of a sequential unformatted variable-length record
539 // as its footer, then overwrite the reserved first four bytes of the
540 // record with its length as its header. These four bytes were skipped
541 // over in BeginUnformattedIO<Output>().
542 // TODO: Break very large records up into subrecords with negative
543 // headers &/or footers
544 std::uint32_t length
;
545 length
= furthestPositionInRecord
- sizeof length
;
547 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
548 sizeof length
, handler
);
549 positionInRecord
= 0;
551 Emit(reinterpret_cast<const char *>(&length
), sizeof length
,
552 sizeof length
, handler
);
554 // Unformatted stream: nothing to do
556 } else if (handler
.GetIoStat() != IostatOk
&&
557 furthestPositionInRecord
== 0) {
558 // Error in formatted variable length record, and no output yet; do
559 // nothing, like most other Fortran compilers do.
562 // Terminate formatted variable length record
563 const char *lineEnding
{"\n"};
564 std::size_t lineEndingBytes
{1};
566 if (!isWindowsTextFile()) {
571 ok
= ok
&& Emit(lineEnding
, lineEndingBytes
, 1, handler
);
573 leftTabLimit
.reset();
574 if (IsAfterEndfile()) {
578 ++currentRecordNumber
;
579 if (access
!= Access::Direct
) {
580 impliedEndfile_
= IsRecordFile();
582 endfileRecordNumber
.reset();
589 void ExternalFileUnit::BackspaceRecord(IoErrorHandler
&handler
) {
590 if (access
== Access::Direct
|| !IsRecordFile()) {
591 handler
.SignalError(IostatBackspaceNonSequential
,
592 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
595 if (IsAfterEndfile()) {
596 // BACKSPACE after explicit ENDFILE
597 currentRecordNumber
= *endfileRecordNumber
;
598 } else if (leftTabLimit
) {
599 // BACKSPACE after non-advancing I/O
600 leftTabLimit
.reset();
602 DoImpliedEndfile(handler
);
603 if (frameOffsetInFile_
+ recordOffsetInFrame_
> 0) {
604 --currentRecordNumber
;
605 if (openRecl
&& access
== Access::Direct
) {
606 BackspaceFixedRecord(handler
);
608 RUNTIME_CHECK(handler
, isUnformatted
.has_value());
609 if (isUnformatted
.value_or(false)) {
610 BackspaceVariableUnformattedRecord(handler
);
612 BackspaceVariableFormattedRecord(handler
);
621 void ExternalFileUnit::FlushOutput(IoErrorHandler
&handler
) {
622 if (!mayPosition()) {
623 auto frameAt
{FrameAt()};
624 if (frameOffsetInFile_
>= frameAt
&&
626 static_cast<std::int64_t>(frameAt
+ FrameLength())) {
627 // A Flush() that's about to happen to a non-positionable file
628 // needs to advance frameOffsetInFile_ to prevent attempts at
631 leftTabLimit
.reset();
637 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler
&handler
) {
639 FlushOutput(handler
);
643 void ExternalFileUnit::Endfile(IoErrorHandler
&handler
) {
644 if (access
== Access::Direct
) {
645 handler
.SignalError(IostatEndfileDirect
,
646 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
647 } else if (!mayWrite()) {
648 handler
.SignalError(IostatEndfileUnwritable
,
649 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
650 } else if (IsAfterEndfile()) {
651 // ENDFILE after ENDFILE
654 if (IsRecordFile() && access
!= Access::Direct
) {
655 // Explicit ENDFILE leaves position *after* the endfile record
656 RUNTIME_CHECK(handler
, endfileRecordNumber
.has_value());
657 currentRecordNumber
= *endfileRecordNumber
+ 1;
662 void ExternalFileUnit::Rewind(IoErrorHandler
&handler
) {
663 if (access
== Access::Direct
) {
664 handler
.SignalError(IostatRewindNonSequential
,
665 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
667 SetPosition(0, handler
);
668 currentRecordNumber
= 1;
669 leftTabLimit
.reset();
673 void ExternalFileUnit::SetPosition(std::int64_t pos
, IoErrorHandler
&handler
) {
674 DoImpliedEndfile(handler
);
675 frameOffsetInFile_
= pos
;
676 recordOffsetInFrame_
= 0;
677 if (access
== Access::Direct
) {
678 directAccessRecWasSet_
= true;
683 bool ExternalFileUnit::SetStreamPos(
684 std::int64_t oneBasedPos
, IoErrorHandler
&handler
) {
685 if (access
!= Access::Stream
) {
686 handler
.SignalError("POS= may not appear unless ACCESS='STREAM'");
689 if (oneBasedPos
< 1) { // POS=1 is beginning of file (12.6.2.11)
691 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos
));
694 SetPosition(oneBasedPos
- 1, handler
);
695 // We no longer know which record we're in. Set currentRecordNumber to
696 // a large value from whence we can both advance and backspace.
697 currentRecordNumber
= std::numeric_limits
<std::int64_t>::max() / 2;
698 endfileRecordNumber
.reset();
702 bool ExternalFileUnit::SetDirectRec(
703 std::int64_t oneBasedRec
, IoErrorHandler
&handler
) {
704 if (access
!= Access::Direct
) {
705 handler
.SignalError("REC= may not appear unless ACCESS='DIRECT'");
709 handler
.SignalError("RECL= was not specified");
712 if (oneBasedRec
< 1) {
714 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec
));
717 currentRecordNumber
= oneBasedRec
;
718 SetPosition((oneBasedRec
- 1) * *openRecl
, handler
);
722 void ExternalFileUnit::EndIoStatement() {
724 u_
.emplace
<std::monostate
>();
728 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
729 IoErrorHandler
&handler
) {
730 std::int32_t header
{0}, footer
{0};
731 std::size_t need
{recordOffsetInFrame_
+ sizeof header
};
732 std::size_t got
{ReadFrame(frameOffsetInFile_
, need
, handler
)};
733 // Try to emit informative errors to help debug corrupted files.
734 const char *error
{nullptr};
736 if (got
== recordOffsetInFrame_
) {
737 HitEndOnRead(handler
);
739 error
= "Unformatted variable-length sequential file input failed at "
740 "record #%jd (file offset %jd): truncated record header";
743 header
= ReadHeaderOrFooter(recordOffsetInFrame_
);
744 recordLength
= sizeof header
+ header
; // does not include footer
745 need
= recordOffsetInFrame_
+ *recordLength
+ sizeof footer
;
746 got
= ReadFrame(frameOffsetInFile_
, need
, handler
);
748 error
= "Unformatted variable-length sequential file input failed at "
749 "record #%jd (file offset %jd): hit EOF reading record with "
752 footer
= ReadHeaderOrFooter(recordOffsetInFrame_
+ *recordLength
);
753 if (footer
!= header
) {
754 error
= "Unformatted variable-length sequential file input failed at "
755 "record #%jd (file offset %jd): record header has length %jd "
756 "that does not match record footer (%jd)";
761 handler
.SignalError(error
, static_cast<std::intmax_t>(currentRecordNumber
),
762 static_cast<std::intmax_t>(frameOffsetInFile_
),
763 static_cast<std::intmax_t>(header
), static_cast<std::intmax_t>(footer
));
764 // TODO: error recovery
766 positionInRecord
= sizeof header
;
769 void ExternalFileUnit::BeginVariableFormattedInputRecord(
770 IoErrorHandler
&handler
) {
771 if (this == defaultInput
) {
773 defaultOutput
->FlushOutput(handler
);
776 errorOutput
->FlushOutput(handler
);
779 std::size_t length
{0};
781 std::size_t need
{length
+ 1};
783 ReadFrame(frameOffsetInFile_
, recordOffsetInFrame_
+ need
, handler
) -
784 recordOffsetInFrame_
;
787 // final record w/o \n
788 recordLength
= length
;
789 unterminatedRecord
= true;
791 HitEndOnRead(handler
);
795 } while (!SetVariableFormattedRecordLength());
798 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler
&handler
) {
799 RUNTIME_CHECK(handler
, openRecl
.has_value());
800 if (frameOffsetInFile_
< *openRecl
) {
801 handler
.SignalError(IostatBackspaceAtFirstRecord
);
803 frameOffsetInFile_
-= *openRecl
;
807 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
808 IoErrorHandler
&handler
) {
809 std::int32_t header
{0};
810 auto headerBytes
{static_cast<std::int64_t>(sizeof header
)};
811 frameOffsetInFile_
+= recordOffsetInFrame_
;
812 recordOffsetInFrame_
= 0;
813 if (frameOffsetInFile_
<= headerBytes
) {
814 handler
.SignalError(IostatBackspaceAtFirstRecord
);
817 // Error conditions here cause crashes, not file format errors, because the
818 // validity of the file structure before the current record will have been
819 // checked informatively in NextSequentialVariableUnformattedInputRecord().
821 ReadFrame(frameOffsetInFile_
- headerBytes
, headerBytes
, handler
)};
822 if (static_cast<std::int64_t>(got
) < headerBytes
) {
823 handler
.SignalError(IostatShortRead
);
826 recordLength
= ReadHeaderOrFooter(0);
827 if (frameOffsetInFile_
< *recordLength
+ 2 * headerBytes
) {
828 handler
.SignalError(IostatBadUnformattedRecord
);
831 frameOffsetInFile_
-= *recordLength
+ 2 * 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