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