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