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