1 //===-- runtime/unit.cpp ----------------------------------------*- C++ -*-===//
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 
15 namespace Fortran::runtime::io {
16 
17 // The per-unit data structures are created on demand so that Fortran I/O
18 // should work without a Fortran main program.
19 static Lock unitMapLock;
20 static UnitMap *unitMap{nullptr};
21 static ExternalFileUnit *defaultInput{nullptr};
22 static ExternalFileUnit *defaultOutput{nullptr};
23 
24 void FlushOutputOnCrash(const Terminator &terminator) {
25   if (!defaultOutput) {
26     return;
27   }
28   CriticalSection critical{unitMapLock};
29   if (defaultOutput) {
30     IoErrorHandler handler{terminator};
31     handler.HasIoStat(); // prevent nested crash if flush has error
32     defaultOutput->Flush(handler);
33   }
34 }
35 
36 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
37   return GetUnitMap().LookUp(unit);
38 }
39 
40 ExternalFileUnit &ExternalFileUnit::LookUpOrCrash(
41     int unit, const Terminator &terminator) {
42   ExternalFileUnit *file{LookUp(unit)};
43   if (!file) {
44     terminator.Crash("Not an open I/O unit number: %d", unit);
45   }
46   return *file;
47 }
48 
49 ExternalFileUnit &ExternalFileUnit::LookUpOrCreate(
50     int unit, const Terminator &terminator, bool &wasExtant) {
51   return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
52 }
53 
54 ExternalFileUnit &ExternalFileUnit::LookUpOrCreateAnonymous(
55     int unit, Direction dir, bool isUnformatted, const Terminator &terminator) {
56   bool exists{false};
57   ExternalFileUnit &result{
58       GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
59   if (!exists) {
60     // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
61     std::size_t pathMaxLen{32};
62     auto path{SizedNew<char>{terminator}(pathMaxLen)};
63     std::snprintf(path.get(), pathMaxLen, "fort.%d", unit);
64     IoErrorHandler handler{terminator};
65     result.OpenUnit(
66         dir == Direction::Input ? OpenStatus::Old : OpenStatus::Replace,
67         Position::Rewind, std::move(path), std::strlen(path.get()), handler);
68     result.isUnformatted = isUnformatted;
69   }
70   return result;
71 }
72 
73 ExternalFileUnit &ExternalFileUnit::CreateNew(
74     int unit, const Terminator &terminator) {
75   bool wasExtant{false};
76   ExternalFileUnit &result{
77       GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
78   RUNTIME_CHECK(terminator, !wasExtant);
79   return result;
80 }
81 
82 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
83   return GetUnitMap().LookUpForClose(unit);
84 }
85 
86 int ExternalFileUnit::NewUnit(const Terminator &terminator) {
87   return GetUnitMap().NewUnit(terminator).unitNumber();
88 }
89 
90 void ExternalFileUnit::OpenUnit(OpenStatus status, Position position,
91     OwningPtr<char> &&newPath, std::size_t newPathLength,
92     IoErrorHandler &handler) {
93   if (IsOpen()) {
94     if (status == OpenStatus::Old &&
95         (!newPath.get() ||
96             (path() && pathLength() == newPathLength &&
97                 std::memcmp(path(), newPath.get(), newPathLength) == 0))) {
98       // OPEN of existing unit, STATUS='OLD', not new FILE=
99       newPath.reset();
100       return;
101     }
102     // Otherwise, OPEN on open unit with new FILE= implies CLOSE
103     DoImpliedEndfile(handler);
104     Flush(handler);
105     Close(CloseStatus::Keep, handler);
106   }
107   set_path(std::move(newPath), newPathLength);
108   Open(status, position, handler);
109   auto totalBytes{knownSize()};
110   if (access == Access::Direct) {
111     if (!isFixedRecordLength || !recordLength) {
112       handler.SignalError(IostatOpenBadRecl,
113           "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
114           unitNumber());
115     } else if (*recordLength <= 0) {
116       handler.SignalError(IostatOpenBadRecl,
117           "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
118           unitNumber(), static_cast<std::intmax_t>(*recordLength));
119     } else if (!totalBytes) {
120       handler.SignalError(IostatOpenUnknownSize,
121           "OPEN(UNIT=%d,ACCESS='DIRECT'): file size is not known");
122     } else if (*totalBytes % *recordLength != 0) {
123       handler.SignalError(IostatOpenBadAppend,
124           "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
125           "even divisor of the file size %jd",
126           unitNumber(), static_cast<std::intmax_t>(*recordLength),
127           static_cast<std::intmax_t>(*totalBytes));
128     }
129   }
130   if (position == Position::Append) {
131     if (*totalBytes && recordLength && *recordLength) {
132       endfileRecordNumber = 1 + (*totalBytes / *recordLength);
133     } else {
134       // Fake it so that we can backspace relative from the end
135       endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 1;
136     }
137     currentRecordNumber = *endfileRecordNumber;
138   } else {
139     currentRecordNumber = 1;
140   }
141 }
142 
143 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
144   DoImpliedEndfile(handler);
145   Flush(handler);
146   Close(status, handler);
147 }
148 
149 void ExternalFileUnit::DestroyClosed() {
150   GetUnitMap().DestroyClosed(*this); // destroys *this
151 }
152 
153 bool ExternalFileUnit::SetDirection(
154     Direction direction, IoErrorHandler &handler) {
155   if (direction == Direction::Input) {
156     if (mayRead()) {
157       direction_ = Direction::Input;
158       return true;
159     } else {
160       handler.SignalError(IostatReadFromWriteOnly,
161           "READ(UNIT=%d) with ACTION='WRITE'", unitNumber());
162       return false;
163     }
164   } else {
165     if (mayWrite()) {
166       direction_ = Direction::Output;
167       return true;
168     } else {
169       handler.SignalError(IostatWriteToReadOnly,
170           "WRITE(UNIT=%d) with ACTION='READ'", unitNumber());
171       return false;
172     }
173   }
174 }
175 
176 UnitMap &ExternalFileUnit::GetUnitMap() {
177   if (unitMap) {
178     return *unitMap;
179   }
180   CriticalSection critical{unitMapLock};
181   if (unitMap) {
182     return *unitMap;
183   }
184   Terminator terminator{__FILE__, __LINE__};
185   IoErrorHandler handler{terminator};
186   unitMap = New<UnitMap>{terminator}().release();
187   ExternalFileUnit &out{ExternalFileUnit::CreateNew(6, terminator)};
188   out.Predefine(1);
189   out.set_mayRead(false);
190   out.set_mayWrite(true);
191   out.set_mayPosition(false);
192   out.SetDirection(Direction::Output, handler);
193   defaultOutput = &out;
194   ExternalFileUnit &in{ExternalFileUnit::CreateNew(5, terminator)};
195   in.Predefine(0);
196   in.set_mayRead(true);
197   in.set_mayWrite(false);
198   in.set_mayPosition(false);
199   in.SetDirection(Direction::Input, handler);
200   defaultInput = &in;
201   // TODO: Set UTF-8 mode from the environment
202   return *unitMap;
203 }
204 
205 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
206   CriticalSection critical{unitMapLock};
207   if (unitMap) {
208     unitMap->CloseAll(handler);
209     FreeMemoryAndNullify(unitMap);
210   }
211   defaultOutput = nullptr;
212 }
213 
214 void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
215   CriticalSection critical{unitMapLock};
216   if (unitMap) {
217     unitMap->FlushAll(handler);
218   }
219 }
220 
221 bool ExternalFileUnit::Emit(
222     const char *data, std::size_t bytes, IoErrorHandler &handler) {
223   auto furthestAfter{std::max(furthestPositionInRecord,
224       positionInRecord + static_cast<std::int64_t>(bytes))};
225   if (furthestAfter > recordLength.value_or(furthestAfter)) {
226     handler.SignalError(IostatRecordWriteOverrun,
227         "Attempt to write %zd bytes to position %jd in a fixed-size record of "
228         "%jd bytes",
229         bytes, static_cast<std::intmax_t>(positionInRecord),
230         static_cast<std::intmax_t>(*recordLength));
231     return false;
232   }
233   WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
234   if (positionInRecord > furthestPositionInRecord) {
235     std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
236         positionInRecord - furthestPositionInRecord);
237   }
238   std::memcpy(Frame() + recordOffsetInFrame_ + positionInRecord, data, bytes);
239   positionInRecord += bytes;
240   furthestPositionInRecord = furthestAfter;
241   return true;
242 }
243 
244 bool ExternalFileUnit::Receive(
245     char *data, std::size_t bytes, IoErrorHandler &handler) {
246   RUNTIME_CHECK(handler, direction_ == Direction::Input);
247   auto furthestAfter{std::max(furthestPositionInRecord,
248       positionInRecord + static_cast<std::int64_t>(bytes))};
249   if (furthestAfter > recordLength.value_or(furthestAfter)) {
250     handler.SignalError(IostatRecordReadOverrun,
251         "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
252         bytes, static_cast<std::intmax_t>(positionInRecord),
253         static_cast<std::intmax_t>(*recordLength));
254     return false;
255   }
256   auto need{recordOffsetInFrame_ + furthestAfter};
257   auto got{ReadFrame(frameOffsetInFile_, need, handler)};
258   if (got >= need) {
259     std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
260     positionInRecord += bytes;
261     furthestPositionInRecord = furthestAfter;
262     return true;
263   } else {
264     handler.SignalEnd();
265     endfileRecordNumber = currentRecordNumber;
266     return false;
267   }
268 }
269 
270 std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
271     IoErrorHandler &handler) {
272   RUNTIME_CHECK(handler, direction_ == Direction::Input);
273   if (const char *p{FrameNextInput(handler, 1)}) {
274     // TODO: UTF-8 decoding; may have to get more bytes in a loop
275     return *p;
276   }
277   return std::nullopt;
278 }
279 
280 const char *ExternalFileUnit::FrameNextInput(
281     IoErrorHandler &handler, std::size_t bytes) {
282   RUNTIME_CHECK(handler, !isUnformatted);
283   if (static_cast<std::int64_t>(positionInRecord + bytes) <=
284       recordLength.value_or(positionInRecord + bytes)) {
285     auto at{recordOffsetInFrame_ + positionInRecord};
286     auto need{static_cast<std::size_t>(at + bytes)};
287     auto got{ReadFrame(frameOffsetInFile_, need, handler)};
288     SetSequentialVariableFormattedRecordLength();
289     if (got >= need) {
290       return Frame() + at;
291     }
292     handler.SignalEnd();
293     endfileRecordNumber = currentRecordNumber;
294   }
295   return nullptr;
296 }
297 
298 bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() {
299   if (recordLength || access != Access::Sequential) {
300     return true;
301   }
302   if (FrameLength() > recordOffsetInFrame_) {
303     const char *record{Frame() + recordOffsetInFrame_};
304     if (const char *nl{reinterpret_cast<const char *>(
305             std::memchr(record, '\n', FrameLength() - recordOffsetInFrame_))}) {
306       recordLength = nl - record;
307       if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
308         --*recordLength;
309       }
310       return true;
311     }
312   }
313   return false;
314 }
315 
316 void ExternalFileUnit::SetLeftTabLimit() {
317   leftTabLimit = furthestPositionInRecord;
318   positionInRecord = furthestPositionInRecord;
319 }
320 
321 void ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
322   RUNTIME_CHECK(handler, direction_ == Direction::Input);
323   if (access == Access::Sequential) {
324     if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
325       handler.SignalEnd();
326     } else if (isFixedRecordLength) {
327       RUNTIME_CHECK(handler, recordLength.has_value());
328       auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)};
329       auto got{ReadFrame(frameOffsetInFile_, need, handler)};
330       if (got < need) {
331         handler.SignalEnd();
332       }
333     } else if (isUnformatted) {
334       BeginSequentialVariableUnformattedInputRecord(handler);
335     } else { // formatted
336       BeginSequentialVariableFormattedInputRecord(handler);
337     }
338   }
339 }
340 
341 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
342   bool ok{true};
343   if (direction_ == Direction::Input) {
344     if (access == Access::Sequential) {
345       RUNTIME_CHECK(handler, recordLength.has_value());
346       if (isFixedRecordLength) {
347         frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength;
348         recordOffsetInFrame_ = 0;
349       } else if (isUnformatted) {
350         // Retain footer in frame for more efficient BACKSPACE
351         frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength;
352         recordOffsetInFrame_ = sizeof(std::uint32_t);
353         recordLength.reset();
354       } else { // formatted
355         if (Frame()[recordOffsetInFrame_ + *recordLength] == '\r') {
356           ++recordOffsetInFrame_;
357         }
358         recordOffsetInFrame_ += *recordLength + 1;
359         RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ - 1] == '\n');
360         recordLength.reset();
361       }
362     }
363   } else { // Direction::Output
364     if (!isUnformatted) {
365       if (isFixedRecordLength && recordLength) {
366         if (furthestPositionInRecord < *recordLength) {
367           WriteFrame(frameOffsetInFile_, *recordLength, handler);
368           std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
369               ' ', *recordLength - furthestPositionInRecord);
370         }
371       } else {
372         positionInRecord = furthestPositionInRecord;
373         ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF
374       }
375     }
376     frameOffsetInFile_ +=
377         recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
378     recordOffsetInFrame_ = 0;
379     impliedEndfile_ = true;
380   }
381   ++currentRecordNumber;
382   BeginRecord();
383   return ok;
384 }
385 
386 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
387   if (access != Access::Sequential) {
388     handler.SignalError(IostatBackspaceNonSequential,
389         "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber());
390   } else {
391     DoImpliedEndfile(handler);
392     --currentRecordNumber;
393     BeginRecord();
394     if (isFixedRecordLength) {
395       BackspaceFixedRecord(handler);
396     } else if (isUnformatted) {
397       BackspaceVariableUnformattedRecord(handler);
398     } else {
399       BackspaceVariableFormattedRecord(handler);
400     }
401   }
402 }
403 
404 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
405   if (isTerminal()) {
406     Flush(handler);
407   }
408 }
409 
410 void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
411   if (access != Access::Sequential) {
412     handler.SignalError(IostatEndfileNonSequential,
413         "ENDFILE(UNIT=%d) on non-sequential file", unitNumber());
414   } else if (!mayWrite()) {
415     handler.SignalError(IostatEndfileUnwritable,
416         "ENDFILE(UNIT=%d) on read-only file", unitNumber());
417   } else {
418     DoEndfile(handler);
419   }
420 }
421 
422 void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
423   if (access == Access::Direct) {
424     handler.SignalError(IostatRewindNonSequential,
425         "REWIND(UNIT=%d) on non-sequential file", unitNumber());
426   } else {
427     DoImpliedEndfile(handler);
428     SetPosition(0);
429     currentRecordNumber = 1;
430     // TODO: reset endfileRecordNumber?
431   }
432 }
433 
434 void ExternalFileUnit::EndIoStatement() {
435   frameOffsetInFile_ += recordOffsetInFrame_;
436   recordOffsetInFrame_ = 0;
437   io_.reset();
438   u_.emplace<std::monostate>();
439   lock_.Drop();
440 }
441 
442 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
443     IoErrorHandler &handler) {
444   std::int32_t header{0}, footer{0};
445   std::size_t need{recordOffsetInFrame_ + sizeof header};
446   std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
447   // Try to emit informative errors to help debug corrupted files.
448   const char *error{nullptr};
449   if (got < need) {
450     if (got == recordOffsetInFrame_) {
451       handler.SignalEnd();
452     } else {
453       error = "Unformatted variable-length sequential file input failed at "
454               "record #%jd (file offset %jd): truncated record header";
455     }
456   } else {
457     std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
458     recordLength = sizeof header + header; // does not include footer
459     need = recordOffsetInFrame_ + *recordLength + sizeof footer;
460     got = ReadFrame(frameOffsetInFile_, need, handler);
461     if (got < need) {
462       error = "Unformatted variable-length sequential file input failed at "
463               "record #%jd (file offset %jd): hit EOF reading record with "
464               "length %jd bytes";
465     } else {
466       std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength,
467           sizeof footer);
468       if (footer != header) {
469         error = "Unformatted variable-length sequential file input failed at "
470                 "record #%jd (file offset %jd): record header has length %jd "
471                 "that does not match record footer (%jd)";
472       }
473     }
474   }
475   if (error) {
476     handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
477         static_cast<std::intmax_t>(frameOffsetInFile_),
478         static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
479     // TODO: error recovery
480   }
481   positionInRecord = sizeof header;
482 }
483 
484 void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
485     IoErrorHandler &handler) {
486   if (this == defaultInput && defaultOutput) {
487     defaultOutput->Flush(handler);
488   }
489   std::size_t length{0};
490   do {
491     std::size_t need{recordOffsetInFrame_ + length + 1};
492     length = ReadFrame(frameOffsetInFile_, need, handler);
493     if (length < need) {
494       handler.SignalEnd();
495       break;
496     }
497   } while (!SetSequentialVariableFormattedRecordLength());
498 }
499 
500 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
501   RUNTIME_CHECK(handler, recordLength.has_value());
502   if (frameOffsetInFile_ < *recordLength) {
503     handler.SignalError(IostatBackspaceAtFirstRecord);
504   } else {
505     frameOffsetInFile_ -= *recordLength;
506   }
507 }
508 
509 void ExternalFileUnit::BackspaceVariableUnformattedRecord(
510     IoErrorHandler &handler) {
511   std::int32_t header{0}, footer{0};
512   auto headerBytes{static_cast<std::int64_t>(sizeof header)};
513   frameOffsetInFile_ += recordOffsetInFrame_;
514   recordOffsetInFrame_ = 0;
515   if (frameOffsetInFile_ <= headerBytes) {
516     handler.SignalError(IostatBackspaceAtFirstRecord);
517     return;
518   }
519   // Error conditions here cause crashes, not file format errors, because the
520   // validity of the file structure before the current record will have been
521   // checked informatively in NextSequentialVariableUnformattedInputRecord().
522   std::size_t got{
523       ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
524   RUNTIME_CHECK(handler, got >= sizeof footer);
525   std::memcpy(&footer, Frame(), sizeof footer);
526   recordLength = footer;
527   RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes);
528   frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
529   if (frameOffsetInFile_ >= headerBytes) {
530     frameOffsetInFile_ -= headerBytes;
531     recordOffsetInFrame_ = headerBytes;
532   }
533   auto need{static_cast<std::size_t>(
534       recordOffsetInFrame_ + sizeof header + *recordLength)};
535   got = ReadFrame(frameOffsetInFile_, need, handler);
536   RUNTIME_CHECK(handler, got >= need);
537   std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
538   RUNTIME_CHECK(handler, header == *recordLength);
539 }
540 
541 // There's no portable memrchr(), unfortunately, and strrchr() would
542 // fail on a record with a NUL, so we have to do it the hard way.
543 static const char *FindLastNewline(const char *str, std::size_t length) {
544   for (const char *p{str + length}; p-- > str;) {
545     if (*p == '\n') {
546       return p;
547     }
548   }
549   return nullptr;
550 }
551 
552 void ExternalFileUnit::BackspaceVariableFormattedRecord(
553     IoErrorHandler &handler) {
554   // File offset of previous record's newline
555   auto prevNL{
556       frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
557   if (prevNL < 0) {
558     handler.SignalError(IostatBackspaceAtFirstRecord);
559     return;
560   }
561   while (true) {
562     if (frameOffsetInFile_ < prevNL) {
563       if (const char *p{
564               FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
565         recordOffsetInFrame_ = p - Frame() + 1;
566         *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
567         break;
568       }
569     }
570     if (frameOffsetInFile_ == 0) {
571       recordOffsetInFrame_ = 0;
572       *recordLength = prevNL;
573       break;
574     }
575     frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
576     auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
577     auto got{ReadFrame(frameOffsetInFile_, need, handler)};
578     RUNTIME_CHECK(handler, got >= need);
579   }
580   RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n');
581   if (*recordLength > 0 &&
582       Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
583     --*recordLength;
584   }
585 }
586 
587 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
588   if (impliedEndfile_) {
589     impliedEndfile_ = false;
590     if (access == Access::Sequential && mayPosition()) {
591       DoEndfile(handler);
592     }
593   }
594 }
595 
596 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
597   endfileRecordNumber = currentRecordNumber;
598   Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler);
599   BeginRecord();
600   impliedEndfile_ = false;
601 }
602 } // namespace Fortran::runtime::io
603