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