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