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