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