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 } 644 } 645 646 void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) { 647 DoImpliedEndfile(handler); 648 frameOffsetInFile_ = pos; 649 recordOffsetInFrame_ = 0; 650 if (access == Access::Direct) { 651 directAccessRecWasSet_ = true; 652 } 653 BeginRecord(); 654 } 655 656 void ExternalFileUnit::EndIoStatement() { 657 io_.reset(); 658 u_.emplace<std::monostate>(); 659 lock_.Drop(); 660 } 661 662 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 663 IoErrorHandler &handler) { 664 std::int32_t header{0}, footer{0}; 665 std::size_t need{recordOffsetInFrame_ + sizeof header}; 666 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 667 // Try to emit informative errors to help debug corrupted files. 668 const char *error{nullptr}; 669 if (got < need) { 670 if (got == recordOffsetInFrame_) { 671 HitEndOnRead(handler); 672 } else { 673 error = "Unformatted variable-length sequential file input failed at " 674 "record #%jd (file offset %jd): truncated record header"; 675 } 676 } else { 677 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 678 recordLength = sizeof header + header; // does not include footer 679 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 680 got = ReadFrame(frameOffsetInFile_, need, handler); 681 if (got < need) { 682 error = "Unformatted variable-length sequential file input failed at " 683 "record #%jd (file offset %jd): hit EOF reading record with " 684 "length %jd bytes"; 685 } else { 686 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, 687 sizeof footer); 688 if (footer != header) { 689 error = "Unformatted variable-length sequential file input failed at " 690 "record #%jd (file offset %jd): record header has length %jd " 691 "that does not match record footer (%jd)"; 692 } 693 } 694 } 695 if (error) { 696 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 697 static_cast<std::intmax_t>(frameOffsetInFile_), 698 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 699 // TODO: error recovery 700 } 701 positionInRecord = sizeof header; 702 } 703 704 void ExternalFileUnit::BeginVariableFormattedInputRecord( 705 IoErrorHandler &handler) { 706 if (this == defaultInput) { 707 if (defaultOutput) { 708 defaultOutput->FlushOutput(handler); 709 } 710 if (errorOutput) { 711 errorOutput->FlushOutput(handler); 712 } 713 } 714 std::size_t length{0}; 715 do { 716 std::size_t need{length + 1}; 717 length = 718 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) - 719 recordOffsetInFrame_; 720 if (length < need) { 721 if (length > 0) { 722 // final record w/o \n 723 recordLength = length; 724 unterminatedRecord = true; 725 } else { 726 HitEndOnRead(handler); 727 } 728 break; 729 } 730 } while (!SetVariableFormattedRecordLength()); 731 } 732 733 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 734 RUNTIME_CHECK(handler, openRecl.has_value()); 735 if (frameOffsetInFile_ < *openRecl) { 736 handler.SignalError(IostatBackspaceAtFirstRecord); 737 } else { 738 frameOffsetInFile_ -= *openRecl; 739 } 740 } 741 742 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 743 IoErrorHandler &handler) { 744 std::int32_t header{0}, footer{0}; 745 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 746 frameOffsetInFile_ += recordOffsetInFrame_; 747 recordOffsetInFrame_ = 0; 748 if (frameOffsetInFile_ <= headerBytes) { 749 handler.SignalError(IostatBackspaceAtFirstRecord); 750 return; 751 } 752 // Error conditions here cause crashes, not file format errors, because the 753 // validity of the file structure before the current record will have been 754 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 755 std::size_t got{ 756 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 757 if (static_cast<std::int64_t>(got) < headerBytes) { 758 handler.SignalError(IostatShortRead); 759 return; 760 } 761 std::memcpy(&footer, Frame(), sizeof footer); 762 recordLength = footer; 763 if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) { 764 handler.SignalError(IostatBadUnformattedRecord); 765 return; 766 } 767 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 768 if (frameOffsetInFile_ >= headerBytes) { 769 frameOffsetInFile_ -= headerBytes; 770 recordOffsetInFrame_ = headerBytes; 771 } 772 auto need{static_cast<std::size_t>( 773 recordOffsetInFrame_ + sizeof header + *recordLength)}; 774 got = ReadFrame(frameOffsetInFile_, need, handler); 775 if (got < need) { 776 handler.SignalError(IostatShortRead); 777 return; 778 } 779 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 780 if (header != *recordLength) { 781 handler.SignalError(IostatBadUnformattedRecord); 782 return; 783 } 784 } 785 786 // There's no portable memrchr(), unfortunately, and strrchr() would 787 // fail on a record with a NUL, so we have to do it the hard way. 788 static const char *FindLastNewline(const char *str, std::size_t length) { 789 for (const char *p{str + length}; p-- > str;) { 790 if (*p == '\n') { 791 return p; 792 } 793 } 794 return nullptr; 795 } 796 797 void ExternalFileUnit::BackspaceVariableFormattedRecord( 798 IoErrorHandler &handler) { 799 // File offset of previous record's newline 800 auto prevNL{ 801 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 802 if (prevNL < 0) { 803 handler.SignalError(IostatBackspaceAtFirstRecord); 804 return; 805 } 806 while (true) { 807 if (frameOffsetInFile_ < prevNL) { 808 if (const char *p{ 809 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 810 recordOffsetInFrame_ = p - Frame() + 1; 811 recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 812 break; 813 } 814 } 815 if (frameOffsetInFile_ == 0) { 816 recordOffsetInFrame_ = 0; 817 recordLength = prevNL; 818 break; 819 } 820 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 821 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 822 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 823 if (got < need) { 824 handler.SignalError(IostatShortRead); 825 return; 826 } 827 } 828 if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') { 829 handler.SignalError(IostatMissingTerminator); 830 return; 831 } 832 if (*recordLength > 0 && 833 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 834 --*recordLength; 835 } 836 } 837 838 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 839 if (impliedEndfile_) { 840 impliedEndfile_ = false; 841 if (access != Access::Direct && IsRecordFile() && mayPosition()) { 842 DoEndfile(handler); 843 } 844 } 845 } 846 847 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 848 if (IsRecordFile() && access != Access::Direct) { 849 if (furthestPositionInRecord > 0) { 850 // Last write was non-advancing, so AdvanceRecord() was not called. 851 leftTabLimit.reset(); 852 ++currentRecordNumber; 853 } 854 endfileRecordNumber = currentRecordNumber; 855 } 856 FlushOutput(handler); 857 Truncate(frameOffsetInFile_ + recordOffsetInFrame_ + furthestPositionInRecord, 858 handler); 859 BeginRecord(); 860 impliedEndfile_ = false; 861 } 862 863 void ExternalFileUnit::CommitWrites() { 864 frameOffsetInFile_ += 865 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 866 recordOffsetInFrame_ = 0; 867 BeginRecord(); 868 } 869 870 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) { 871 if (access == Access::Direct) { 872 RUNTIME_CHECK(handler, openRecl); 873 if (!directAccessRecWasSet_) { 874 handler.SignalError( 875 "No REC= was specified for a data transfer with ACCESS='DIRECT'"); 876 return false; 877 } 878 } 879 return true; 880 } 881 882 void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) { 883 handler.SignalEnd(); 884 if (IsRecordFile() && access != Access::Direct) { 885 endfileRecordNumber = currentRecordNumber; 886 } 887 } 888 889 ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) { 890 OwningPtr<ChildIo> current{std::move(child_)}; 891 Terminator &terminator{parent.GetIoErrorHandler()}; 892 OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))}; 893 child_.reset(next.release()); 894 return *child_; 895 } 896 897 void ExternalFileUnit::PopChildIo(ChildIo &child) { 898 if (child_.get() != &child) { 899 child.parent().GetIoErrorHandler().Crash( 900 "ChildIo being popped is not top of stack"); 901 } 902 child_.reset(child.AcquirePrevious().release()); // deletes top child 903 } 904 905 int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) { 906 if (!mayAsynchronous()) { 907 handler.SignalError(IostatBadAsynchronous); 908 return -1; 909 } else if (auto least{asyncIdAvailable_.LeastElement()}) { 910 asyncIdAvailable_.reset(*least); 911 return static_cast<int>(*least); 912 } else { 913 handler.SignalError(IostatTooManyAsyncOps); 914 return -1; 915 } 916 } 917 918 bool ExternalFileUnit::Wait(int id) { 919 if (id < 0 || asyncIdAvailable_.test(id)) { 920 return false; 921 } else { 922 if (id == 0) { 923 asyncIdAvailable_.set(); 924 } else { 925 asyncIdAvailable_.set(id); 926 } 927 return true; 928 } 929 } 930 931 void ChildIo::EndIoStatement() { 932 io_.reset(); 933 u_.emplace<std::monostate>(); 934 } 935 936 Iostat ChildIo::CheckFormattingAndDirection( 937 bool unformatted, Direction direction) { 938 bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()}; 939 bool parentIsFormatted{parentIsInput 940 ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() != 941 nullptr 942 : parent_.get_if<FormattedIoStatementState<Direction::Output>>() != 943 nullptr}; 944 bool parentIsUnformatted{!parentIsFormatted}; 945 if (unformatted != parentIsUnformatted) { 946 return unformatted ? IostatUnformattedChildOnFormattedParent 947 : IostatFormattedChildOnUnformattedParent; 948 } else if (parentIsInput != (direction == Direction::Input)) { 949 return parentIsInput ? IostatChildOutputToInputParent 950 : IostatChildInputFromOutputParent; 951 } else { 952 return IostatOk; 953 } 954 } 955 956 } // namespace Fortran::runtime::io 957