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