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