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

source code of flang/runtime/unit.cpp