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