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