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