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