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 | #include "unit.h" |
10 | #include "io-error.h" |
11 | #include "lock.h" |
12 | #include "tools.h" |
13 | #include "unit-map.h" |
14 | #include "flang/Runtime/magic-numbers.h" |
15 | #include <cstdio> |
16 | #include <limits> |
17 | #include <utility> |
18 | |
19 | namespace Fortran::runtime::io { |
20 | |
21 | // The per-unit data structures are created on demand so that Fortran I/O |
22 | // should work without a Fortran main program. |
23 | static Lock unitMapLock; |
24 | static Lock createOpenLock; |
25 | static UnitMap *unitMap{nullptr}; |
26 | static ExternalFileUnit *defaultInput{nullptr}; // unit 5 |
27 | static ExternalFileUnit *defaultOutput{nullptr}; // unit 6 |
28 | static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension |
29 | |
30 | void FlushOutputOnCrash(const Terminator &terminator) { |
31 | if (!defaultOutput && !errorOutput) { |
32 | return; |
33 | } |
34 | IoErrorHandler handler{terminator}; |
35 | handler.HasIoStat(); // prevent nested crash if flush has error |
36 | CriticalSection critical{unitMapLock}; |
37 | if (defaultOutput) { |
38 | defaultOutput->FlushOutput(handler); |
39 | } |
40 | if (errorOutput) { |
41 | errorOutput->FlushOutput(handler); |
42 | } |
43 | } |
44 | |
45 | ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { |
46 | return GetUnitMap().LookUp(n: unit); |
47 | } |
48 | |
49 | ExternalFileUnit *ExternalFileUnit::LookUpOrCreate( |
50 | int unit, const Terminator &terminator, bool &wasExtant) { |
51 | return GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant); |
52 | } |
53 | |
54 | ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit, |
55 | Direction dir, std::optional<bool> isUnformatted, |
56 | const Terminator &terminator) { |
57 | // Make sure that the returned anonymous unit has been opened |
58 | // not just created in the unitMap. |
59 | CriticalSection critical{createOpenLock}; |
60 | bool exists{false}; |
61 | ExternalFileUnit *result{ |
62 | GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant&: exists)}; |
63 | if (result && !exists) { |
64 | IoErrorHandler handler{terminator}; |
65 | result->OpenAnonymousUnit( |
66 | dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace, |
67 | Action::ReadWrite, Position::Rewind, Convert::Unknown, handler); |
68 | result->isUnformatted = isUnformatted; |
69 | } |
70 | return result; |
71 | } |
72 | |
73 | ExternalFileUnit *ExternalFileUnit::LookUp( |
74 | const char *path, std::size_t pathLen) { |
75 | return GetUnitMap().LookUp(path, pathLen); |
76 | } |
77 | |
78 | ExternalFileUnit &ExternalFileUnit::CreateNew( |
79 | int unit, const Terminator &terminator) { |
80 | bool wasExtant{false}; |
81 | ExternalFileUnit *result{ |
82 | GetUnitMap().LookUpOrCreate(n: unit, terminator, wasExtant)}; |
83 | RUNTIME_CHECK(terminator, result && !wasExtant); |
84 | return *result; |
85 | } |
86 | |
87 | ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { |
88 | return GetUnitMap().LookUpForClose(unit); |
89 | } |
90 | |
91 | ExternalFileUnit &ExternalFileUnit::NewUnit( |
92 | const Terminator &terminator, bool forChildIo) { |
93 | ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)}; |
94 | unit.createdForInternalChildIo_ = forChildIo; |
95 | return unit; |
96 | } |
97 | |
98 | bool ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status, |
99 | std::optional<Action> action, Position position, OwningPtr<char> &&newPath, |
100 | std::size_t newPathLength, Convert convert, IoErrorHandler &handler) { |
101 | if (convert == Convert::Unknown) { |
102 | convert = executionEnvironment.conversion; |
103 | } |
104 | swapEndianness_ = convert == Convert::Swap || |
105 | (convert == Convert::LittleEndian && !isHostLittleEndian) || |
106 | (convert == Convert::BigEndian && isHostLittleEndian); |
107 | bool impliedClose{false}; |
108 | if (IsConnected()) { |
109 | bool isSamePath{newPath.get() && path() && pathLength() == newPathLength && |
110 | std::memcmp(s1: path(), s2: newPath.get(), n: newPathLength) == 0}; |
111 | if (status && *status != OpenStatus::Old && isSamePath) { |
112 | handler.SignalError(msg: "OPEN statement for connected unit may not have " |
113 | "explicit STATUS= other than 'OLD'" ); |
114 | return impliedClose; |
115 | } |
116 | if (!newPath.get() || isSamePath) { |
117 | // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE= |
118 | newPath.reset(); |
119 | return impliedClose; |
120 | } |
121 | // Otherwise, OPEN on open unit with new FILE= implies CLOSE |
122 | DoImpliedEndfile(handler); |
123 | FlushOutput(handler); |
124 | TruncateFrame(0, handler); |
125 | Close(CloseStatus::Keep, handler); |
126 | impliedClose = true; |
127 | } |
128 | if (newPath.get() && newPathLength > 0) { |
129 | if (const auto *already{ |
130 | GetUnitMap().LookUp(newPath.get(), newPathLength)}) { |
131 | handler.SignalError(IostatOpenAlreadyConnected, |
132 | "OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d" , |
133 | unitNumber_, static_cast<int>(newPathLength), newPath.get(), |
134 | already->unitNumber_); |
135 | return impliedClose; |
136 | } |
137 | } |
138 | set_path(std::move(newPath), newPathLength); |
139 | Open(status.value_or(u: OpenStatus::Unknown), action, position, handler); |
140 | auto totalBytes{knownSize()}; |
141 | if (access == Access::Direct) { |
142 | if (!openRecl) { |
143 | handler.SignalError(IostatOpenBadRecl, |
144 | "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known" , |
145 | unitNumber()); |
146 | } else if (*openRecl <= 0) { |
147 | handler.SignalError(IostatOpenBadRecl, |
148 | "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid" , |
149 | unitNumber(), static_cast<std::intmax_t>(*openRecl)); |
150 | } else if (totalBytes && (*totalBytes % *openRecl != 0)) { |
151 | handler.SignalError(IostatOpenBadRecl, |
152 | "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " |
153 | "even divisor of the file size %jd" , |
154 | unitNumber(), static_cast<std::intmax_t>(*openRecl), |
155 | static_cast<std::intmax_t>(*totalBytes)); |
156 | } |
157 | recordLength = openRecl; |
158 | } |
159 | endfileRecordNumber.reset(); |
160 | currentRecordNumber = 1; |
161 | if (totalBytes && access == Access::Direct && openRecl.value_or(u: 0) > 0) { |
162 | endfileRecordNumber = 1 + (*totalBytes / *openRecl); |
163 | } |
164 | if (position == Position::Append) { |
165 | if (totalBytes) { |
166 | frameOffsetInFile_ = *totalBytes; |
167 | } |
168 | if (access != Access::Stream) { |
169 | if (!endfileRecordNumber) { |
170 | // Fake it so that we can backspace relative from the end |
171 | endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2; |
172 | } |
173 | currentRecordNumber = *endfileRecordNumber; |
174 | } |
175 | } |
176 | return impliedClose; |
177 | } |
178 | |
179 | void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status, |
180 | std::optional<Action> action, Position position, Convert convert, |
181 | IoErrorHandler &handler) { |
182 | // I/O to an unconnected unit reads/creates a local file, e.g. fort.7 |
183 | std::size_t pathMaxLen{32}; |
184 | auto path{SizedNew<char>{handler}(pathMaxLen)}; |
185 | std::snprintf(s: path.get(), maxlen: pathMaxLen, format: "fort.%d" , unitNumber_); |
186 | OpenUnit(status, action, position, std::move(path), std::strlen(path.get()), |
187 | convert, handler); |
188 | } |
189 | |
190 | void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { |
191 | DoImpliedEndfile(handler); |
192 | FlushOutput(handler); |
193 | Close(status, handler); |
194 | } |
195 | |
196 | void ExternalFileUnit::DestroyClosed() { |
197 | GetUnitMap().DestroyClosed(*this); // destroys *this |
198 | } |
199 | |
200 | Iostat ExternalFileUnit::SetDirection(Direction direction) { |
201 | if (direction == Direction::Input) { |
202 | if (mayRead()) { |
203 | direction_ = Direction::Input; |
204 | return IostatOk; |
205 | } else { |
206 | return IostatReadFromWriteOnly; |
207 | } |
208 | } else { |
209 | if (mayWrite()) { |
210 | direction_ = Direction::Output; |
211 | return IostatOk; |
212 | } else { |
213 | return IostatWriteToReadOnly; |
214 | } |
215 | } |
216 | } |
217 | |
218 | UnitMap &ExternalFileUnit::CreateUnitMap() { |
219 | Terminator terminator{__FILE__, __LINE__}; |
220 | IoErrorHandler handler{terminator}; |
221 | UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()}; |
222 | |
223 | bool wasExtant{false}; |
224 | ExternalFileUnit &out{*newUnitMap.LookUpOrCreate( |
225 | FORTRAN_DEFAULT_OUTPUT_UNIT, terminator, wasExtant)}; |
226 | RUNTIME_CHECK(terminator, !wasExtant); |
227 | out.Predefine(1); |
228 | handler.SignalError(out.SetDirection(Direction::Output)); |
229 | out.isUnformatted = false; |
230 | defaultOutput = &out; |
231 | |
232 | ExternalFileUnit &in{*newUnitMap.LookUpOrCreate( |
233 | FORTRAN_DEFAULT_INPUT_UNIT, terminator, wasExtant)}; |
234 | RUNTIME_CHECK(terminator, !wasExtant); |
235 | in.Predefine(0); |
236 | handler.SignalError(in.SetDirection(Direction::Input)); |
237 | in.isUnformatted = false; |
238 | defaultInput = ∈ |
239 | |
240 | ExternalFileUnit &error{ |
241 | *newUnitMap.LookUpOrCreate(FORTRAN_ERROR_UNIT, terminator, wasExtant)}; |
242 | RUNTIME_CHECK(terminator, !wasExtant); |
243 | error.Predefine(2); |
244 | handler.SignalError(error.SetDirection(Direction::Output)); |
245 | error.isUnformatted = false; |
246 | errorOutput = &error; |
247 | |
248 | return newUnitMap; |
249 | } |
250 | |
251 | // A back-up atexit() handler for programs that don't terminate with a main |
252 | // program END or a STOP statement or other Fortran-initiated program shutdown, |
253 | // such as programs with a C main() that terminate normally. It flushes all |
254 | // external I/O units. It is registered once the first time that any external |
255 | // I/O is attempted. |
256 | static void CloseAllExternalUnits() { |
257 | IoErrorHandler handler{"Fortran program termination" }; |
258 | ExternalFileUnit::CloseAll(handler); |
259 | } |
260 | |
261 | UnitMap &ExternalFileUnit::GetUnitMap() { |
262 | if (unitMap) { |
263 | return *unitMap; |
264 | } |
265 | { |
266 | CriticalSection critical{unitMapLock}; |
267 | if (unitMap) { |
268 | return *unitMap; |
269 | } |
270 | unitMap = &CreateUnitMap(); |
271 | } |
272 | std::atexit(func: CloseAllExternalUnits); |
273 | return *unitMap; |
274 | } |
275 | |
276 | void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { |
277 | CriticalSection critical{unitMapLock}; |
278 | if (unitMap) { |
279 | unitMap->CloseAll(handler); |
280 | FreeMemoryAndNullify(unitMap); |
281 | } |
282 | defaultOutput = nullptr; |
283 | defaultInput = nullptr; |
284 | errorOutput = nullptr; |
285 | } |
286 | |
287 | void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { |
288 | CriticalSection critical{unitMapLock}; |
289 | if (unitMap) { |
290 | unitMap->FlushAll(handler); |
291 | } |
292 | } |
293 | |
294 | static inline void SwapEndianness( |
295 | char *data, std::size_t bytes, std::size_t elementBytes) { |
296 | if (elementBytes > 1) { |
297 | auto half{elementBytes >> 1}; |
298 | for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) { |
299 | for (std::size_t k{0}; k < half; ++k) { |
300 | std::swap(a&: data[j + k], b&: data[j + elementBytes - 1 - k]); |
301 | } |
302 | } |
303 | } |
304 | } |
305 | |
306 | bool ExternalFileUnit::Emit(const char *data, std::size_t bytes, |
307 | std::size_t elementBytes, IoErrorHandler &handler) { |
308 | auto furthestAfter{std::max(a: furthestPositionInRecord, |
309 | b: positionInRecord + static_cast<std::int64_t>(bytes))}; |
310 | if (openRecl) { |
311 | // Check for fixed-length record overrun, but allow for |
312 | // sequential record termination. |
313 | int {0}; |
314 | int {0}; |
315 | if (access == Access::Sequential) { |
316 | if (isUnformatted.value_or(u: false)) { |
317 | // record header + footer |
318 | header = static_cast<int>(sizeof(std::uint32_t)); |
319 | extra = 2 * header; |
320 | } else { |
321 | #ifdef _WIN32 |
322 | if (!isWindowsTextFile()) { |
323 | ++extra; // carriage return (CR) |
324 | } |
325 | #endif |
326 | ++extra; // newline (LF) |
327 | } |
328 | } |
329 | if (furthestAfter > extra + *openRecl) { |
330 | handler.SignalError(IostatRecordWriteOverrun, |
331 | "Attempt to write %zd bytes to position %jd in a fixed-size record " |
332 | "of %jd bytes" , |
333 | bytes, static_cast<std::intmax_t>(positionInRecord - header), |
334 | static_cast<std::intmax_t>(*openRecl)); |
335 | return false; |
336 | } |
337 | } |
338 | if (recordLength) { |
339 | // It is possible for recordLength to have a value now for a |
340 | // variable-length output record if the previous operation |
341 | // was a BACKSPACE or non advancing input statement. |
342 | recordLength.reset(); |
343 | beganReadingRecord_ = false; |
344 | } |
345 | if (IsAfterEndfile()) { |
346 | handler.SignalError(IostatWriteAfterEndfile); |
347 | return false; |
348 | } |
349 | CheckDirectAccess(handler); |
350 | WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); |
351 | if (positionInRecord > furthestPositionInRecord) { |
352 | std::memset(s: Frame() + recordOffsetInFrame_ + furthestPositionInRecord, c: ' ', |
353 | n: positionInRecord - furthestPositionInRecord); |
354 | } |
355 | char *to{Frame() + recordOffsetInFrame_ + positionInRecord}; |
356 | std::memcpy(dest: to, src: data, n: bytes); |
357 | if (swapEndianness_) { |
358 | SwapEndianness(data: to, bytes, elementBytes); |
359 | } |
360 | positionInRecord += bytes; |
361 | furthestPositionInRecord = furthestAfter; |
362 | anyWriteSinceLastPositioning_ = true; |
363 | return true; |
364 | } |
365 | |
366 | bool ExternalFileUnit::Receive(char *data, std::size_t bytes, |
367 | std::size_t elementBytes, IoErrorHandler &handler) { |
368 | RUNTIME_CHECK(handler, direction_ == Direction::Input); |
369 | auto furthestAfter{std::max(a: furthestPositionInRecord, |
370 | b: positionInRecord + static_cast<std::int64_t>(bytes))}; |
371 | if (furthestAfter > recordLength.value_or(u&: furthestAfter)) { |
372 | handler.SignalError(IostatRecordReadOverrun, |
373 | "Attempt to read %zd bytes at position %jd in a record of %jd bytes" , |
374 | bytes, static_cast<std::intmax_t>(positionInRecord), |
375 | static_cast<std::intmax_t>(*recordLength)); |
376 | return false; |
377 | } |
378 | auto need{recordOffsetInFrame_ + furthestAfter}; |
379 | auto got{ReadFrame(frameOffsetInFile_, need, handler)}; |
380 | if (got >= need) { |
381 | std::memcpy(dest: data, src: Frame() + recordOffsetInFrame_ + positionInRecord, n: bytes); |
382 | if (swapEndianness_) { |
383 | SwapEndianness(data, bytes, elementBytes); |
384 | } |
385 | positionInRecord += bytes; |
386 | furthestPositionInRecord = furthestAfter; |
387 | return true; |
388 | } else { |
389 | HitEndOnRead(handler); |
390 | return false; |
391 | } |
392 | } |
393 | |
394 | std::size_t ExternalFileUnit::GetNextInputBytes( |
395 | const char *&p, IoErrorHandler &handler) { |
396 | RUNTIME_CHECK(handler, direction_ == Direction::Input); |
397 | std::size_t length{1}; |
398 | if (auto recl{EffectiveRecordLength()}) { |
399 | if (positionInRecord < *recl) { |
400 | length = *recl - positionInRecord; |
401 | } else { |
402 | p = nullptr; |
403 | return 0; |
404 | } |
405 | } |
406 | p = FrameNextInput(handler, length); |
407 | return p ? length : 0; |
408 | } |
409 | |
410 | const char *ExternalFileUnit::FrameNextInput( |
411 | IoErrorHandler &handler, std::size_t bytes) { |
412 | RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted); |
413 | if (static_cast<std::int64_t>(positionInRecord + bytes) <= |
414 | recordLength.value_or(u: positionInRecord + bytes)) { |
415 | auto at{recordOffsetInFrame_ + positionInRecord}; |
416 | auto need{static_cast<std::size_t>(at + bytes)}; |
417 | auto got{ReadFrame(frameOffsetInFile_, need, handler)}; |
418 | SetVariableFormattedRecordLength(); |
419 | if (got >= need) { |
420 | return Frame() + at; |
421 | } |
422 | HitEndOnRead(handler); |
423 | } |
424 | return nullptr; |
425 | } |
426 | |
427 | bool ExternalFileUnit::SetVariableFormattedRecordLength() { |
428 | if (recordLength || access == Access::Direct) { |
429 | return true; |
430 | } else if (FrameLength() > recordOffsetInFrame_) { |
431 | const char *record{Frame() + recordOffsetInFrame_}; |
432 | std::size_t bytes{FrameLength() - recordOffsetInFrame_}; |
433 | if (const char *nl{FindCharacter(record, '\n', bytes)}) { |
434 | recordLength = nl - record; |
435 | if (*recordLength > 0 && record[*recordLength - 1] == '\r') { |
436 | --*recordLength; |
437 | } |
438 | return true; |
439 | } |
440 | } |
441 | return false; |
442 | } |
443 | |
444 | bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { |
445 | RUNTIME_CHECK(handler, direction_ == Direction::Input); |
446 | if (!beganReadingRecord_) { |
447 | beganReadingRecord_ = true; |
448 | // Don't use IsAtEOF() to check for an EOF condition here, just detect |
449 | // it from a failed or short read from the file. IsAtEOF() could be |
450 | // wrong for formatted input if actual newline characters had been |
451 | // written in-band by previous WRITEs before a REWIND. In fact, |
452 | // now that we know that the unit is being used for input (again), |
453 | // it's best to reset endfileRecordNumber and ensure IsAtEOF() will |
454 | // now be true on return only if it gets set by HitEndOnRead(). |
455 | endfileRecordNumber.reset(); |
456 | if (access == Access::Direct) { |
457 | CheckDirectAccess(handler); |
458 | auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)}; |
459 | auto got{ReadFrame(frameOffsetInFile_, need, handler)}; |
460 | if (got >= need) { |
461 | recordLength = openRecl; |
462 | } else { |
463 | recordLength.reset(); |
464 | HitEndOnRead(handler); |
465 | } |
466 | } else { |
467 | if (anyWriteSinceLastPositioning_ && access == Access::Sequential) { |
468 | // Most Fortran implementations allow a READ after a WRITE; |
469 | // the read then just hits an EOF. |
470 | DoEndfile(handler); |
471 | } |
472 | recordLength.reset(); |
473 | RUNTIME_CHECK(handler, isUnformatted.has_value()); |
474 | if (*isUnformatted) { |
475 | if (access == Access::Sequential) { |
476 | BeginSequentialVariableUnformattedInputRecord(handler); |
477 | } |
478 | } else { // formatted sequential or stream |
479 | BeginVariableFormattedInputRecord(handler); |
480 | } |
481 | } |
482 | } |
483 | RUNTIME_CHECK(handler, |
484 | recordLength.has_value() || !IsRecordFile() || handler.InError()); |
485 | return !handler.InError(); |
486 | } |
487 | |
488 | void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) { |
489 | RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); |
490 | beganReadingRecord_ = false; |
491 | if (handler.GetIoStat() == IostatEnd || |
492 | (IsRecordFile() && !recordLength.has_value())) { |
493 | // Avoid bogus crashes in END/ERR circumstances; but |
494 | // still increment the current record number so that |
495 | // an attempted read of an endfile record, followed by |
496 | // a BACKSPACE, will still be at EOF. |
497 | ++currentRecordNumber; |
498 | } else if (IsRecordFile()) { |
499 | recordOffsetInFrame_ += *recordLength; |
500 | if (access != Access::Direct) { |
501 | RUNTIME_CHECK(handler, isUnformatted.has_value()); |
502 | recordLength.reset(); |
503 | if (isUnformatted.value_or(u: false)) { |
504 | // Retain footer in frame for more efficient BACKSPACE |
505 | frameOffsetInFile_ += recordOffsetInFrame_; |
506 | recordOffsetInFrame_ = sizeof(std::uint32_t); |
507 | } else { // formatted |
508 | if (FrameLength() > recordOffsetInFrame_ && |
509 | Frame()[recordOffsetInFrame_] == '\r') { |
510 | ++recordOffsetInFrame_; |
511 | } |
512 | if (FrameLength() > recordOffsetInFrame_ && |
513 | Frame()[recordOffsetInFrame_] == '\n') { |
514 | ++recordOffsetInFrame_; |
515 | } |
516 | if (!pinnedFrame || mayPosition()) { |
517 | frameOffsetInFile_ += recordOffsetInFrame_; |
518 | recordOffsetInFrame_ = 0; |
519 | } |
520 | } |
521 | } |
522 | ++currentRecordNumber; |
523 | } else { // unformatted stream |
524 | furthestPositionInRecord = |
525 | std::max(a: furthestPositionInRecord, b: positionInRecord); |
526 | frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; |
527 | } |
528 | BeginRecord(); |
529 | } |
530 | |
531 | bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { |
532 | if (direction_ == Direction::Input) { |
533 | FinishReadingRecord(handler); |
534 | return BeginReadingRecord(handler); |
535 | } else { // Direction::Output |
536 | bool ok{true}; |
537 | RUNTIME_CHECK(handler, isUnformatted.has_value()); |
538 | positionInRecord = furthestPositionInRecord; |
539 | if (access == Access::Direct) { |
540 | if (furthestPositionInRecord < |
541 | openRecl.value_or(u&: furthestPositionInRecord)) { |
542 | // Pad remainder of fixed length record |
543 | WriteFrame( |
544 | frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler); |
545 | std::memset(s: Frame() + recordOffsetInFrame_ + furthestPositionInRecord, |
546 | c: isUnformatted.value_or(u: false) ? 0 : ' ', |
547 | n: *openRecl - furthestPositionInRecord); |
548 | furthestPositionInRecord = *openRecl; |
549 | } |
550 | } else if (*isUnformatted) { |
551 | if (access == Access::Sequential) { |
552 | // Append the length of a sequential unformatted variable-length record |
553 | // as its footer, then overwrite the reserved first four bytes of the |
554 | // record with its length as its header. These four bytes were skipped |
555 | // over in BeginUnformattedIO<Output>(). |
556 | // TODO: Break very large records up into subrecords with negative |
557 | // headers &/or footers |
558 | std::uint32_t length; |
559 | length = furthestPositionInRecord - sizeof length; |
560 | ok = ok && |
561 | Emit(data: reinterpret_cast<const char *>(&length), bytes: sizeof length, |
562 | elementBytes: sizeof length, handler); |
563 | positionInRecord = 0; |
564 | ok = ok && |
565 | Emit(data: reinterpret_cast<const char *>(&length), bytes: sizeof length, |
566 | elementBytes: sizeof length, handler); |
567 | } else { |
568 | // Unformatted stream: nothing to do |
569 | } |
570 | } else if (handler.GetIoStat() != IostatOk && |
571 | furthestPositionInRecord == 0) { |
572 | // Error in formatted variable length record, and no output yet; do |
573 | // nothing, like most other Fortran compilers do. |
574 | return true; |
575 | } else { |
576 | // Terminate formatted variable length record |
577 | const char *lineEnding{"\n" }; |
578 | std::size_t lineEndingBytes{1}; |
579 | #ifdef _WIN32 |
580 | if (!isWindowsTextFile()) { |
581 | lineEnding = "\r\n" ; |
582 | lineEndingBytes = 2; |
583 | } |
584 | #endif |
585 | ok = ok && Emit(data: lineEnding, bytes: lineEndingBytes, elementBytes: 1, handler); |
586 | } |
587 | leftTabLimit.reset(); |
588 | if (IsAfterEndfile()) { |
589 | return false; |
590 | } |
591 | CommitWrites(); |
592 | ++currentRecordNumber; |
593 | if (access != Access::Direct) { |
594 | impliedEndfile_ = IsRecordFile(); |
595 | if (IsAtEOF()) { |
596 | endfileRecordNumber.reset(); |
597 | } |
598 | } |
599 | return ok; |
600 | } |
601 | } |
602 | |
603 | void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { |
604 | if (access == Access::Direct || !IsRecordFile()) { |
605 | handler.SignalError(IostatBackspaceNonSequential, |
606 | "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream" , |
607 | unitNumber()); |
608 | } else { |
609 | if (IsAfterEndfile()) { |
610 | // BACKSPACE after explicit ENDFILE |
611 | currentRecordNumber = *endfileRecordNumber; |
612 | } else if (leftTabLimit && direction_ == Direction::Input) { |
613 | // BACKSPACE after non-advancing input |
614 | leftTabLimit.reset(); |
615 | } else { |
616 | DoImpliedEndfile(handler); |
617 | if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) { |
618 | --currentRecordNumber; |
619 | if (openRecl && access == Access::Direct) { |
620 | BackspaceFixedRecord(handler); |
621 | } else { |
622 | RUNTIME_CHECK(handler, isUnformatted.has_value()); |
623 | if (isUnformatted.value_or(u: false)) { |
624 | BackspaceVariableUnformattedRecord(handler); |
625 | } else { |
626 | BackspaceVariableFormattedRecord(handler); |
627 | } |
628 | } |
629 | } |
630 | } |
631 | BeginRecord(); |
632 | anyWriteSinceLastPositioning_ = false; |
633 | } |
634 | } |
635 | |
636 | void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) { |
637 | if (!mayPosition()) { |
638 | auto frameAt{FrameAt()}; |
639 | if (frameOffsetInFile_ >= frameAt && |
640 | frameOffsetInFile_ < |
641 | static_cast<std::int64_t>(frameAt + FrameLength())) { |
642 | // A Flush() that's about to happen to a non-positionable file |
643 | // needs to advance frameOffsetInFile_ to prevent attempts at |
644 | // impossible seeks |
645 | CommitWrites(); |
646 | leftTabLimit.reset(); |
647 | } |
648 | } |
649 | Flush(handler); |
650 | } |
651 | |
652 | void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { |
653 | if (isTerminal()) { |
654 | FlushOutput(handler); |
655 | } |
656 | } |
657 | |
658 | void ExternalFileUnit::Endfile(IoErrorHandler &handler) { |
659 | if (access == Access::Direct) { |
660 | handler.SignalError(IostatEndfileDirect, |
661 | "ENDFILE(UNIT=%d) on direct-access file" , unitNumber()); |
662 | } else if (!mayWrite()) { |
663 | handler.SignalError(IostatEndfileUnwritable, |
664 | "ENDFILE(UNIT=%d) on read-only file" , unitNumber()); |
665 | } else if (IsAfterEndfile()) { |
666 | // ENDFILE after ENDFILE |
667 | } else { |
668 | DoEndfile(handler); |
669 | if (IsRecordFile() && access != Access::Direct) { |
670 | // Explicit ENDFILE leaves position *after* the endfile record |
671 | RUNTIME_CHECK(handler, endfileRecordNumber.has_value()); |
672 | currentRecordNumber = *endfileRecordNumber + 1; |
673 | } |
674 | } |
675 | } |
676 | |
677 | void ExternalFileUnit::Rewind(IoErrorHandler &handler) { |
678 | if (access == Access::Direct) { |
679 | handler.SignalError(IostatRewindNonSequential, |
680 | "REWIND(UNIT=%d) on non-sequential file" , unitNumber()); |
681 | } else { |
682 | SetPosition(0, handler); |
683 | currentRecordNumber = 1; |
684 | leftTabLimit.reset(); |
685 | anyWriteSinceLastPositioning_ = false; |
686 | } |
687 | } |
688 | |
689 | void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) { |
690 | DoImpliedEndfile(handler); |
691 | frameOffsetInFile_ = pos; |
692 | recordOffsetInFrame_ = 0; |
693 | if (access == Access::Direct) { |
694 | directAccessRecWasSet_ = true; |
695 | } |
696 | BeginRecord(); |
697 | } |
698 | |
699 | bool ExternalFileUnit::SetStreamPos( |
700 | std::int64_t oneBasedPos, IoErrorHandler &handler) { |
701 | if (access != Access::Stream) { |
702 | handler.SignalError(msg: "POS= may not appear unless ACCESS='STREAM'" ); |
703 | return false; |
704 | } |
705 | if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11) |
706 | handler.SignalError( |
707 | msg: "POS=%zd is invalid" , xs: static_cast<std::intmax_t>(oneBasedPos)); |
708 | return false; |
709 | } |
710 | SetPosition(pos: oneBasedPos - 1, handler); |
711 | // We no longer know which record we're in. Set currentRecordNumber to |
712 | // a large value from whence we can both advance and backspace. |
713 | currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2; |
714 | endfileRecordNumber.reset(); |
715 | return true; |
716 | } |
717 | |
718 | bool ExternalFileUnit::SetDirectRec( |
719 | std::int64_t oneBasedRec, IoErrorHandler &handler) { |
720 | if (access != Access::Direct) { |
721 | handler.SignalError(msg: "REC= may not appear unless ACCESS='DIRECT'" ); |
722 | return false; |
723 | } |
724 | if (!openRecl) { |
725 | handler.SignalError(msg: "RECL= was not specified" ); |
726 | return false; |
727 | } |
728 | if (oneBasedRec < 1) { |
729 | handler.SignalError( |
730 | msg: "REC=%zd is invalid" , xs: static_cast<std::intmax_t>(oneBasedRec)); |
731 | return false; |
732 | } |
733 | currentRecordNumber = oneBasedRec; |
734 | SetPosition(pos: (oneBasedRec - 1) * *openRecl, handler); |
735 | return true; |
736 | } |
737 | |
738 | void ExternalFileUnit::EndIoStatement() { |
739 | io_.reset(); |
740 | u_.emplace<std::monostate>(); |
741 | lock_.Drop(); |
742 | } |
743 | |
744 | void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( |
745 | IoErrorHandler &handler) { |
746 | RUNTIME_CHECK(handler, access == Access::Sequential); |
747 | std::int32_t {0}, {0}; |
748 | std::size_t need{recordOffsetInFrame_ + sizeof header}; |
749 | std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; |
750 | // Try to emit informative errors to help debug corrupted files. |
751 | const char *error{nullptr}; |
752 | if (got < need) { |
753 | if (got == recordOffsetInFrame_) { |
754 | HitEndOnRead(handler); |
755 | } else { |
756 | error = "Unformatted variable-length sequential file input failed at " |
757 | "record #%jd (file offset %jd): truncated record header" ; |
758 | } |
759 | } else { |
760 | header = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_); |
761 | recordLength = sizeof header + header; // does not include footer |
762 | need = recordOffsetInFrame_ + *recordLength + sizeof footer; |
763 | got = ReadFrame(frameOffsetInFile_, need, handler); |
764 | if (got < need) { |
765 | error = "Unformatted variable-length sequential file input failed at " |
766 | "record #%jd (file offset %jd): hit EOF reading record with " |
767 | "length %jd bytes" ; |
768 | } else { |
769 | footer = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_ + *recordLength); |
770 | if (footer != header) { |
771 | error = "Unformatted variable-length sequential file input failed at " |
772 | "record #%jd (file offset %jd): record header has length %jd " |
773 | "that does not match record footer (%jd)" ; |
774 | } |
775 | } |
776 | } |
777 | if (error) { |
778 | handler.SignalError(msg: error, xs: static_cast<std::intmax_t>(currentRecordNumber), |
779 | xs: static_cast<std::intmax_t>(frameOffsetInFile_), |
780 | xs: static_cast<std::intmax_t>(header), xs: static_cast<std::intmax_t>(footer)); |
781 | // TODO: error recovery |
782 | } |
783 | positionInRecord = sizeof header; |
784 | } |
785 | |
786 | void ExternalFileUnit::BeginVariableFormattedInputRecord( |
787 | IoErrorHandler &handler) { |
788 | if (this == defaultInput) { |
789 | if (defaultOutput) { |
790 | defaultOutput->FlushOutput(handler); |
791 | } |
792 | if (errorOutput) { |
793 | errorOutput->FlushOutput(handler); |
794 | } |
795 | } |
796 | std::size_t length{0}; |
797 | do { |
798 | std::size_t need{length + 1}; |
799 | length = |
800 | ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) - |
801 | recordOffsetInFrame_; |
802 | if (length < need) { |
803 | if (length > 0) { |
804 | // final record w/o \n |
805 | recordLength = length; |
806 | unterminatedRecord = true; |
807 | } else { |
808 | HitEndOnRead(handler); |
809 | } |
810 | break; |
811 | } |
812 | } while (!SetVariableFormattedRecordLength()); |
813 | } |
814 | |
815 | void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { |
816 | RUNTIME_CHECK(handler, openRecl.has_value()); |
817 | if (frameOffsetInFile_ < *openRecl) { |
818 | handler.SignalError(IostatBackspaceAtFirstRecord); |
819 | } else { |
820 | frameOffsetInFile_ -= *openRecl; |
821 | } |
822 | } |
823 | |
824 | void ExternalFileUnit::BackspaceVariableUnformattedRecord( |
825 | IoErrorHandler &handler) { |
826 | std::int32_t {0}; |
827 | auto {static_cast<std::int64_t>(sizeof header)}; |
828 | frameOffsetInFile_ += recordOffsetInFrame_; |
829 | recordOffsetInFrame_ = 0; |
830 | if (frameOffsetInFile_ <= headerBytes) { |
831 | handler.SignalError(IostatBackspaceAtFirstRecord); |
832 | return; |
833 | } |
834 | // Error conditions here cause crashes, not file format errors, because the |
835 | // validity of the file structure before the current record will have been |
836 | // checked informatively in NextSequentialVariableUnformattedInputRecord(). |
837 | std::size_t got{ |
838 | ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; |
839 | if (static_cast<std::int64_t>(got) < headerBytes) { |
840 | handler.SignalError(IostatShortRead); |
841 | return; |
842 | } |
843 | recordLength = ReadHeaderOrFooter(frameOffset: 0); |
844 | if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) { |
845 | handler.SignalError(IostatBadUnformattedRecord); |
846 | return; |
847 | } |
848 | frameOffsetInFile_ -= *recordLength + 2 * headerBytes; |
849 | auto need{static_cast<std::size_t>( |
850 | recordOffsetInFrame_ + sizeof header + *recordLength)}; |
851 | got = ReadFrame(frameOffsetInFile_, need, handler); |
852 | if (got < need) { |
853 | handler.SignalError(IostatShortRead); |
854 | return; |
855 | } |
856 | header = ReadHeaderOrFooter(frameOffset: recordOffsetInFrame_); |
857 | if (header != *recordLength) { |
858 | handler.SignalError(IostatBadUnformattedRecord); |
859 | return; |
860 | } |
861 | } |
862 | |
863 | // There's no portable memrchr(), unfortunately, and strrchr() would |
864 | // fail on a record with a NUL, so we have to do it the hard way. |
865 | static const char *FindLastNewline(const char *str, std::size_t length) { |
866 | for (const char *p{str + length}; p >= str; p--) { |
867 | if (*p == '\n') { |
868 | return p; |
869 | } |
870 | } |
871 | return nullptr; |
872 | } |
873 | |
874 | void ExternalFileUnit::BackspaceVariableFormattedRecord( |
875 | IoErrorHandler &handler) { |
876 | // File offset of previous record's newline |
877 | auto prevNL{ |
878 | frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; |
879 | if (prevNL < 0) { |
880 | handler.SignalError(IostatBackspaceAtFirstRecord); |
881 | return; |
882 | } |
883 | while (true) { |
884 | if (frameOffsetInFile_ < prevNL) { |
885 | if (const char *p{ |
886 | FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { |
887 | recordOffsetInFrame_ = p - Frame() + 1; |
888 | recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); |
889 | break; |
890 | } |
891 | } |
892 | if (frameOffsetInFile_ == 0) { |
893 | recordOffsetInFrame_ = 0; |
894 | recordLength = prevNL; |
895 | break; |
896 | } |
897 | frameOffsetInFile_ -= std::min<std::int64_t>(a: frameOffsetInFile_, b: 1024); |
898 | auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; |
899 | auto got{ReadFrame(frameOffsetInFile_, need, handler)}; |
900 | if (got < need) { |
901 | handler.SignalError(IostatShortRead); |
902 | return; |
903 | } |
904 | } |
905 | if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') { |
906 | handler.SignalError(IostatMissingTerminator); |
907 | return; |
908 | } |
909 | if (*recordLength > 0 && |
910 | Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { |
911 | --*recordLength; |
912 | } |
913 | } |
914 | |
915 | void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { |
916 | if (access != Access::Direct) { |
917 | if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) { |
918 | // Flush a partial record after non-advancing output |
919 | impliedEndfile_ = true; |
920 | } |
921 | if (impliedEndfile_ && mayPosition()) { |
922 | DoEndfile(handler); |
923 | } |
924 | } |
925 | impliedEndfile_ = false; |
926 | } |
927 | |
928 | void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { |
929 | if (IsRecordFile() && access != Access::Direct) { |
930 | furthestPositionInRecord = |
931 | std::max(a: positionInRecord, b: furthestPositionInRecord); |
932 | if (leftTabLimit) { // last I/O was non-advancing |
933 | if (access == Access::Sequential && direction_ == Direction::Output) { |
934 | AdvanceRecord(handler); |
935 | } else { // Access::Stream or input |
936 | leftTabLimit.reset(); |
937 | ++currentRecordNumber; |
938 | } |
939 | } |
940 | endfileRecordNumber = currentRecordNumber; |
941 | } |
942 | frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; |
943 | recordOffsetInFrame_ = 0; |
944 | FlushOutput(handler); |
945 | Truncate(frameOffsetInFile_, handler); |
946 | TruncateFrame(frameOffsetInFile_, handler); |
947 | BeginRecord(); |
948 | impliedEndfile_ = false; |
949 | anyWriteSinceLastPositioning_ = false; |
950 | } |
951 | |
952 | void ExternalFileUnit::CommitWrites() { |
953 | frameOffsetInFile_ += |
954 | recordOffsetInFrame_ + recordLength.value_or(u&: furthestPositionInRecord); |
955 | recordOffsetInFrame_ = 0; |
956 | BeginRecord(); |
957 | } |
958 | |
959 | bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) { |
960 | if (access == Access::Direct) { |
961 | RUNTIME_CHECK(handler, openRecl); |
962 | if (!directAccessRecWasSet_) { |
963 | handler.SignalError( |
964 | msg: "No REC= was specified for a data transfer with ACCESS='DIRECT'" ); |
965 | return false; |
966 | } |
967 | } |
968 | return true; |
969 | } |
970 | |
971 | void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) { |
972 | handler.SignalEnd(); |
973 | if (IsRecordFile() && access != Access::Direct) { |
974 | endfileRecordNumber = currentRecordNumber; |
975 | } |
976 | } |
977 | |
978 | ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) { |
979 | OwningPtr<ChildIo> current{std::move(child_)}; |
980 | Terminator &terminator{parent.GetIoErrorHandler()}; |
981 | OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))}; |
982 | child_.reset(next.release()); |
983 | return *child_; |
984 | } |
985 | |
986 | void ExternalFileUnit::PopChildIo(ChildIo &child) { |
987 | if (child_.get() != &child) { |
988 | child.parent().GetIoErrorHandler().Crash( |
989 | "ChildIo being popped is not top of stack" ); |
990 | } |
991 | child_.reset(child.AcquirePrevious().release()); // deletes top child |
992 | } |
993 | |
994 | int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) { |
995 | if (!mayAsynchronous()) { |
996 | handler.SignalError(IostatBadAsynchronous); |
997 | return -1; |
998 | } else if (auto least{asyncIdAvailable_.LeastElement()}) { |
999 | asyncIdAvailable_.reset(*least); |
1000 | return static_cast<int>(*least); |
1001 | } else { |
1002 | handler.SignalError(IostatTooManyAsyncOps); |
1003 | return -1; |
1004 | } |
1005 | } |
1006 | |
1007 | bool ExternalFileUnit::Wait(int id) { |
1008 | if (static_cast<std::size_t>(id) >= asyncIdAvailable_.size() || |
1009 | asyncIdAvailable_.test(id)) { |
1010 | return false; |
1011 | } else { |
1012 | if (id == 0) { // means "all IDs" |
1013 | asyncIdAvailable_.set(); |
1014 | asyncIdAvailable_.reset(0); |
1015 | } else { |
1016 | asyncIdAvailable_.set(id); |
1017 | } |
1018 | return true; |
1019 | } |
1020 | } |
1021 | |
1022 | std::int32_t ExternalFileUnit::(std::int64_t frameOffset) { |
1023 | std::int32_t word; |
1024 | char *wordPtr{reinterpret_cast<char *>(&word)}; |
1025 | std::memcpy(dest: wordPtr, src: Frame() + frameOffset, n: sizeof word); |
1026 | if (swapEndianness_) { |
1027 | SwapEndianness(data: wordPtr, bytes: sizeof word, elementBytes: sizeof word); |
1028 | } |
1029 | return word; |
1030 | } |
1031 | |
1032 | void ChildIo::EndIoStatement() { |
1033 | io_.reset(); |
1034 | u_.emplace<std::monostate>(); |
1035 | } |
1036 | |
1037 | Iostat ChildIo::CheckFormattingAndDirection( |
1038 | bool unformatted, Direction direction) { |
1039 | bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()}; |
1040 | bool parentIsFormatted{parentIsInput |
1041 | ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() != |
1042 | nullptr |
1043 | : parent_.get_if<FormattedIoStatementState<Direction::Output>>() != |
1044 | nullptr}; |
1045 | bool parentIsUnformatted{!parentIsFormatted}; |
1046 | if (unformatted != parentIsUnformatted) { |
1047 | return unformatted ? IostatUnformattedChildOnFormattedParent |
1048 | : IostatFormattedChildOnUnformattedParent; |
1049 | } else if (parentIsInput != (direction == Direction::Input)) { |
1050 | return parentIsInput ? IostatChildOutputToInputParent |
1051 | : IostatChildInputFromOutputParent; |
1052 | } else { |
1053 | return IostatOk; |
1054 | } |
1055 | } |
1056 | |
1057 | } // namespace Fortran::runtime::io |
1058 | |