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