xref: /llvm-project/flang/runtime/unit.cpp (revision c2a95ad25c65acede2492ac83039150f9522c3ae)
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 // Implementation of ExternalFileUnit common for both
10 // RT_USE_PSEUDO_FILE_UNIT=0 and RT_USE_PSEUDO_FILE_UNIT=1.
11 //
12 //===----------------------------------------------------------------------===//
13 #include "unit.h"
14 #include "io-error.h"
15 #include "lock.h"
16 #include "tools.h"
17 #include <limits>
18 #include <utility>
19 
20 namespace Fortran::runtime::io {
21 
22 #ifndef FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
23 RT_OFFLOAD_VAR_GROUP_BEGIN
24 RT_VAR_ATTRS ExternalFileUnit *defaultInput{nullptr}; // unit 5
25 RT_VAR_ATTRS ExternalFileUnit *defaultOutput{nullptr}; // unit 6
26 RT_VAR_ATTRS ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
27 RT_OFFLOAD_VAR_GROUP_END
28 #endif // FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS
29 
30 RT_OFFLOAD_API_GROUP_BEGIN
31 
32 static inline RT_API_ATTRS void SwapEndianness(
33     char *data, std::size_t bytes, std::size_t elementBytes) {
34   if (elementBytes > 1) {
35     auto half{elementBytes >> 1};
36     for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
37       for (std::size_t k{0}; k < half; ++k) {
38         RT_DIAG_PUSH
39         RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN
40         std::swap(data[j + k], data[j + elementBytes - 1 - k]);
41         RT_DIAG_POP
42       }
43     }
44   }
45 }
46 
47 bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
48     std::size_t elementBytes, IoErrorHandler &handler) {
49   auto furthestAfter{std::max(furthestPositionInRecord,
50       positionInRecord + static_cast<std::int64_t>(bytes))};
51   if (openRecl) {
52     // Check for fixed-length record overrun, but allow for
53     // sequential record termination.
54     int extra{0};
55     int header{0};
56     if (access == Access::Sequential) {
57       if (isUnformatted.value_or(false)) {
58         // record header + footer
59         header = static_cast<int>(sizeof(std::uint32_t));
60         extra = 2 * header;
61       } else {
62 #ifdef _WIN32
63         if (!isWindowsTextFile()) {
64           ++extra; // carriage return (CR)
65         }
66 #endif
67         ++extra; // newline (LF)
68       }
69     }
70     if (furthestAfter > extra + *openRecl) {
71       handler.SignalError(IostatRecordWriteOverrun,
72           "Attempt to write %zd bytes to position %jd in a fixed-size record "
73           "of %jd bytes",
74           bytes, static_cast<std::intmax_t>(positionInRecord - header),
75           static_cast<std::intmax_t>(*openRecl));
76       return false;
77     }
78   }
79   if (recordLength) {
80     // It is possible for recordLength to have a value now for a
81     // variable-length output record if the previous operation
82     // was a BACKSPACE or non advancing input statement.
83     recordLength.reset();
84     beganReadingRecord_ = false;
85   }
86   if (IsAfterEndfile()) {
87     handler.SignalError(IostatWriteAfterEndfile);
88     return false;
89   }
90   CheckDirectAccess(handler);
91   WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
92   if (positionInRecord > furthestPositionInRecord) {
93     std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
94         positionInRecord - furthestPositionInRecord);
95   }
96   char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
97   std::memcpy(to, data, bytes);
98   if (swapEndianness_) {
99     SwapEndianness(to, bytes, elementBytes);
100   }
101   positionInRecord += bytes;
102   furthestPositionInRecord = furthestAfter;
103   anyWriteSinceLastPositioning_ = true;
104   return true;
105 }
106 
107 bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
108     std::size_t elementBytes, IoErrorHandler &handler) {
109   RUNTIME_CHECK(handler, direction_ == Direction::Input);
110   auto furthestAfter{std::max(furthestPositionInRecord,
111       positionInRecord + static_cast<std::int64_t>(bytes))};
112   if (furthestAfter > recordLength.value_or(furthestAfter)) {
113     handler.SignalError(IostatRecordReadOverrun,
114         "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
115         bytes, static_cast<std::intmax_t>(positionInRecord),
116         static_cast<std::intmax_t>(*recordLength));
117     return false;
118   }
119   auto need{recordOffsetInFrame_ + furthestAfter};
120   auto got{ReadFrame(frameOffsetInFile_, need, handler)};
121   if (got >= need) {
122     std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
123     if (swapEndianness_) {
124       SwapEndianness(data, bytes, elementBytes);
125     }
126     positionInRecord += bytes;
127     furthestPositionInRecord = furthestAfter;
128     return true;
129   } else {
130     HitEndOnRead(handler);
131     return false;
132   }
133 }
134 
135 std::size_t ExternalFileUnit::GetNextInputBytes(
136     const char *&p, IoErrorHandler &handler) {
137   RUNTIME_CHECK(handler, direction_ == Direction::Input);
138   std::size_t length{1};
139   if (auto recl{EffectiveRecordLength()}) {
140     if (positionInRecord < *recl) {
141       length = *recl - positionInRecord;
142     } else {
143       p = nullptr;
144       return 0;
145     }
146   }
147   p = FrameNextInput(handler, length);
148   return p ? length : 0;
149 }
150 
151 std::size_t ExternalFileUnit::ViewBytesInRecord(
152     const char *&p, bool forward) const {
153   p = nullptr;
154   auto recl{recordLength.value_or(positionInRecord)};
155   if (forward) {
156     if (positionInRecord < recl) {
157       p = Frame() + recordOffsetInFrame_ + positionInRecord;
158       return recl - positionInRecord;
159     }
160   } else {
161     if (positionInRecord <= recl) {
162       p = Frame() + recordOffsetInFrame_ + positionInRecord;
163     }
164     return positionInRecord - leftTabLimit.value_or(0);
165   }
166   return 0;
167 }
168 
169 const char *ExternalFileUnit::FrameNextInput(
170     IoErrorHandler &handler, std::size_t bytes) {
171   RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);
172   if (static_cast<std::int64_t>(positionInRecord + bytes) <=
173       recordLength.value_or(positionInRecord + bytes)) {
174     auto at{recordOffsetInFrame_ + positionInRecord};
175     auto need{static_cast<std::size_t>(at + bytes)};
176     auto got{ReadFrame(frameOffsetInFile_, need, handler)};
177     SetVariableFormattedRecordLength();
178     if (got >= need) {
179       return Frame() + at;
180     }
181     HitEndOnRead(handler);
182   }
183   return nullptr;
184 }
185 
186 bool ExternalFileUnit::SetVariableFormattedRecordLength() {
187   if (recordLength || access == Access::Direct) {
188     return true;
189   } else if (FrameLength() > recordOffsetInFrame_) {
190     const char *record{Frame() + recordOffsetInFrame_};
191     std::size_t bytes{FrameLength() - recordOffsetInFrame_};
192     if (const char *nl{FindCharacter(record, '\n', bytes)}) {
193       recordLength = nl - record;
194       if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
195         --*recordLength;
196       }
197       return true;
198     }
199   }
200   return false;
201 }
202 
203 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
204   RUNTIME_CHECK(handler, direction_ == Direction::Input);
205   if (!beganReadingRecord_) {
206     beganReadingRecord_ = true;
207     // Don't use IsAtEOF() to check for an EOF condition here, just detect
208     // it from a failed or short read from the file.  IsAtEOF() could be
209     // wrong for formatted input if actual newline characters had been
210     // written in-band by previous WRITEs before a REWIND.  In fact,
211     // now that we know that the unit is being used for input (again),
212     // it's best to reset endfileRecordNumber and ensure IsAtEOF() will
213     // now be true on return only if it gets set by HitEndOnRead().
214     endfileRecordNumber.reset();
215     if (access == Access::Direct) {
216       CheckDirectAccess(handler);
217       auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};
218       auto got{ReadFrame(frameOffsetInFile_, need, handler)};
219       if (got >= need) {
220         recordLength = openRecl;
221       } else {
222         recordLength.reset();
223         HitEndOnRead(handler);
224       }
225     } else {
226       if (anyWriteSinceLastPositioning_ && access == Access::Sequential) {
227         // Most Fortran implementations allow a READ after a WRITE;
228         // the read then just hits an EOF.
229         DoEndfile<false, Direction::Input>(handler);
230       }
231       recordLength.reset();
232       RUNTIME_CHECK(handler, isUnformatted.has_value());
233       if (*isUnformatted) {
234         if (access == Access::Sequential) {
235           BeginSequentialVariableUnformattedInputRecord(handler);
236         }
237       } else { // formatted sequential or stream
238         BeginVariableFormattedInputRecord(handler);
239       }
240     }
241   }
242   RUNTIME_CHECK(handler,
243       recordLength.has_value() || !IsRecordFile() || handler.InError());
244   return !handler.InError();
245 }
246 
247 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
248   RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
249   beganReadingRecord_ = false;
250   if (handler.GetIoStat() == IostatEnd ||
251       (IsRecordFile() && !recordLength.has_value())) {
252     // Avoid bogus crashes in END/ERR circumstances; but
253     // still increment the current record number so that
254     // an attempted read of an endfile record, followed by
255     // a BACKSPACE, will still be at EOF.
256     ++currentRecordNumber;
257   } else if (IsRecordFile()) {
258     recordOffsetInFrame_ += *recordLength;
259     if (access != Access::Direct) {
260       RUNTIME_CHECK(handler, isUnformatted.has_value());
261       recordLength.reset();
262       if (isUnformatted.value_or(false)) {
263         // Retain footer in frame for more efficient BACKSPACE
264         frameOffsetInFile_ += recordOffsetInFrame_;
265         recordOffsetInFrame_ = sizeof(std::uint32_t);
266       } else { // formatted
267         if (FrameLength() > recordOffsetInFrame_ &&
268             Frame()[recordOffsetInFrame_] == '\r') {
269           ++recordOffsetInFrame_;
270         }
271         if (FrameLength() > recordOffsetInFrame_ &&
272             Frame()[recordOffsetInFrame_] == '\n') {
273           ++recordOffsetInFrame_;
274         }
275         if (!pinnedFrame || mayPosition()) {
276           frameOffsetInFile_ += recordOffsetInFrame_;
277           recordOffsetInFrame_ = 0;
278         }
279       }
280     }
281     ++currentRecordNumber;
282   } else { // unformatted stream
283     furthestPositionInRecord =
284         std::max(furthestPositionInRecord, positionInRecord);
285     frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
286     recordOffsetInFrame_ = 0;
287   }
288   BeginRecord();
289   leftTabLimit.reset();
290 }
291 
292 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
293   if (direction_ == Direction::Input) {
294     FinishReadingRecord(handler);
295     return BeginReadingRecord(handler);
296   } else { // Direction::Output
297     bool ok{true};
298     RUNTIME_CHECK(handler, isUnformatted.has_value());
299     positionInRecord = furthestPositionInRecord;
300     if (access == Access::Direct) {
301       if (furthestPositionInRecord <
302           openRecl.value_or(furthestPositionInRecord)) {
303         // Pad remainder of fixed length record
304         WriteFrame(
305             frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);
306         std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
307             isUnformatted.value_or(false) ? 0 : ' ',
308             *openRecl - furthestPositionInRecord);
309         furthestPositionInRecord = *openRecl;
310       }
311     } else if (*isUnformatted) {
312       if (access == Access::Sequential) {
313         // Append the length of a sequential unformatted variable-length record
314         // as its footer, then overwrite the reserved first four bytes of the
315         // record with its length as its header.  These four bytes were skipped
316         // over in BeginUnformattedIO<Output>().
317         // TODO: Break very large records up into subrecords with negative
318         // headers &/or footers
319         std::uint32_t length;
320         length = furthestPositionInRecord - sizeof length;
321         ok = ok &&
322             Emit(reinterpret_cast<const char *>(&length), sizeof length,
323                 sizeof length, handler);
324         positionInRecord = 0;
325         ok = ok &&
326             Emit(reinterpret_cast<const char *>(&length), sizeof length,
327                 sizeof length, handler);
328       } else {
329         // Unformatted stream: nothing to do
330       }
331     } else if (handler.GetIoStat() != IostatOk &&
332         furthestPositionInRecord == 0) {
333       // Error in formatted variable length record, and no output yet; do
334       // nothing, like most other Fortran compilers do.
335       return true;
336     } else {
337       // Terminate formatted variable length record
338       const char *lineEnding{"\n"};
339       std::size_t lineEndingBytes{1};
340 #ifdef _WIN32
341       if (!isWindowsTextFile()) {
342         lineEnding = "\r\n";
343         lineEndingBytes = 2;
344       }
345 #endif
346       ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler);
347     }
348     leftTabLimit.reset();
349     if (IsAfterEndfile()) {
350       return false;
351     }
352     CommitWrites();
353     ++currentRecordNumber;
354     if (access != Access::Direct) {
355       impliedEndfile_ = IsRecordFile();
356       if (IsAtEOF()) {
357         endfileRecordNumber.reset();
358       }
359     }
360     return ok;
361   }
362 }
363 
364 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
365   if (access == Access::Direct || !IsRecordFile()) {
366     handler.SignalError(IostatBackspaceNonSequential,
367         "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",
368         unitNumber());
369   } else {
370     if (IsAfterEndfile()) {
371       // BACKSPACE after explicit ENDFILE
372       currentRecordNumber = *endfileRecordNumber;
373     } else if (leftTabLimit && direction_ == Direction::Input) {
374       // BACKSPACE after non-advancing input
375       leftTabLimit.reset();
376     } else {
377       DoImpliedEndfile(handler);
378       if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
379         --currentRecordNumber;
380         if (openRecl && access == Access::Direct) {
381           BackspaceFixedRecord(handler);
382         } else {
383           RUNTIME_CHECK(handler, isUnformatted.has_value());
384           if (isUnformatted.value_or(false)) {
385             BackspaceVariableUnformattedRecord(handler);
386           } else {
387             BackspaceVariableFormattedRecord(handler);
388           }
389         }
390       }
391     }
392     BeginRecord();
393     anyWriteSinceLastPositioning_ = false;
394   }
395 }
396 
397 void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {
398   if (!mayPosition()) {
399     auto frameAt{FrameAt()};
400     if (frameOffsetInFile_ >= frameAt &&
401         frameOffsetInFile_ <
402             static_cast<std::int64_t>(frameAt + FrameLength())) {
403       // A Flush() that's about to happen to a non-positionable file
404       // needs to advance frameOffsetInFile_ to prevent attempts at
405       // impossible seeks
406       CommitWrites();
407       leftTabLimit.reset();
408     }
409   }
410   Flush(handler);
411 }
412 
413 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
414   if (isTerminal()) {
415     FlushOutput(handler);
416   }
417 }
418 
419 void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
420   if (access == Access::Direct) {
421     handler.SignalError(IostatEndfileDirect,
422         "ENDFILE(UNIT=%d) on direct-access file", unitNumber());
423   } else if (!mayWrite()) {
424     handler.SignalError(IostatEndfileUnwritable,
425         "ENDFILE(UNIT=%d) on read-only file", unitNumber());
426   } else if (IsAfterEndfile()) {
427     // ENDFILE after ENDFILE
428   } else {
429     DoEndfile(handler);
430     if (IsRecordFile() && access != Access::Direct) {
431       // Explicit ENDFILE leaves position *after* the endfile record
432       RUNTIME_CHECK(handler, endfileRecordNumber.has_value());
433       currentRecordNumber = *endfileRecordNumber + 1;
434     }
435   }
436 }
437 
438 void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
439   if (access == Access::Direct) {
440     handler.SignalError(IostatRewindNonSequential,
441         "REWIND(UNIT=%d) on non-sequential file", unitNumber());
442   } else {
443     DoImpliedEndfile(handler);
444     SetPosition(0, handler);
445     currentRecordNumber = 1;
446     leftTabLimit.reset();
447     anyWriteSinceLastPositioning_ = false;
448   }
449 }
450 
451 void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) {
452   frameOffsetInFile_ = pos;
453   recordOffsetInFrame_ = 0;
454   if (access == Access::Direct) {
455     directAccessRecWasSet_ = true;
456   }
457   BeginRecord();
458 }
459 
460 bool ExternalFileUnit::SetStreamPos(
461     std::int64_t oneBasedPos, IoErrorHandler &handler) {
462   if (access != Access::Stream) {
463     handler.SignalError("POS= may not appear unless ACCESS='STREAM'");
464     return false;
465   }
466   if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)
467     handler.SignalError(
468         "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos));
469     return false;
470   }
471   // A backwards POS= implies truncation after writing, at least in
472   // Intel and NAG.
473   if (static_cast<std::size_t>(oneBasedPos - 1) <
474       frameOffsetInFile_ + recordOffsetInFrame_) {
475     DoImpliedEndfile(handler);
476   }
477   SetPosition(oneBasedPos - 1, handler);
478   // We no longer know which record we're in.  Set currentRecordNumber to
479   // a large value from whence we can both advance and backspace.
480   currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;
481   endfileRecordNumber.reset();
482   return true;
483 }
484 
485 bool ExternalFileUnit::SetDirectRec(
486     std::int64_t oneBasedRec, IoErrorHandler &handler) {
487   if (access != Access::Direct) {
488     handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");
489     return false;
490   }
491   if (!openRecl) {
492     handler.SignalError("RECL= was not specified");
493     return false;
494   }
495   if (oneBasedRec < 1) {
496     handler.SignalError(
497         "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec));
498     return false;
499   }
500   currentRecordNumber = oneBasedRec;
501   SetPosition((oneBasedRec - 1) * *openRecl, handler);
502   return true;
503 }
504 
505 void ExternalFileUnit::EndIoStatement() {
506   io_.reset();
507   u_.emplace<std::monostate>();
508   lock_.Drop();
509 }
510 
511 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
512     IoErrorHandler &handler) {
513   RUNTIME_CHECK(handler, access == Access::Sequential);
514   std::int32_t header{0}, footer{0};
515   std::size_t need{recordOffsetInFrame_ + sizeof header};
516   std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
517   // Try to emit informative errors to help debug corrupted files.
518   const char *error{nullptr};
519   if (got < need) {
520     if (got == recordOffsetInFrame_) {
521       HitEndOnRead(handler);
522     } else {
523       error = "Unformatted variable-length sequential file input failed at "
524               "record #%jd (file offset %jd): truncated record header";
525     }
526   } else {
527     header = ReadHeaderOrFooter(recordOffsetInFrame_);
528     recordLength = sizeof header + header; // does not include footer
529     need = recordOffsetInFrame_ + *recordLength + sizeof footer;
530     got = ReadFrame(frameOffsetInFile_, need, handler);
531     if (got < need) {
532       error = "Unformatted variable-length sequential file input failed at "
533               "record #%jd (file offset %jd): hit EOF reading record with "
534               "length %jd bytes";
535     } else {
536       footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);
537       if (footer != header) {
538         error = "Unformatted variable-length sequential file input failed at "
539                 "record #%jd (file offset %jd): record header has length %jd "
540                 "that does not match record footer (%jd)";
541       }
542     }
543   }
544   if (error) {
545     handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
546         static_cast<std::intmax_t>(frameOffsetInFile_),
547         static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
548     // TODO: error recovery
549   }
550   positionInRecord = sizeof header;
551 }
552 
553 void ExternalFileUnit::BeginVariableFormattedInputRecord(
554     IoErrorHandler &handler) {
555   if (this == defaultInput) {
556     if (defaultOutput) {
557       defaultOutput->FlushOutput(handler);
558     }
559     if (errorOutput) {
560       errorOutput->FlushOutput(handler);
561     }
562   }
563   std::size_t length{0};
564   do {
565     std::size_t need{length + 1};
566     length =
567         ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -
568         recordOffsetInFrame_;
569     if (length < need) {
570       if (length > 0) {
571         // final record w/o \n
572         recordLength = length;
573         unterminatedRecord = true;
574       } else {
575         HitEndOnRead(handler);
576       }
577       break;
578     }
579   } while (!SetVariableFormattedRecordLength());
580 }
581 
582 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
583   RUNTIME_CHECK(handler, openRecl.has_value());
584   if (frameOffsetInFile_ < *openRecl) {
585     handler.SignalError(IostatBackspaceAtFirstRecord);
586   } else {
587     frameOffsetInFile_ -= *openRecl;
588   }
589 }
590 
591 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
592     IoErrorHandler &handler) {
593   std::int32_t header{0};
594   auto headerBytes{static_cast<std::int64_t>(sizeof header)};
595   frameOffsetInFile_ += recordOffsetInFrame_;
596   recordOffsetInFrame_ = 0;
597   if (frameOffsetInFile_ <= headerBytes) {
598     handler.SignalError(IostatBackspaceAtFirstRecord);
599     return;
600   }
601   // Error conditions here cause crashes, not file format errors, because the
602   // validity of the file structure before the current record will have been
603   // checked informatively in NextSequentialVariableUnformattedInputRecord().
604   std::size_t got{
605       ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
606   if (static_cast<std::int64_t>(got) < headerBytes) {
607     handler.SignalError(IostatShortRead);
608     return;
609   }
610   recordLength = ReadHeaderOrFooter(0);
611   if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {
612     handler.SignalError(IostatBadUnformattedRecord);
613     return;
614   }
615   frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
616   auto need{static_cast<std::size_t>(
617       recordOffsetInFrame_ + sizeof header + *recordLength)};
618   got = ReadFrame(frameOffsetInFile_, need, handler);
619   if (got < need) {
620     handler.SignalError(IostatShortRead);
621     return;
622   }
623   header = ReadHeaderOrFooter(recordOffsetInFrame_);
624   if (header != *recordLength) {
625     handler.SignalError(IostatBadUnformattedRecord);
626     return;
627   }
628 }
629 
630 // There's no portable memrchr(), unfortunately, and strrchr() would
631 // fail on a record with a NUL, so we have to do it the hard way.
632 static RT_API_ATTRS const char *FindLastNewline(
633     const char *str, std::size_t length) {
634   for (const char *p{str + length}; p >= str; p--) {
635     if (*p == '\n') {
636       return p;
637     }
638   }
639   return nullptr;
640 }
641 
642 void ExternalFileUnit::BackspaceVariableFormattedRecord(
643     IoErrorHandler &handler) {
644   // File offset of previous record's newline
645   auto prevNL{
646       frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
647   if (prevNL < 0) {
648     handler.SignalError(IostatBackspaceAtFirstRecord);
649     return;
650   }
651   while (true) {
652     if (frameOffsetInFile_ < prevNL) {
653       if (const char *p{
654               FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
655         recordOffsetInFrame_ = p - Frame() + 1;
656         recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
657         break;
658       }
659     }
660     if (frameOffsetInFile_ == 0) {
661       recordOffsetInFrame_ = 0;
662       recordLength = prevNL;
663       break;
664     }
665     frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
666     auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
667     auto got{ReadFrame(frameOffsetInFile_, need, handler)};
668     if (got < need) {
669       handler.SignalError(IostatShortRead);
670       return;
671     }
672   }
673   if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {
674     handler.SignalError(IostatMissingTerminator);
675     return;
676   }
677   if (*recordLength > 0 &&
678       Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
679     --*recordLength;
680   }
681 }
682 
683 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
684   if (access != Access::Direct) {
685     if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) {
686       // Flush a partial record after non-advancing output
687       impliedEndfile_ = true;
688     }
689     if (impliedEndfile_ && mayPosition()) {
690       DoEndfile(handler);
691     }
692   }
693   impliedEndfile_ = false;
694 }
695 
696 template <bool ANY_DIR, Direction DIR>
697 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
698   if (IsRecordFile() && access != Access::Direct) {
699     furthestPositionInRecord =
700         std::max(positionInRecord, furthestPositionInRecord);
701     if (leftTabLimit) { // last I/O was non-advancing
702       if (access == Access::Sequential && direction_ == Direction::Output) {
703         if constexpr (ANY_DIR || DIR == Direction::Output) {
704           // When DoEndfile() is called from BeginReadingRecord(),
705           // this call to AdvanceRecord() may appear as a recursion
706           // though it may never happen. Expose the call only
707           // under the constexpr direction check.
708           AdvanceRecord(handler);
709         } else {
710           // This check always fails if we are here.
711           RUNTIME_CHECK(handler, direction_ != Direction::Output);
712         }
713       } else { // Access::Stream or input
714         leftTabLimit.reset();
715         ++currentRecordNumber;
716       }
717     }
718     endfileRecordNumber = currentRecordNumber;
719   }
720   frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
721   recordOffsetInFrame_ = 0;
722   FlushOutput(handler);
723   Truncate(frameOffsetInFile_, handler);
724   TruncateFrame(frameOffsetInFile_, handler);
725   BeginRecord();
726   impliedEndfile_ = false;
727   anyWriteSinceLastPositioning_ = false;
728 }
729 
730 template void ExternalFileUnit::DoEndfile(IoErrorHandler &handler);
731 template void ExternalFileUnit::DoEndfile<false, Direction::Output>(
732     IoErrorHandler &handler);
733 template void ExternalFileUnit::DoEndfile<false, Direction::Input>(
734     IoErrorHandler &handler);
735 
736 void ExternalFileUnit::CommitWrites() {
737   frameOffsetInFile_ +=
738       recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
739   recordOffsetInFrame_ = 0;
740   BeginRecord();
741 }
742 
743 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) {
744   if (access == Access::Direct) {
745     RUNTIME_CHECK(handler, openRecl);
746     if (!directAccessRecWasSet_) {
747       handler.SignalError(
748           "No REC= was specified for a data transfer with ACCESS='DIRECT'");
749       return false;
750     }
751   }
752   return true;
753 }
754 
755 void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) {
756   handler.SignalEnd();
757   if (IsRecordFile() && access != Access::Direct) {
758     endfileRecordNumber = currentRecordNumber;
759   }
760 }
761 
762 ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) {
763   OwningPtr<ChildIo> current{std::move(child_)};
764   Terminator &terminator{parent.GetIoErrorHandler()};
765   OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))};
766   child_.reset(next.release());
767   return *child_;
768 }
769 
770 void ExternalFileUnit::PopChildIo(ChildIo &child) {
771   if (child_.get() != &child) {
772     child.parent().GetIoErrorHandler().Crash(
773         "ChildIo being popped is not top of stack");
774   }
775   child_.reset(child.AcquirePrevious().release()); // deletes top child
776 }
777 
778 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
779   std::int32_t word;
780   char *wordPtr{reinterpret_cast<char *>(&word)};
781   std::memcpy(wordPtr, Frame() + frameOffset, sizeof word);
782   if (swapEndianness_) {
783     SwapEndianness(wordPtr, sizeof word, sizeof word);
784   }
785   return word;
786 }
787 
788 void ChildIo::EndIoStatement() {
789   io_.reset();
790   u_.emplace<std::monostate>();
791 }
792 
793 Iostat ChildIo::CheckFormattingAndDirection(
794     bool unformatted, Direction direction) {
795   bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
796   bool parentIsFormatted{parentIsInput
797           ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
798               nullptr
799           : parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
800               nullptr};
801   bool parentIsUnformatted{!parentIsFormatted};
802   if (unformatted != parentIsUnformatted) {
803     return unformatted ? IostatUnformattedChildOnFormattedParent
804                        : IostatFormattedChildOnUnformattedParent;
805   } else if (parentIsInput != (direction == Direction::Input)) {
806     return parentIsInput ? IostatChildOutputToInputParent
807                          : IostatChildInputFromOutputParent;
808   } else {
809     return IostatOk;
810   }
811 }
812 
813 RT_OFFLOAD_API_GROUP_END
814 } // namespace Fortran::runtime::io
815