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 (!isUnformatted) { 410 if (isFixedRecordLength && recordLength) { 411 if (furthestPositionInRecord < *recordLength) { 412 WriteFrame(frameOffsetInFile_, *recordLength, handler); 413 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 414 ' ', *recordLength - furthestPositionInRecord); 415 } 416 } else { 417 positionInRecord = furthestPositionInRecord; 418 ok &= Emit("\n", 1, 1, handler); // TODO: Windows CR+LF 419 } 420 } 421 frameOffsetInFile_ += 422 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 423 recordOffsetInFrame_ = 0; 424 impliedEndfile_ = true; 425 ++currentRecordNumber; 426 BeginRecord(); 427 } 428 return ok; 429 } 430 431 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 432 if (access != Access::Sequential) { 433 handler.SignalError(IostatBackspaceNonSequential, 434 "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); 435 } else { 436 if (endfileRecordNumber && currentRecordNumber > *endfileRecordNumber) { 437 // BACKSPACE after ENDFILE 438 } else { 439 DoImpliedEndfile(handler); 440 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) { 441 --currentRecordNumber; 442 if (isFixedRecordLength) { 443 BackspaceFixedRecord(handler); 444 } else if (isUnformatted) { 445 BackspaceVariableUnformattedRecord(handler); 446 } else { 447 BackspaceVariableFormattedRecord(handler); 448 } 449 } 450 } 451 BeginRecord(); 452 } 453 } 454 455 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 456 if (isTerminal()) { 457 Flush(handler); 458 } 459 } 460 461 void ExternalFileUnit::Endfile(IoErrorHandler &handler) { 462 if (access != Access::Sequential) { 463 handler.SignalError(IostatEndfileNonSequential, 464 "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); 465 } else if (!mayWrite()) { 466 handler.SignalError(IostatEndfileUnwritable, 467 "ENDFILE(UNIT=%d) on read-only file", unitNumber()); 468 } else if (endfileRecordNumber && 469 currentRecordNumber > *endfileRecordNumber) { 470 // ENDFILE after ENDFILE 471 } else { 472 DoEndfile(handler); 473 ++currentRecordNumber; 474 } 475 } 476 477 void ExternalFileUnit::Rewind(IoErrorHandler &handler) { 478 if (access == Access::Direct) { 479 handler.SignalError(IostatRewindNonSequential, 480 "REWIND(UNIT=%d) on non-sequential file", unitNumber()); 481 } else { 482 DoImpliedEndfile(handler); 483 SetPosition(0); 484 currentRecordNumber = 1; 485 } 486 } 487 488 void ExternalFileUnit::EndIoStatement() { 489 frameOffsetInFile_ += recordOffsetInFrame_; 490 recordOffsetInFrame_ = 0; 491 io_.reset(); 492 u_.emplace<std::monostate>(); 493 lock_.Drop(); 494 } 495 496 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 497 IoErrorHandler &handler) { 498 std::int32_t header{0}, footer{0}; 499 std::size_t need{recordOffsetInFrame_ + sizeof header}; 500 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 501 // Try to emit informative errors to help debug corrupted files. 502 const char *error{nullptr}; 503 if (got < need) { 504 if (got == recordOffsetInFrame_) { 505 handler.SignalEnd(); 506 } else { 507 error = "Unformatted variable-length sequential file input failed at " 508 "record #%jd (file offset %jd): truncated record header"; 509 } 510 } else { 511 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 512 recordLength = sizeof header + header; // does not include footer 513 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 514 got = ReadFrame(frameOffsetInFile_, need, handler); 515 if (got < need) { 516 error = "Unformatted variable-length sequential file input failed at " 517 "record #%jd (file offset %jd): hit EOF reading record with " 518 "length %jd bytes"; 519 } else { 520 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, 521 sizeof footer); 522 if (footer != header) { 523 error = "Unformatted variable-length sequential file input failed at " 524 "record #%jd (file offset %jd): record header has length %jd " 525 "that does not match record footer (%jd)"; 526 } 527 } 528 } 529 if (error) { 530 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 531 static_cast<std::intmax_t>(frameOffsetInFile_), 532 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 533 // TODO: error recovery 534 } 535 positionInRecord = sizeof header; 536 } 537 538 void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( 539 IoErrorHandler &handler) { 540 if (this == defaultInput && defaultOutput) { 541 defaultOutput->Flush(handler); 542 } 543 std::size_t length{0}; 544 do { 545 std::size_t need{recordOffsetInFrame_ + length + 1}; 546 length = ReadFrame(frameOffsetInFile_, need, handler); 547 if (length < need) { 548 handler.SignalEnd(); 549 break; 550 } 551 } while (!SetSequentialVariableFormattedRecordLength()); 552 } 553 554 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 555 RUNTIME_CHECK(handler, recordLength.has_value()); 556 if (frameOffsetInFile_ < *recordLength) { 557 handler.SignalError(IostatBackspaceAtFirstRecord); 558 } else { 559 frameOffsetInFile_ -= *recordLength; 560 } 561 } 562 563 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 564 IoErrorHandler &handler) { 565 std::int32_t header{0}, footer{0}; 566 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 567 frameOffsetInFile_ += recordOffsetInFrame_; 568 recordOffsetInFrame_ = 0; 569 if (frameOffsetInFile_ <= headerBytes) { 570 handler.SignalError(IostatBackspaceAtFirstRecord); 571 return; 572 } 573 // Error conditions here cause crashes, not file format errors, because the 574 // validity of the file structure before the current record will have been 575 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 576 std::size_t got{ 577 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 578 RUNTIME_CHECK(handler, got >= sizeof footer); 579 std::memcpy(&footer, Frame(), sizeof footer); 580 recordLength = footer; 581 RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes); 582 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 583 if (frameOffsetInFile_ >= headerBytes) { 584 frameOffsetInFile_ -= headerBytes; 585 recordOffsetInFrame_ = headerBytes; 586 } 587 auto need{static_cast<std::size_t>( 588 recordOffsetInFrame_ + sizeof header + *recordLength)}; 589 got = ReadFrame(frameOffsetInFile_, need, handler); 590 RUNTIME_CHECK(handler, got >= need); 591 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 592 RUNTIME_CHECK(handler, header == *recordLength); 593 } 594 595 // There's no portable memrchr(), unfortunately, and strrchr() would 596 // fail on a record with a NUL, so we have to do it the hard way. 597 static const char *FindLastNewline(const char *str, std::size_t length) { 598 for (const char *p{str + length}; p-- > str;) { 599 if (*p == '\n') { 600 return p; 601 } 602 } 603 return nullptr; 604 } 605 606 void ExternalFileUnit::BackspaceVariableFormattedRecord( 607 IoErrorHandler &handler) { 608 // File offset of previous record's newline 609 auto prevNL{ 610 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 611 if (prevNL < 0) { 612 handler.SignalError(IostatBackspaceAtFirstRecord); 613 return; 614 } 615 while (true) { 616 if (frameOffsetInFile_ < prevNL) { 617 if (const char *p{ 618 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 619 recordOffsetInFrame_ = p - Frame() + 1; 620 *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 621 break; 622 } 623 } 624 if (frameOffsetInFile_ == 0) { 625 recordOffsetInFrame_ = 0; 626 *recordLength = prevNL; 627 break; 628 } 629 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 630 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 631 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 632 RUNTIME_CHECK(handler, got >= need); 633 } 634 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 635 if (*recordLength > 0 && 636 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 637 --*recordLength; 638 } 639 } 640 641 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 642 if (impliedEndfile_) { 643 impliedEndfile_ = false; 644 if (access == Access::Sequential && mayPosition()) { 645 DoEndfile(handler); 646 } 647 } 648 } 649 650 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 651 endfileRecordNumber = currentRecordNumber; 652 Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); 653 BeginRecord(); 654 impliedEndfile_ = false; 655 } 656 } // namespace Fortran::runtime::io 657