1//===-- runtime/unit.cpp --------------------------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
9#include "unit.h"
10#include "io-error.h"
11#include "lock.h"
12#include "tools.h"
13#include "unit-map.h"
14#include "flang/Runtime/magic-numbers.h"
15#include <cstdio>
16#include <limits>
17#include <utility>
18
19namespace Fortran::runtime::io {
20
21// The per-unit data structures are created on demand so that Fortran I/O
22// should work without a Fortran main program.
23static Lock unitMapLock;
24static Lock createOpenLock;
25static UnitMap *unitMap{nullptr};
26static ExternalFileUnit *defaultInput{nullptr}; // unit 5
27static ExternalFileUnit *defaultOutput{nullptr}; // unit 6
28static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
29
30void FlushOutputOnCrash(const Terminator &terminator) {
31 if (!defaultOutput && !errorOutput) {
32 return;
33 }
34 IoErrorHandler handler{terminator};
35 handler.HasIoStat(); // prevent nested crash if flush has error
36 CriticalSection critical{unitMapLock};
37 if (defaultOutput) {
38 defaultOutput->FlushOutput(handler);
39 }
40 if (errorOutput) {
41 errorOutput->FlushOutput(handler);
42 }
43}
44
45ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
46 return GetUnitMap().LookUp(n: unit);
47}
48
49ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
50 int unit, const Terminator &terminator, bool &wasExtant) {
51 return GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant);
52}
53
54ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
55 Direction dir, std::optional<bool> isUnformatted,
56 const Terminator &terminator) {
57 // Make sure that the returned anonymous unit has been opened
58 // not just created in the unitMap.
59 CriticalSection critical{createOpenLock};
60 bool exists{false};
61 ExternalFileUnit *result{
62 GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant&: exists)};
63 if (result && !exists) {
64 IoErrorHandler handler{terminator};
65 result->OpenAnonymousUnit(
66 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
67 Action::ReadWrite, Position::Rewind, Convert::Unknown, handler);
68 result->isUnformatted = isUnformatted;
69 }
70 return result;
71}
72
73ExternalFileUnit *ExternalFileUnit::LookUp(
74 const char *path, std::size_t pathLen) {
75 return GetUnitMap().LookUp(path, pathLen);
76}
77
78ExternalFileUnit &ExternalFileUnit::CreateNew(
79 int unit, const Terminator &terminator) {
80 bool wasExtant{false};
81 ExternalFileUnit *result{
82 GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant)};
83 RUNTIME_CHECK(terminator, result && !wasExtant);
84 return *result;
85}
86
87ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
88 return GetUnitMap().LookUpForClose(unit);
89}
90
91ExternalFileUnit &ExternalFileUnit::NewUnit(
92 const Terminator &terminator, bool forChildIo) {
93 ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)};
94 unit.createdForInternalChildIo_ = forChildIo;
95 return unit;
96}
97
98bool ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
99 std::optional<Action> action, Position position, OwningPtr<char> &&newPath,
100 std::size_t newPathLength, Convert convert, IoErrorHandler &handler) {
101 if (convert == Convert::Unknown) {
102 convert = executionEnvironment.conversion;
103 }
104 swapEndianness_ = convert == Convert::Swap ||
105 (convert == Convert::LittleEndian && !isHostLittleEndian) ||
106 (convert == Convert::BigEndian && isHostLittleEndian);
107 bool impliedClose{false};
108 if (IsConnected()) {
109 bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
110 std::memcmp(s1: path(), s2: newPath.get(), n: newPathLength) == 0};
111 if (status && *status != OpenStatus::Old && isSamePath) {
112 handler.SignalError(msg: "OPEN statement for connected unit may not have "
113 "explicit STATUS= other than 'OLD'");
114 return impliedClose;
115 }
116 if (!newPath.get() || isSamePath) {
117 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
118 newPath.reset();
119 return impliedClose;
120 }
121 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
122 DoImpliedEndfile(handler);
123 FlushOutput(handler);
124 TruncateFrame(0, handler);
125 Close(CloseStatus::Keep, handler);
126 impliedClose = true;
127 }
128 if (newPath.get() && newPathLength > 0) {
129 if (const auto *already{
130 GetUnitMap().LookUp(newPath.get(), newPathLength)}) {
131 handler.SignalError(IostatOpenAlreadyConnected,
132 "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
133 unitNumber_, static_cast<int>(newPathLength), newPath.get(),
134 already->unitNumber_);
135 return impliedClose;
136 }
137 }
138 set_path(std::move(newPath), newPathLength);
139 Open(status.value_or(u: OpenStatus::Unknown), action, position, handler);
140 auto totalBytes{knownSize()};
141 if (access == Access::Direct) {
142 if (!openRecl) {
143 handler.SignalError(IostatOpenBadRecl,
144 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
145 unitNumber());
146 } else if (*openRecl <= 0) {
147 handler.SignalError(IostatOpenBadRecl,
148 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
149 unitNumber(), static_cast<std::intmax_t>(*openRecl));
150 } else if (totalBytes && (*totalBytes % *openRecl != 0)) {
151 handler.SignalError(IostatOpenBadRecl,
152 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
153 "even divisor of the file size %jd",
154 unitNumber(), static_cast<std::intmax_t>(*openRecl),
155 static_cast<std::intmax_t>(*totalBytes));
156 }
157 recordLength = openRecl;
158 }
159 endfileRecordNumber.reset();
160 currentRecordNumber = 1;
161 if (totalBytes && access == Access::Direct && openRecl.value_or(u: 0) > 0) {
162 endfileRecordNumber = 1 + (*totalBytes / *openRecl);
163 }
164 if (position == Position::Append) {
165 if (totalBytes) {
166 frameOffsetInFile_ = *totalBytes;
167 }
168 if (access != Access::Stream) {
169 if (!endfileRecordNumber) {
170 // Fake it so that we can backspace relative from the end
171 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
172 }
173 currentRecordNumber = *endfileRecordNumber;
174 }
175 }
176 return impliedClose;
177}
178
179void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status,
180 std::optional<Action> action, Position position, Convert convert,
181 IoErrorHandler &handler) {
182 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
183 std::size_t pathMaxLen{32};
184 auto path{SizedNew<char>{handler}(pathMaxLen)};
185 std::snprintf(s: path.get(), maxlen: pathMaxLen, format: "fort.%d", unitNumber_);
186 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
187 convert, handler);
188}
189
190void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
191 DoImpliedEndfile(handler);
192 FlushOutput(handler);
193 Close(status, handler);
194}
195
196void ExternalFileUnit::DestroyClosed() {
197 GetUnitMap().DestroyClosed(*this); // destroys *this
198}
199
200Iostat ExternalFileUnit::SetDirection(Direction direction) {
201 if (direction == Direction::Input) {
202 if (mayRead()) {
203 direction_ = Direction::Input;
204 return IostatOk;
205 } else {
206 return IostatReadFromWriteOnly;
207 }
208 } else {
209 if (mayWrite()) {
210 direction_ = Direction::Output;
211 return IostatOk;
212 } else {
213 return IostatWriteToReadOnly;
214 }
215 }
216}
217
218UnitMap &ExternalFileUnit::CreateUnitMap() {
219 Terminator terminator{__FILE__, __LINE__};
220 IoErrorHandler handler{terminator};
221 UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()};
222
223 bool wasExtant{false};
224 ExternalFileUnit &out{*newUnitMap.LookUpOrCreate(
225 FORTRAN_DEFAULT_OUTPUT_UNIT, terminator, wasExtant)};
226 RUNTIME_CHECK(terminator, !wasExtant);
227 out.Predefine(1);
228 handler.SignalError(out.SetDirection(Direction::Output));
229 out.isUnformatted = false;
230 defaultOutput = &out;
231
232 ExternalFileUnit &in{*newUnitMap.LookUpOrCreate(
233 FORTRAN_DEFAULT_INPUT_UNIT, terminator, wasExtant)};
234 RUNTIME_CHECK(terminator, !wasExtant);
235 in.Predefine(0);
236 handler.SignalError(in.SetDirection(Direction::Input));
237 in.isUnformatted = false;
238 defaultInput = &in;
239
240 ExternalFileUnit &error{
241 *newUnitMap.LookUpOrCreate(FORTRAN_ERROR_UNIT, terminator, wasExtant)};
242 RUNTIME_CHECK(terminator, !wasExtant);
243 error.Predefine(2);
244 handler.SignalError(error.SetDirection(Direction::Output));
245 error.isUnformatted = false;
246 errorOutput = &error;
247
248 return newUnitMap;
249}
250
251// A back-up atexit() handler for programs that don't terminate with a main
252// program END or a STOP statement or other Fortran-initiated program shutdown,
253// such as programs with a C main() that terminate normally. It flushes all
254// external I/O units. It is registered once the first time that any external
255// I/O is attempted.
256static void CloseAllExternalUnits() {
257 IoErrorHandler handler{"Fortran program termination"};
258 ExternalFileUnit::CloseAll(handler);
259}
260
261UnitMap &ExternalFileUnit::GetUnitMap() {
262 if (unitMap) {
263 return *unitMap;
264 }
265 {
266 CriticalSection critical{unitMapLock};
267 if (unitMap) {
268 return *unitMap;
269 }
270 unitMap = &CreateUnitMap();
271 }
272 std::atexit(func: CloseAllExternalUnits);
273 return *unitMap;
274}
275
276void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
277 CriticalSection critical{unitMapLock};
278 if (unitMap) {
279 unitMap->CloseAll(handler);
280 FreeMemoryAndNullify(unitMap);
281 }
282 defaultOutput = nullptr;
283 defaultInput = nullptr;
284 errorOutput = nullptr;
285}
286
287void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
288 CriticalSection critical{unitMapLock};
289 if (unitMap) {
290 unitMap->FlushAll(handler);
291 }
292}
293
294static inline void SwapEndianness(
295 char *data, std::size_t bytes, std::size_t elementBytes) {
296 if (elementBytes > 1) {
297 auto half{elementBytes >> 1};
298 for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
299 for (std::size_t k{0}; k < half; ++k) {
300 std::swap(a&: data[j + k], b&: data[j + elementBytes - 1 - k]);
301 }
302 }
303 }
304}
305
306bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
307 std::size_t elementBytes, IoErrorHandler &handler) {
308 auto furthestAfter{std::max(a: furthestPositionInRecord,
309 b: positionInRecord + static_cast<std::int64_t>(bytes))};
310 if (openRecl) {
311 // Check for fixed-length record overrun, but allow for
312 // sequential record termination.
313 int extra{0};
314 int header{0};
315 if (access == Access::Sequential) {
316 if (isUnformatted.value_or(u: false)) {
317 // record header + footer
318 header = static_cast<int>(sizeof(std::uint32_t));
319 extra = 2 * header;
320 } else {
321#ifdef _WIN32
322 if (!isWindowsTextFile()) {
323 ++extra; // carriage return (CR)
324 }
325#endif
326 ++extra; // newline (LF)
327 }
328 }
329 if (furthestAfter > extra + *openRecl) {
330 handler.SignalError(IostatRecordWriteOverrun,
331 "Attempt to write %zd bytes to position %jd in a fixed-size record "
332 "of %jd bytes",
333 bytes, static_cast<std::intmax_t>(positionInRecord - header),
334 static_cast<std::intmax_t>(*openRecl));
335 return false;
336 }
337 }
338 if (recordLength) {
339 // It is possible for recordLength to have a value now for a
340 // variable-length output record if the previous operation
341 // was a BACKSPACE or non advancing input statement.
342 recordLength.reset();
343 beganReadingRecord_ = false;
344 }
345 if (IsAfterEndfile()) {
346 handler.SignalError(IostatWriteAfterEndfile);
347 return false;
348 }
349 CheckDirectAccess(handler);
350 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
351 if (positionInRecord > furthestPositionInRecord) {
352 std::memset(s: Frame() + recordOffsetInFrame_ + furthestPositionInRecord, c: ' ',
353 n: positionInRecord - furthestPositionInRecord);
354 }
355 char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
356 std::memcpy(dest: to, src: data, n: bytes);
357 if (swapEndianness_) {
358 SwapEndianness(data: to, bytes, elementBytes);
359 }
360 positionInRecord += bytes;
361 furthestPositionInRecord = furthestAfter;
362 anyWriteSinceLastPositioning_ = true;
363 return true;
364}
365
366bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
367 std::size_t elementBytes, IoErrorHandler &handler) {
368 RUNTIME_CHECK(handler, direction_ == Direction::Input);
369 auto furthestAfter{std::max(a: furthestPositionInRecord,
370 b: positionInRecord + static_cast<std::int64_t>(bytes))};
371 if (furthestAfter > recordLength.value_or(u&: furthestAfter)) {
372 handler.SignalError(IostatRecordReadOverrun,
373 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
374 bytes, static_cast<std::intmax_t>(positionInRecord),
375 static_cast<std::intmax_t>(*recordLength));
376 return false;
377 }
378 auto need{recordOffsetInFrame_ + furthestAfter};
379 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
380 if (got >= need) {
381 std::memcpy(dest: data, src: Frame() + recordOffsetInFrame_ + positionInRecord, n: bytes);
382 if (swapEndianness_) {
383 SwapEndianness(data, bytes, elementBytes);
384 }
385 positionInRecord += bytes;
386 furthestPositionInRecord = furthestAfter;
387 return true;
388 } else {
389 HitEndOnRead(handler);
390 return false;
391 }
392}
393
394std::size_t ExternalFileUnit::GetNextInputBytes(
395 const char *&p, IoErrorHandler &handler) {
396 RUNTIME_CHECK(handler, direction_ == Direction::Input);
397 std::size_t length{1};
398 if (auto recl{EffectiveRecordLength()}) {
399 if (positionInRecord < *recl) {
400 length = *recl - positionInRecord;
401 } else {
402 p = nullptr;
403 return 0;
404 }
405 }
406 p = FrameNextInput(handler, length);
407 return p ? length : 0;
408}
409
410const char *ExternalFileUnit::FrameNextInput(
411 IoErrorHandler &handler, std::size_t bytes) {
412 RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);
413 if (static_cast<std::int64_t>(positionInRecord + bytes) <=
414 recordLength.value_or(u: positionInRecord + bytes)) {
415 auto at{recordOffsetInFrame_ + positionInRecord};
416 auto need{static_cast<std::size_t>(at + bytes)};
417 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
418 SetVariableFormattedRecordLength();
419 if (got >= need) {
420 return Frame() + at;
421 }
422 HitEndOnRead(handler);
423 }
424 return nullptr;
425}
426
427bool ExternalFileUnit::SetVariableFormattedRecordLength() {
428 if (recordLength || access == Access::Direct) {
429 return true;
430 } else if (FrameLength() > recordOffsetInFrame_) {
431 const char *record{Frame() + recordOffsetInFrame_};
432 std::size_t bytes{FrameLength() - recordOffsetInFrame_};
433 if (const char *nl{FindCharacter(record, '\n', bytes)}) {
434 recordLength = nl - record;
435 if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
436 --*recordLength;
437 }
438 return true;
439 }
440 }
441 return false;
442}
443
444bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
445 RUNTIME_CHECK(handler, direction_ == Direction::Input);
446 if (!beganReadingRecord_) {
447 beganReadingRecord_ = true;
448 // Don't use IsAtEOF() to check for an EOF condition here, just detect
449 // it from a failed or short read from the file. IsAtEOF() could be
450 // wrong for formatted input if actual newline characters had been
451 // written in-band by previous WRITEs before a REWIND. In fact,
452 // now that we know that the unit is being used for input (again),
453 // it's best to reset endfileRecordNumber and ensure IsAtEOF() will
454 // now be true on return only if it gets set by HitEndOnRead().
455 endfileRecordNumber.reset();
456 if (access == Access::Direct) {
457 CheckDirectAccess(handler);
458 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
459 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
460 if (got >= need) {
461 recordLength = openRecl;
462 } else {
463 recordLength.reset();
464 HitEndOnRead(handler);
465 }
466 } else {
467 if (anyWriteSinceLastPositioning_ && access == Access::Sequential) {
468 // Most Fortran implementations allow a READ after a WRITE;
469 // the read then just hits an EOF.
470 DoEndfile(handler);
471 }
472 recordLength.reset();
473 RUNTIME_CHECK(handler, isUnformatted.has_value());
474 if (*isUnformatted) {
475 if (access == Access::Sequential) {
476 BeginSequentialVariableUnformattedInputRecord(handler);
477 }
478 } else { // formatted sequential or stream
479 BeginVariableFormattedInputRecord(handler);
480 }
481 }
482 }
483 RUNTIME_CHECK(handler,
484 recordLength.has_value() || !IsRecordFile() || handler.InError());
485 return !handler.InError();
486}
487
488void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
489 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
490 beganReadingRecord_ = false;
491 if (handler.GetIoStat() == IostatEnd ||
492 (IsRecordFile() && !recordLength.has_value())) {
493 // Avoid bogus crashes in END/ERR circumstances; but
494 // still increment the current record number so that
495 // an attempted read of an endfile record, followed by
496 // a BACKSPACE, will still be at EOF.
497 ++currentRecordNumber;
498 } else if (IsRecordFile()) {
499 recordOffsetInFrame_ += *recordLength;
500 if (access != Access::Direct) {
501 RUNTIME_CHECK(handler, isUnformatted.has_value());
502 recordLength.reset();
503 if (isUnformatted.value_or(u: false)) {
504 // Retain footer in frame for more efficient BACKSPACE
505 frameOffsetInFile_ += recordOffsetInFrame_;
506 recordOffsetInFrame_ = sizeof(std::uint32_t);
507 } else { // formatted
508 if (FrameLength() > recordOffsetInFrame_ &&
509 Frame()[recordOffsetInFrame_] == '\r') {
510 ++recordOffsetInFrame_;
511 }
512 if (FrameLength() > recordOffsetInFrame_ &&
513 Frame()[recordOffsetInFrame_] == '\n') {
514 ++recordOffsetInFrame_;
515 }
516 if (!pinnedFrame || mayPosition()) {
517 frameOffsetInFile_ += recordOffsetInFrame_;
518 recordOffsetInFrame_ = 0;
519 }
520 }
521 }
522 ++currentRecordNumber;
523 } else { // unformatted stream
524 furthestPositionInRecord =
525 std::max(a: furthestPositionInRecord, b: positionInRecord);
526 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
527 }
528 BeginRecord();
529}
530
531bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
532 if (direction_ == Direction::Input) {
533 FinishReadingRecord(handler);
534 return BeginReadingRecord(handler);
535 } else { // Direction::Output
536 bool ok{true};
537 RUNTIME_CHECK(handler, isUnformatted.has_value());
538 positionInRecord = furthestPositionInRecord;
539 if (access == Access::Direct) {
540 if (furthestPositionInRecord <
541 openRecl.value_or(u&: furthestPositionInRecord)) {
542 // Pad remainder of fixed length record
543 WriteFrame(
544 frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
545 std::memset(s: Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
546 c: isUnformatted.value_or(u: false) ? 0 : ' ',
547 n: *openRecl - furthestPositionInRecord);
548 furthestPositionInRecord = *openRecl;
549 }
550 } else if (*isUnformatted) {
551 if (access == Access::Sequential) {
552 // Append the length of a sequential unformatted variable-length record
553 // as its footer, then overwrite the reserved first four bytes of the
554 // record with its length as its header. These four bytes were skipped
555 // over in BeginUnformattedIO<Output>().
556 // TODO: Break very large records up into subrecords with negative
557 // headers &/or footers
558 std::uint32_t length;
559 length = furthestPositionInRecord - sizeof length;
560 ok = ok &&
561 Emit(data: reinterpret_cast<const char *>(&length), bytes: sizeof length,
562 elementBytes: sizeof length, handler);
563 positionInRecord = 0;
564 ok = ok &&
565 Emit(data: reinterpret_cast<const char *>(&length), bytes: sizeof length,
566 elementBytes: sizeof length, handler);
567 } else {
568 // Unformatted stream: nothing to do
569 }
570 } else if (handler.GetIoStat() != IostatOk &&
571 furthestPositionInRecord == 0) {
572 // Error in formatted variable length record, and no output yet; do
573 // nothing, like most other Fortran compilers do.
574 return true;
575 } else {
576 // Terminate formatted variable length record
577 const char *lineEnding{"\n"};
578 std::size_t lineEndingBytes{1};
579#ifdef _WIN32
580 if (!isWindowsTextFile()) {
581 lineEnding = "\r\n";
582 lineEndingBytes = 2;
583 }
584#endif
585 ok = ok && Emit(data: lineEnding, bytes: lineEndingBytes, elementBytes: 1, handler);
586 }
587 leftTabLimit.reset();
588 if (IsAfterEndfile()) {
589 return false;
590 }
591 CommitWrites();
592 ++currentRecordNumber;
593 if (access != Access::Direct) {
594 impliedEndfile_ = IsRecordFile();
595 if (IsAtEOF()) {
596 endfileRecordNumber.reset();
597 }
598 }
599 return ok;
600 }
601}
602
603void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
604 if (access == Access::Direct || !IsRecordFile()) {
605 handler.SignalError(IostatBackspaceNonSequential,
606 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
607 unitNumber());
608 } else {
609 if (IsAfterEndfile()) {
610 // BACKSPACE after explicit ENDFILE
611 currentRecordNumber = *endfileRecordNumber;
612 } else if (leftTabLimit && direction_ == Direction::Input) {
613 // BACKSPACE after non-advancing input
614 leftTabLimit.reset();
615 } else {
616 DoImpliedEndfile(handler);
617 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
618 --currentRecordNumber;
619 if (openRecl && access == Access::Direct) {
620 BackspaceFixedRecord(handler);
621 } else {
622 RUNTIME_CHECK(handler, isUnformatted.has_value());
623 if (isUnformatted.value_or(u: false)) {
624 BackspaceVariableUnformattedRecord(handler);
625 } else {
626 BackspaceVariableFormattedRecord(handler);
627 }
628 }
629 }
630 }
631 BeginRecord();
632 anyWriteSinceLastPositioning_ = false;
633 }
634}
635
636void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {
637 if (!mayPosition()) {
638 auto frameAt{FrameAt()};
639 if (frameOffsetInFile_ >= frameAt &&
640 frameOffsetInFile_ <
641 static_cast<std::int64_t>(frameAt + FrameLength())) {
642 // A Flush() that's about to happen to a non-positionable file
643 // needs to advance frameOffsetInFile_ to prevent attempts at
644 // impossible seeks
645 CommitWrites();
646 leftTabLimit.reset();
647 }
648 }
649 Flush(handler);
650}
651
652void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
653 if (isTerminal()) {
654 FlushOutput(handler);
655 }
656}
657
658void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
659 if (access == Access::Direct) {
660 handler.SignalError(IostatEndfileDirect,
661 "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
662 } else if (!mayWrite()) {
663 handler.SignalError(IostatEndfileUnwritable,
664 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
665 } else if (IsAfterEndfile()) {
666 // ENDFILE after ENDFILE
667 } else {
668 DoEndfile(handler);
669 if (IsRecordFile() && access != Access::Direct) {
670 // Explicit ENDFILE leaves position *after* the endfile record
671 RUNTIME_CHECK(handler, endfileRecordNumber.has_value());
672 currentRecordNumber = *endfileRecordNumber + 1;
673 }
674 }
675}
676
677void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
678 if (access == Access::Direct) {
679 handler.SignalError(IostatRewindNonSequential,
680 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
681 } else {
682 SetPosition(0, handler);
683 currentRecordNumber = 1;
684 leftTabLimit.reset();
685 anyWriteSinceLastPositioning_ = false;
686 }
687}
688
689void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) {
690 DoImpliedEndfile(handler);
691 frameOffsetInFile_ = pos;
692 recordOffsetInFrame_ = 0;
693 if (access == Access::Direct) {
694 directAccessRecWasSet_ = true;
695 }
696 BeginRecord();
697}
698
699bool ExternalFileUnit::SetStreamPos(
700 std::int64_t oneBasedPos, IoErrorHandler &handler) {
701 if (access != Access::Stream) {
702 handler.SignalError(msg: "POS= may not appear unless ACCESS='STREAM'");
703 return false;
704 }
705 if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)
706 handler.SignalError(
707 msg: "POS=%zd is invalid", xs: static_cast<std::intmax_t>(oneBasedPos));
708 return false;
709 }
710 SetPosition(pos: oneBasedPos - 1, handler);
711 // We no longer know which record we're in. Set currentRecordNumber to
712 // a large value from whence we can both advance and backspace.
713 currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;
714 endfileRecordNumber.reset();
715 return true;
716}
717
718bool ExternalFileUnit::SetDirectRec(
719 std::int64_t oneBasedRec, IoErrorHandler &handler) {
720 if (access != Access::Direct) {
721 handler.SignalError(msg: "REC= may not appear unless ACCESS='DIRECT'");
722 return false;
723 }
724 if (!openRecl) {
725 handler.SignalError(msg: "RECL= was not specified");
726 return false;
727 }
728 if (oneBasedRec < 1) {
729 handler.SignalError(
730 msg: "REC=%zd is invalid", xs: static_cast<std::intmax_t>(oneBasedRec));
731 return false;
732 }
733 currentRecordNumber = oneBasedRec;
734 SetPosition(pos: (oneBasedRec - 1) * *openRecl, handler);
735 return true;
736}
737
738void ExternalFileUnit::EndIoStatement() {
739 io_.reset();
740 u_.emplace<std::monostate>();
741 lock_.Drop();
742}
743
744void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
745 IoErrorHandler &handler) {
746 RUNTIME_CHECK(handler, access == Access::Sequential);
747 std::int32_t header{0}, footer{0};
748 std::size_t need{recordOffsetInFrame_ + sizeof header};
749 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
750 // Try to emit informative errors to help debug corrupted files.
751 const char *error{nullptr};
752 if (got < need) {
753 if (got == recordOffsetInFrame_) {
754 HitEndOnRead(handler);
755 } else {
756 error = "Unformatted variable-length sequential file input failed at "
757 "record #%jd (file offset %jd): truncated record header";
758 }
759 } else {
760 header = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_);
761 recordLength = sizeof header + header; // does not include footer
762 need = recordOffsetInFrame_ + *recordLength + sizeof footer;
763 got = ReadFrame(frameOffsetInFile_, need, handler);
764 if (got < need) {
765 error = "Unformatted variable-length sequential file input failed at "
766 "record #%jd (file offset %jd): hit EOF reading record with "
767 "length %jd bytes";
768 } else {
769 footer = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_ + *recordLength);
770 if (footer != header) {
771 error = "Unformatted variable-length sequential file input failed at "
772 "record #%jd (file offset %jd): record header has length %jd "
773 "that does not match record footer (%jd)";
774 }
775 }
776 }
777 if (error) {
778 handler.SignalError(msg: error, xs: static_cast<std::intmax_t>(currentRecordNumber),
779 xs: static_cast<std::intmax_t>(frameOffsetInFile_),
780 xs: static_cast<std::intmax_t>(header), xs: static_cast<std::intmax_t>(footer));
781 // TODO: error recovery
782 }
783 positionInRecord = sizeof header;
784}
785
786void ExternalFileUnit::BeginVariableFormattedInputRecord(
787 IoErrorHandler &handler) {
788 if (this == defaultInput) {
789 if (defaultOutput) {
790 defaultOutput->FlushOutput(handler);
791 }
792 if (errorOutput) {
793 errorOutput->FlushOutput(handler);
794 }
795 }
796 std::size_t length{0};
797 do {
798 std::size_t need{length + 1};
799 length =
800 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -
801 recordOffsetInFrame_;
802 if (length < need) {
803 if (length > 0) {
804 // final record w/o \n
805 recordLength = length;
806 unterminatedRecord = true;
807 } else {
808 HitEndOnRead(handler);
809 }
810 break;
811 }
812 } while (!SetVariableFormattedRecordLength());
813}
814
815void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
816 RUNTIME_CHECK(handler, openRecl.has_value());
817 if (frameOffsetInFile_ < *openRecl) {
818 handler.SignalError(IostatBackspaceAtFirstRecord);
819 } else {
820 frameOffsetInFile_ -= *openRecl;
821 }
822}
823
824void ExternalFileUnit::BackspaceVariableUnformattedRecord(
825 IoErrorHandler &handler) {
826 std::int32_t header{0};
827 auto headerBytes{static_cast<std::int64_t>(sizeof header)};
828 frameOffsetInFile_ += recordOffsetInFrame_;
829 recordOffsetInFrame_ = 0;
830 if (frameOffsetInFile_ <= headerBytes) {
831 handler.SignalError(IostatBackspaceAtFirstRecord);
832 return;
833 }
834 // Error conditions here cause crashes, not file format errors, because the
835 // validity of the file structure before the current record will have been
836 // checked informatively in NextSequentialVariableUnformattedInputRecord().
837 std::size_t got{
838 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
839 if (static_cast<std::int64_t>(got) < headerBytes) {
840 handler.SignalError(IostatShortRead);
841 return;
842 }
843 recordLength = ReadHeaderOrFooter(frameOffset: 0);
844 if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
845 handler.SignalError(IostatBadUnformattedRecord);
846 return;
847 }
848 frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
849 auto need{static_cast<std::size_t>(
850 recordOffsetInFrame_ + sizeof header + *recordLength)};
851 got = ReadFrame(frameOffsetInFile_, need, handler);
852 if (got < need) {
853 handler.SignalError(IostatShortRead);
854 return;
855 }
856 header = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_);
857 if (header != *recordLength) {
858 handler.SignalError(IostatBadUnformattedRecord);
859 return;
860 }
861}
862
863// There's no portable memrchr(), unfortunately, and strrchr() would
864// fail on a record with a NUL, so we have to do it the hard way.
865static const char *FindLastNewline(const char *str, std::size_t length) {
866 for (const char *p{str + length}; p >= str; p--) {
867 if (*p == '\n') {
868 return p;
869 }
870 }
871 return nullptr;
872}
873
874void ExternalFileUnit::BackspaceVariableFormattedRecord(
875 IoErrorHandler &handler) {
876 // File offset of previous record's newline
877 auto prevNL{
878 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
879 if (prevNL < 0) {
880 handler.SignalError(IostatBackspaceAtFirstRecord);
881 return;
882 }
883 while (true) {
884 if (frameOffsetInFile_ < prevNL) {
885 if (const char *p{
886 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
887 recordOffsetInFrame_ = p - Frame() + 1;
888 recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
889 break;
890 }
891 }
892 if (frameOffsetInFile_ == 0) {
893 recordOffsetInFrame_ = 0;
894 recordLength = prevNL;
895 break;
896 }
897 frameOffsetInFile_ -= std::min<std::int64_t>(a: frameOffsetInFile_, b: 1024);
898 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
899 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
900 if (got < need) {
901 handler.SignalError(IostatShortRead);
902 return;
903 }
904 }
905 if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {
906 handler.SignalError(IostatMissingTerminator);
907 return;
908 }
909 if (*recordLength > 0 &&
910 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
911 --*recordLength;
912 }
913}
914
915void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
916 if (access != Access::Direct) {
917 if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) {
918 // Flush a partial record after non-advancing output
919 impliedEndfile_ = true;
920 }
921 if (impliedEndfile_ && mayPosition()) {
922 DoEndfile(handler);
923 }
924 }
925 impliedEndfile_ = false;
926}
927
928void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
929 if (IsRecordFile() && access != Access::Direct) {
930 furthestPositionInRecord =
931 std::max(a: positionInRecord, b: furthestPositionInRecord);
932 if (leftTabLimit) { // last I/O was non-advancing
933 if (access == Access::Sequential && direction_ == Direction::Output) {
934 AdvanceRecord(handler);
935 } else { // Access::Stream or input
936 leftTabLimit.reset();
937 ++currentRecordNumber;
938 }
939 }
940 endfileRecordNumber = currentRecordNumber;
941 }
942 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
943 recordOffsetInFrame_ = 0;
944 FlushOutput(handler);
945 Truncate(frameOffsetInFile_, handler);
946 TruncateFrame(frameOffsetInFile_, handler);
947 BeginRecord();
948 impliedEndfile_ = false;
949 anyWriteSinceLastPositioning_ = false;
950}
951
952void ExternalFileUnit::CommitWrites() {
953 frameOffsetInFile_ +=
954 recordOffsetInFrame_ + recordLength.value_or(u&: furthestPositionInRecord);
955 recordOffsetInFrame_ = 0;
956 BeginRecord();
957}
958
959bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) {
960 if (access == Access::Direct) {
961 RUNTIME_CHECK(handler, openRecl);
962 if (!directAccessRecWasSet_) {
963 handler.SignalError(
964 msg: "No REC= was specified for a data transfer with ACCESS='DIRECT'");
965 return false;
966 }
967 }
968 return true;
969}
970
971void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) {
972 handler.SignalEnd();
973 if (IsRecordFile() && access != Access::Direct) {
974 endfileRecordNumber = currentRecordNumber;
975 }
976}
977
978ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) {
979 OwningPtr<ChildIo> current{std::move(child_)};
980 Terminator &terminator{parent.GetIoErrorHandler()};
981 OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))};
982 child_.reset(next.release());
983 return *child_;
984}
985
986void ExternalFileUnit::PopChildIo(ChildIo &child) {
987 if (child_.get() != &child) {
988 child.parent().GetIoErrorHandler().Crash(
989 "ChildIo being popped is not top of stack");
990 }
991 child_.reset(child.AcquirePrevious().release()); // deletes top child
992}
993
994int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
995 if (!mayAsynchronous()) {
996 handler.SignalError(IostatBadAsynchronous);
997 return -1;
998 } else if (auto least{asyncIdAvailable_.LeastElement()}) {
999 asyncIdAvailable_.reset(*least);
1000 return static_cast<int>(*least);
1001 } else {
1002 handler.SignalError(IostatTooManyAsyncOps);
1003 return -1;
1004 }
1005}
1006
1007bool ExternalFileUnit::Wait(int id) {
1008 if (static_cast<std::size_t>(id) >= asyncIdAvailable_.size() ||
1009 asyncIdAvailable_.test(id)) {
1010 return false;
1011 } else {
1012 if (id == 0) { // means "all IDs"
1013 asyncIdAvailable_.set();
1014 asyncIdAvailable_.reset(0);
1015 } else {
1016 asyncIdAvailable_.set(id);
1017 }
1018 return true;
1019 }
1020}
1021
1022std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
1023 std::int32_t word;
1024 char *wordPtr{reinterpret_cast<char *>(&word)};
1025 std::memcpy(dest: wordPtr, src: Frame() + frameOffset, n: sizeof word);
1026 if (swapEndianness_) {
1027 SwapEndianness(data: wordPtr, bytes: sizeof word, elementBytes: sizeof word);
1028 }
1029 return word;
1030}
1031
1032void ChildIo::EndIoStatement() {
1033 io_.reset();
1034 u_.emplace<std::monostate>();
1035}
1036
1037Iostat ChildIo::CheckFormattingAndDirection(
1038 bool unformatted, Direction direction) {
1039 bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
1040 bool parentIsFormatted{parentIsInput
1041 ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
1042 nullptr
1043 : parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
1044 nullptr};
1045 bool parentIsUnformatted{!parentIsFormatted};
1046 if (unformatted != parentIsUnformatted) {
1047 return unformatted ? IostatUnformattedChildOnFormattedParent
1048 : IostatFormattedChildOnUnformattedParent;
1049 } else if (parentIsInput != (direction == Direction::Input)) {
1050 return parentIsInput ? IostatChildOutputToInputParent
1051 : IostatChildInputFromOutputParent;
1052 } else {
1053 return IostatOk;
1054 }
1055}
1056
1057} // namespace Fortran::runtime::io
1058

source code of flang/runtime/unit.cpp