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 | |
20 | namespace Fortran::runtime::io { |
21 | |
22 | RT_OFFLOAD_VAR_GROUP_BEGIN |
23 | RT_VAR_ATTRS ExternalFileUnit *defaultInput{nullptr}; // unit 5 |
24 | RT_VAR_ATTRS ExternalFileUnit *defaultOutput{nullptr}; // unit 6 |
25 | RT_VAR_ATTRS ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension |
26 | RT_OFFLOAD_VAR_GROUP_END |
27 | |
28 | RT_OFFLOAD_API_GROUP_BEGIN |
29 | |
30 | static 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 | |
45 | bool 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 {0}; |
53 | int {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 | |
105 | bool 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 | |
133 | std::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 | |
149 | const 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 | |
166 | bool 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 | |
183 | bool 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 | |
227 | void 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 | |
270 | bool 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 | |
342 | void 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 | |
375 | void 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 | |
391 | void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { |
392 | if (isTerminal()) { |
393 | FlushOutput(handler); |
394 | } |
395 | } |
396 | |
397 | void 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 | |
416 | void 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 | |
429 | void 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 | |
438 | bool 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 | |
463 | bool 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 | |
483 | void ExternalFileUnit::EndIoStatement() { |
484 | io_.reset(); |
485 | u_.emplace<std::monostate>(); |
486 | lock_.Drop(); |
487 | } |
488 | |
489 | void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( |
490 | IoErrorHandler &handler) { |
491 | RUNTIME_CHECK(handler, access == Access::Sequential); |
492 | std::int32_t {0}, {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 | |
531 | void 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 | |
560 | void 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 | |
569 | void ExternalFileUnit::BackspaceVariableUnformattedRecord( |
570 | IoErrorHandler &handler) { |
571 | std::int32_t {0}; |
572 | auto {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. |
610 | static 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 | |
620 | void 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 | |
661 | void 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 | |
674 | template <bool ANY_DIR, Direction DIR> |
675 | void 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 | |
708 | template void ExternalFileUnit::DoEndfile(IoErrorHandler &handler); |
709 | template void ExternalFileUnit::DoEndfile<false, Direction::Output>( |
710 | IoErrorHandler &handler); |
711 | template void ExternalFileUnit::DoEndfile<false, Direction::Input>( |
712 | IoErrorHandler &handler); |
713 | |
714 | void ExternalFileUnit::CommitWrites() { |
715 | frameOffsetInFile_ += |
716 | recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); |
717 | recordOffsetInFrame_ = 0; |
718 | BeginRecord(); |
719 | } |
720 | |
721 | bool 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 | |
733 | void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) { |
734 | handler.SignalEnd(); |
735 | if (IsRecordFile() && access != Access::Direct) { |
736 | endfileRecordNumber = currentRecordNumber; |
737 | } |
738 | } |
739 | |
740 | ChildIo &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 | |
748 | void 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 | |
756 | std::int32_t ExternalFileUnit::(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 | |
766 | void ChildIo::EndIoStatement() { |
767 | io_.reset(); |
768 | u_.emplace<std::monostate>(); |
769 | } |
770 | |
771 | Iostat 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 | |
791 | RT_OFFLOAD_API_GROUP_END |
792 | } // namespace Fortran::runtime::io |
793 | |