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