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 "environment.h" 11 #include "io-error.h" 12 #include "lock.h" 13 #include "unit-map.h" 14 #include <cstdio> 15 #include <limits> 16 #include <utility> 17 18 namespace Fortran::runtime::io { 19 20 // The per-unit data structures are created on demand so that Fortran I/O 21 // should work without a Fortran main program. 22 static Lock unitMapLock; 23 static UnitMap *unitMap{nullptr}; 24 static ExternalFileUnit *defaultInput{nullptr}; // unit 5 25 static ExternalFileUnit *defaultOutput{nullptr}; // unit 6 26 static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension 27 28 void FlushOutputOnCrash(const Terminator &terminator) { 29 if (!defaultOutput && !errorOutput) { 30 return; 31 } 32 IoErrorHandler handler{terminator}; 33 handler.HasIoStat(); // prevent nested crash if flush has error 34 CriticalSection critical{unitMapLock}; 35 if (defaultOutput) { 36 defaultOutput->FlushOutput(handler); 37 } 38 if (errorOutput) { 39 errorOutput->FlushOutput(handler); 40 } 41 } 42 43 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { 44 return GetUnitMap().LookUp(unit); 45 } 46 47 ExternalFileUnit &ExternalFileUnit::LookUpOrCrash( 48 int unit, const Terminator &terminator) { 49 ExternalFileUnit *file{LookUp(unit)}; 50 if (!file) { 51 terminator.Crash("Not an open I/O unit number: %d", unit); 52 } 53 return *file; 54 } 55 56 ExternalFileUnit &ExternalFileUnit::LookUpOrCreate( 57 int unit, const Terminator &terminator, bool &wasExtant) { 58 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); 59 } 60 61 ExternalFileUnit &ExternalFileUnit::LookUpOrCreateAnonymous(int unit, 62 Direction dir, std::optional<bool> isUnformatted, 63 const Terminator &terminator) { 64 bool exists{false}; 65 ExternalFileUnit &result{ 66 GetUnitMap().LookUpOrCreate(unit, terminator, exists)}; 67 if (!exists) { 68 IoErrorHandler handler{terminator}; 69 result.OpenAnonymousUnit( 70 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace, 71 Action::ReadWrite, Position::Rewind, Convert::Native, handler); 72 result.isUnformatted = isUnformatted; 73 } 74 return result; 75 } 76 77 ExternalFileUnit *ExternalFileUnit::LookUp(const char *path) { 78 return GetUnitMap().LookUp(path); 79 } 80 81 ExternalFileUnit &ExternalFileUnit::CreateNew( 82 int unit, const Terminator &terminator) { 83 bool wasExtant{false}; 84 ExternalFileUnit &result{ 85 GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)}; 86 RUNTIME_CHECK(terminator, !wasExtant); 87 return result; 88 } 89 90 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { 91 return GetUnitMap().LookUpForClose(unit); 92 } 93 94 ExternalFileUnit &ExternalFileUnit::NewUnit( 95 const Terminator &terminator, bool forChildIo) { 96 ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)}; 97 unit.createdForInternalChildIo_ = forChildIo; 98 return unit; 99 } 100 101 void ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status, 102 std::optional<Action> action, Position position, OwningPtr<char> &&newPath, 103 std::size_t newPathLength, Convert convert, IoErrorHandler &handler) { 104 if (executionEnvironment.conversion != Convert::Unknown) { 105 convert = executionEnvironment.conversion; 106 } 107 swapEndianness_ = convert == Convert::Swap || 108 (convert == Convert::LittleEndian && !isHostLittleEndian) || 109 (convert == Convert::BigEndian && isHostLittleEndian); 110 if (IsConnected()) { 111 bool isSamePath{newPath.get() && path() && pathLength() == newPathLength && 112 std::memcmp(path(), newPath.get(), newPathLength) == 0}; 113 if (status && *status != OpenStatus::Old && isSamePath) { 114 handler.SignalError("OPEN statement for connected unit may not have " 115 "explicit STATUS= other than 'OLD'"); 116 return; 117 } 118 if (!newPath.get() || isSamePath) { 119 // OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE= 120 newPath.reset(); 121 return; 122 } 123 // Otherwise, OPEN on open unit with new FILE= implies CLOSE 124 DoImpliedEndfile(handler); 125 FlushOutput(handler); 126 Close(CloseStatus::Keep, handler); 127 } 128 set_path(std::move(newPath), newPathLength); 129 Open(status.value_or(OpenStatus::Unknown), action, position, handler); 130 auto totalBytes{knownSize()}; 131 if (access == Access::Direct) { 132 if (!openRecl) { 133 handler.SignalError(IostatOpenBadRecl, 134 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known", 135 unitNumber()); 136 } else if (*openRecl <= 0) { 137 handler.SignalError(IostatOpenBadRecl, 138 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid", 139 unitNumber(), static_cast<std::intmax_t>(*openRecl)); 140 } else if (totalBytes && (*totalBytes % *openRecl != 0)) { 141 handler.SignalError(IostatOpenBadAppend, 142 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " 143 "even divisor of the file size %jd", 144 unitNumber(), static_cast<std::intmax_t>(*openRecl), 145 static_cast<std::intmax_t>(*totalBytes)); 146 } 147 recordLength = openRecl; 148 } 149 endfileRecordNumber.reset(); 150 currentRecordNumber = 1; 151 if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) { 152 endfileRecordNumber = 1 + (*totalBytes / *openRecl); 153 } 154 if (position == Position::Append) { 155 if (!endfileRecordNumber) { 156 // Fake it so that we can backspace relative from the end 157 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2; 158 } 159 currentRecordNumber = *endfileRecordNumber; 160 } 161 } 162 163 void ExternalFileUnit::OpenAnonymousUnit(std::optional<OpenStatus> status, 164 std::optional<Action> action, Position position, Convert convert, 165 IoErrorHandler &handler) { 166 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7 167 std::size_t pathMaxLen{32}; 168 auto path{SizedNew<char>{handler}(pathMaxLen)}; 169 std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_); 170 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()), 171 convert, handler); 172 } 173 174 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { 175 DoImpliedEndfile(handler); 176 FlushOutput(handler); 177 Close(status, handler); 178 } 179 180 void ExternalFileUnit::DestroyClosed() { 181 GetUnitMap().DestroyClosed(*this); // destroys *this 182 } 183 184 bool ExternalFileUnit::SetDirection( 185 Direction direction, IoErrorHandler &handler) { 186 if (direction == Direction::Input) { 187 if (mayRead()) { 188 direction_ = Direction::Input; 189 return true; 190 } else { 191 handler.SignalError(IostatReadFromWriteOnly, 192 "READ(UNIT=%d) with ACTION='WRITE'", unitNumber()); 193 return false; 194 } 195 } else { 196 if (mayWrite()) { 197 direction_ = Direction::Output; 198 return true; 199 } else { 200 handler.SignalError(IostatWriteToReadOnly, 201 "WRITE(UNIT=%d) with ACTION='READ'", unitNumber()); 202 return false; 203 } 204 } 205 } 206 207 UnitMap &ExternalFileUnit::GetUnitMap() { 208 if (unitMap) { 209 return *unitMap; 210 } 211 CriticalSection critical{unitMapLock}; 212 if (unitMap) { 213 return *unitMap; 214 } 215 Terminator terminator{__FILE__, __LINE__}; 216 IoErrorHandler handler{terminator}; 217 UnitMap *newUnitMap{New<UnitMap>{terminator}().release()}; 218 219 bool wasExtant{false}; 220 ExternalFileUnit &out{newUnitMap->LookUpOrCreate(6, terminator, wasExtant)}; 221 RUNTIME_CHECK(terminator, !wasExtant); 222 out.Predefine(1); 223 out.SetDirection(Direction::Output, handler); 224 out.isUnformatted = false; 225 defaultOutput = &out; 226 227 ExternalFileUnit &in{newUnitMap->LookUpOrCreate(5, terminator, wasExtant)}; 228 RUNTIME_CHECK(terminator, !wasExtant); 229 in.Predefine(0); 230 in.SetDirection(Direction::Input, handler); 231 in.isUnformatted = false; 232 defaultInput = ∈ 233 234 ExternalFileUnit &error{newUnitMap->LookUpOrCreate(0, terminator, wasExtant)}; 235 RUNTIME_CHECK(terminator, !wasExtant); 236 error.Predefine(2); 237 error.SetDirection(Direction::Output, handler); 238 error.isUnformatted = false; 239 errorOutput = &error; 240 241 // TODO: Set UTF-8 mode from the environment 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 extra = 1; // newline 292 } 293 } 294 if (furthestAfter > extra + *openRecl) { 295 handler.SignalError(IostatRecordWriteOverrun, 296 "Attempt to write %zd bytes to position %jd in a fixed-size record " 297 "of %jd bytes", 298 bytes, static_cast<std::intmax_t>(positionInRecord - header), 299 static_cast<std::intmax_t>(*openRecl)); 300 return false; 301 } 302 } else if (recordLength) { 303 // It is possible for recordLength to have a value now for a 304 // variable-length output record if the previous operation 305 // was a BACKSPACE or non advancing input statement. 306 recordLength.reset(); 307 beganReadingRecord_ = false; 308 } 309 if (IsAfterEndfile()) { 310 handler.SignalError(IostatWriteAfterEndfile); 311 return false; 312 } 313 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); 314 if (positionInRecord > furthestPositionInRecord) { 315 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', 316 positionInRecord - furthestPositionInRecord); 317 } 318 char *to{Frame() + recordOffsetInFrame_ + positionInRecord}; 319 std::memcpy(to, data, bytes); 320 if (swapEndianness_) { 321 SwapEndianness(to, bytes, elementBytes); 322 } 323 positionInRecord += bytes; 324 furthestPositionInRecord = furthestAfter; 325 return true; 326 } 327 328 bool ExternalFileUnit::Receive(char *data, std::size_t bytes, 329 std::size_t elementBytes, IoErrorHandler &handler) { 330 RUNTIME_CHECK(handler, direction_ == Direction::Input); 331 auto furthestAfter{std::max(furthestPositionInRecord, 332 positionInRecord + static_cast<std::int64_t>(bytes))}; 333 if (furthestAfter > recordLength.value_or(furthestAfter)) { 334 handler.SignalError(IostatRecordReadOverrun, 335 "Attempt to read %zd bytes at position %jd in a record of %jd bytes", 336 bytes, static_cast<std::intmax_t>(positionInRecord), 337 static_cast<std::intmax_t>(*recordLength)); 338 return false; 339 } 340 auto need{recordOffsetInFrame_ + furthestAfter}; 341 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 342 if (got >= need) { 343 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes); 344 if (swapEndianness_) { 345 SwapEndianness(data, bytes, elementBytes); 346 } 347 positionInRecord += bytes; 348 furthestPositionInRecord = furthestAfter; 349 return true; 350 } else { 351 // EOF or error: can be handled & has been signaled 352 endfileRecordNumber = currentRecordNumber; 353 return false; 354 } 355 } 356 357 std::size_t ExternalFileUnit::GetNextInputBytes( 358 const char *&p, IoErrorHandler &handler) { 359 RUNTIME_CHECK(handler, direction_ == Direction::Input); 360 p = FrameNextInput(handler, 1); 361 return p ? EffectiveRecordLength().value_or(positionInRecord + 1) - 362 positionInRecord 363 : 0; 364 } 365 366 std::optional<char32_t> ExternalFileUnit::GetCurrentChar( 367 IoErrorHandler &handler) { 368 const char *p{nullptr}; 369 std::size_t bytes{GetNextInputBytes(p, handler)}; 370 if (bytes == 0) { 371 return std::nullopt; 372 } else { 373 // TODO: UTF-8 decoding; may have to get more bytes in a loop 374 return *p; 375 } 376 } 377 378 const char *ExternalFileUnit::FrameNextInput( 379 IoErrorHandler &handler, std::size_t bytes) { 380 RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted); 381 if (static_cast<std::int64_t>(positionInRecord + bytes) <= 382 recordLength.value_or(positionInRecord + bytes)) { 383 auto at{recordOffsetInFrame_ + positionInRecord}; 384 auto need{static_cast<std::size_t>(at + bytes)}; 385 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 386 SetSequentialVariableFormattedRecordLength(); 387 if (got >= need) { 388 return Frame() + at; 389 } 390 handler.SignalEnd(); 391 endfileRecordNumber = currentRecordNumber; 392 } 393 return nullptr; 394 } 395 396 bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() { 397 if (recordLength || access != Access::Sequential) { 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 void ExternalFileUnit::SetLeftTabLimit() { 415 leftTabLimit = furthestPositionInRecord; 416 positionInRecord = furthestPositionInRecord; 417 } 418 419 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { 420 RUNTIME_CHECK(handler, direction_ == Direction::Input); 421 if (!beganReadingRecord_) { 422 beganReadingRecord_ = true; 423 if (access == Access::Direct) { 424 RUNTIME_CHECK(handler, openRecl); 425 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)}; 426 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 427 if (got >= need) { 428 recordLength = openRecl; 429 } else { 430 recordLength.reset(); 431 handler.SignalEnd(); 432 } 433 } else if (access == Access::Sequential) { 434 recordLength.reset(); 435 if (IsAtEOF()) { 436 handler.SignalEnd(); 437 } else { 438 RUNTIME_CHECK(handler, isUnformatted.has_value()); 439 if (isUnformatted.value_or(false)) { 440 BeginSequentialVariableUnformattedInputRecord(handler); 441 } else { // formatted 442 BeginSequentialVariableFormattedInputRecord(handler); 443 } 444 } 445 } 446 } 447 RUNTIME_CHECK(handler, 448 recordLength.has_value() || !IsRecordFile(access) || handler.InError()); 449 return !handler.InError(); 450 } 451 452 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) { 453 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); 454 beganReadingRecord_ = false; 455 if (handler.InError() && handler.GetIoStat() != IostatEor) { 456 // avoid bogus crashes in END/ERR circumstances 457 } else if (access == Access::Sequential) { 458 RUNTIME_CHECK(handler, recordLength.has_value()); 459 recordOffsetInFrame_ += *recordLength; 460 if (openRecl && access == Access::Direct) { 461 frameOffsetInFile_ += recordOffsetInFrame_; 462 recordOffsetInFrame_ = 0; 463 } else { 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 } 486 ++currentRecordNumber; 487 BeginRecord(); 488 } 489 490 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 491 if (direction_ == Direction::Input) { 492 FinishReadingRecord(handler); 493 return BeginReadingRecord(handler); 494 } else { // Direction::Output 495 bool ok{true}; 496 RUNTIME_CHECK(handler, isUnformatted.has_value()); 497 if (openRecl && access == Access::Direct) { 498 if (furthestPositionInRecord < *openRecl) { 499 // Pad remainder of fixed length record 500 WriteFrame( 501 frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler); 502 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 503 isUnformatted.value_or(false) ? 0 : ' ', 504 *openRecl - furthestPositionInRecord); 505 furthestPositionInRecord = *openRecl; 506 } 507 } else { 508 positionInRecord = furthestPositionInRecord; 509 if (isUnformatted.value_or(false)) { 510 // Append the length of a sequential unformatted variable-length record 511 // as its footer, then overwrite the reserved first four bytes of the 512 // record with its length as its header. These four bytes were skipped 513 // over in BeginUnformattedIO<Output>(). 514 // TODO: Break very large records up into subrecords with negative 515 // headers &/or footers 516 std::uint32_t length; 517 length = furthestPositionInRecord - sizeof length; 518 ok = ok && 519 Emit(reinterpret_cast<const char *>(&length), sizeof length, 520 sizeof length, handler); 521 positionInRecord = 0; 522 ok = ok && 523 Emit(reinterpret_cast<const char *>(&length), sizeof length, 524 sizeof length, handler); 525 } else { 526 // Terminate formatted variable length record 527 ok = ok && Emit("\n", 1, 1, handler); // TODO: Windows CR+LF 528 } 529 } 530 if (IsAfterEndfile()) { 531 return false; 532 } 533 CommitWrites(); 534 impliedEndfile_ = true; 535 ++currentRecordNumber; 536 if (IsAtEOF()) { 537 endfileRecordNumber.reset(); 538 } 539 return ok; 540 } 541 } 542 543 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 544 if (access != Access::Sequential) { 545 handler.SignalError(IostatBackspaceNonSequential, 546 "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); 547 } else { 548 if (IsAfterEndfile()) { 549 // BACKSPACE after explicit ENDFILE 550 currentRecordNumber = *endfileRecordNumber; 551 } else { 552 DoImpliedEndfile(handler); 553 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) { 554 --currentRecordNumber; 555 if (openRecl && access == Access::Direct) { 556 BackspaceFixedRecord(handler); 557 } else { 558 RUNTIME_CHECK(handler, isUnformatted.has_value()); 559 if (isUnformatted.value_or(false)) { 560 BackspaceVariableUnformattedRecord(handler); 561 } else { 562 BackspaceVariableFormattedRecord(handler); 563 } 564 } 565 } 566 } 567 BeginRecord(); 568 } 569 } 570 571 void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) { 572 if (!mayPosition()) { 573 auto frameAt{FrameAt()}; 574 if (frameOffsetInFile_ >= frameAt && 575 frameOffsetInFile_ < 576 static_cast<std::int64_t>(frameAt + FrameLength())) { 577 // A Flush() that's about to happen to a non-positionable file 578 // needs to advance frameOffsetInFile_ to prevent attempts at 579 // impossible seeks 580 CommitWrites(); 581 } 582 } 583 Flush(handler); 584 } 585 586 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 587 if (isTerminal()) { 588 FlushOutput(handler); 589 } 590 } 591 592 void ExternalFileUnit::Endfile(IoErrorHandler &handler) { 593 if (access != Access::Sequential) { 594 handler.SignalError(IostatEndfileNonSequential, 595 "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); 596 } else if (!mayWrite()) { 597 handler.SignalError(IostatEndfileUnwritable, 598 "ENDFILE(UNIT=%d) on read-only file", unitNumber()); 599 } else if (IsAfterEndfile()) { 600 // ENDFILE after ENDFILE 601 } else { 602 DoEndfile(handler); 603 // Explicit ENDFILE leaves position *after* the endfile record 604 RUNTIME_CHECK(handler, endfileRecordNumber.has_value()); 605 currentRecordNumber = *endfileRecordNumber + 1; 606 } 607 } 608 609 void ExternalFileUnit::Rewind(IoErrorHandler &handler) { 610 if (access == Access::Direct) { 611 handler.SignalError(IostatRewindNonSequential, 612 "REWIND(UNIT=%d) on non-sequential file", unitNumber()); 613 } else { 614 DoImpliedEndfile(handler); 615 SetPosition(0); 616 currentRecordNumber = 1; 617 } 618 } 619 620 void ExternalFileUnit::EndIoStatement() { 621 io_.reset(); 622 u_.emplace<std::monostate>(); 623 lock_.Drop(); 624 } 625 626 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 627 IoErrorHandler &handler) { 628 std::int32_t header{0}, footer{0}; 629 std::size_t need{recordOffsetInFrame_ + sizeof header}; 630 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 631 // Try to emit informative errors to help debug corrupted files. 632 const char *error{nullptr}; 633 if (got < need) { 634 if (got == recordOffsetInFrame_) { 635 handler.SignalEnd(); 636 } else { 637 error = "Unformatted variable-length sequential file input failed at " 638 "record #%jd (file offset %jd): truncated record header"; 639 } 640 } else { 641 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 642 recordLength = sizeof header + header; // does not include footer 643 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 644 got = ReadFrame(frameOffsetInFile_, need, handler); 645 if (got < need) { 646 error = "Unformatted variable-length sequential file input failed at " 647 "record #%jd (file offset %jd): hit EOF reading record with " 648 "length %jd bytes"; 649 } else { 650 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, 651 sizeof footer); 652 if (footer != header) { 653 error = "Unformatted variable-length sequential file input failed at " 654 "record #%jd (file offset %jd): record header has length %jd " 655 "that does not match record footer (%jd)"; 656 } 657 } 658 } 659 if (error) { 660 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 661 static_cast<std::intmax_t>(frameOffsetInFile_), 662 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 663 // TODO: error recovery 664 } 665 positionInRecord = sizeof header; 666 } 667 668 void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( 669 IoErrorHandler &handler) { 670 if (this == defaultInput) { 671 if (defaultOutput) { 672 defaultOutput->FlushOutput(handler); 673 } 674 if (errorOutput) { 675 errorOutput->FlushOutput(handler); 676 } 677 } 678 std::size_t length{0}; 679 do { 680 std::size_t need{length + 1}; 681 length = 682 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) - 683 recordOffsetInFrame_; 684 if (length < need) { 685 if (length > 0) { 686 // final record w/o \n 687 recordLength = length; 688 } else { 689 handler.SignalEnd(); 690 } 691 break; 692 } 693 } while (!SetSequentialVariableFormattedRecordLength()); 694 } 695 696 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 697 RUNTIME_CHECK(handler, openRecl.has_value()); 698 if (frameOffsetInFile_ < *openRecl) { 699 handler.SignalError(IostatBackspaceAtFirstRecord); 700 } else { 701 frameOffsetInFile_ -= *openRecl; 702 } 703 } 704 705 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 706 IoErrorHandler &handler) { 707 std::int32_t header{0}, footer{0}; 708 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 709 frameOffsetInFile_ += recordOffsetInFrame_; 710 recordOffsetInFrame_ = 0; 711 if (frameOffsetInFile_ <= headerBytes) { 712 handler.SignalError(IostatBackspaceAtFirstRecord); 713 return; 714 } 715 // Error conditions here cause crashes, not file format errors, because the 716 // validity of the file structure before the current record will have been 717 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 718 std::size_t got{ 719 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 720 RUNTIME_CHECK(handler, got >= sizeof footer); 721 std::memcpy(&footer, Frame(), sizeof footer); 722 recordLength = footer; 723 RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes); 724 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 725 if (frameOffsetInFile_ >= headerBytes) { 726 frameOffsetInFile_ -= headerBytes; 727 recordOffsetInFrame_ = headerBytes; 728 } 729 auto need{static_cast<std::size_t>( 730 recordOffsetInFrame_ + sizeof header + *recordLength)}; 731 got = ReadFrame(frameOffsetInFile_, need, handler); 732 RUNTIME_CHECK(handler, got >= need); 733 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 734 RUNTIME_CHECK(handler, header == *recordLength); 735 } 736 737 // There's no portable memrchr(), unfortunately, and strrchr() would 738 // fail on a record with a NUL, so we have to do it the hard way. 739 static const char *FindLastNewline(const char *str, std::size_t length) { 740 for (const char *p{str + length}; p-- > str;) { 741 if (*p == '\n') { 742 return p; 743 } 744 } 745 return nullptr; 746 } 747 748 void ExternalFileUnit::BackspaceVariableFormattedRecord( 749 IoErrorHandler &handler) { 750 // File offset of previous record's newline 751 auto prevNL{ 752 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 753 if (prevNL < 0) { 754 handler.SignalError(IostatBackspaceAtFirstRecord); 755 return; 756 } 757 while (true) { 758 if (frameOffsetInFile_ < prevNL) { 759 if (const char *p{ 760 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 761 recordOffsetInFrame_ = p - Frame() + 1; 762 recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 763 break; 764 } 765 } 766 if (frameOffsetInFile_ == 0) { 767 recordOffsetInFrame_ = 0; 768 recordLength = prevNL; 769 break; 770 } 771 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 772 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 773 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 774 RUNTIME_CHECK(handler, got >= need); 775 } 776 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 777 if (*recordLength > 0 && 778 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 779 --*recordLength; 780 } 781 } 782 783 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 784 if (impliedEndfile_) { 785 impliedEndfile_ = false; 786 if (access == Access::Sequential && mayPosition()) { 787 DoEndfile(handler); 788 } 789 } 790 } 791 792 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 793 endfileRecordNumber = currentRecordNumber; 794 Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); 795 BeginRecord(); 796 impliedEndfile_ = false; 797 } 798 799 void ExternalFileUnit::CommitWrites() { 800 frameOffsetInFile_ += 801 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 802 recordOffsetInFrame_ = 0; 803 BeginRecord(); 804 } 805 806 ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) { 807 OwningPtr<ChildIo> current{std::move(child_)}; 808 Terminator &terminator{parent.GetIoErrorHandler()}; 809 OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))}; 810 child_.reset(next.release()); 811 return *child_; 812 } 813 814 void ExternalFileUnit::PopChildIo(ChildIo &child) { 815 if (child_.get() != &child) { 816 child.parent().GetIoErrorHandler().Crash( 817 "ChildIo being popped is not top of stack"); 818 } 819 child_.reset(child.AcquirePrevious().release()); // deletes top child 820 } 821 822 void ChildIo::EndIoStatement() { 823 io_.reset(); 824 u_.emplace<std::monostate>(); 825 } 826 827 bool ChildIo::CheckFormattingAndDirection(Terminator &terminator, 828 const char *what, bool unformatted, Direction direction) { 829 bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()}; 830 bool parentIsFormatted{parentIsInput 831 ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() != 832 nullptr 833 : parent_.get_if<FormattedIoStatementState<Direction::Output>>() != 834 nullptr}; 835 bool parentIsUnformatted{!parentIsFormatted}; 836 if (unformatted != parentIsUnformatted) { 837 terminator.Crash("Child %s attempted on %s parent I/O unit", what, 838 parentIsUnformatted ? "unformatted" : "formatted"); 839 return false; 840 } else if (parentIsInput != (direction == Direction::Input)) { 841 terminator.Crash("Child %s attempted on %s parent I/O unit", what, 842 parentIsInput ? "input" : "output"); 843 return false; 844 } else { 845 return true; 846 } 847 } 848 849 } // namespace Fortran::runtime::io 850