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