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