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