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 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { 356 RUNTIME_CHECK(handler, direction_ == Direction::Input); 357 if (!beganReadingRecord_) { 358 beganReadingRecord_ = true; 359 if (access == Access::Sequential) { 360 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 361 handler.SignalEnd(); 362 } else if (isFixedRecordLength) { 363 RUNTIME_CHECK(handler, recordLength.has_value()); 364 auto need{ 365 static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)}; 366 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 367 if (got < need) { 368 handler.SignalEnd(); 369 } 370 } else if (isUnformatted) { 371 BeginSequentialVariableUnformattedInputRecord(handler); 372 } else { // formatted 373 BeginSequentialVariableFormattedInputRecord(handler); 374 } 375 } 376 } 377 RUNTIME_CHECK(handler, 378 access != Access::Sequential || recordLength.has_value() || 379 handler.InError()); 380 return !handler.InError(); 381 } 382 383 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) { 384 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); 385 beganReadingRecord_ = false; 386 if (handler.InError()) { 387 // avoid bogus crashes in END/ERR circumstances 388 } else if (access == Access::Sequential) { 389 RUNTIME_CHECK(handler, recordLength.has_value()); 390 if (isFixedRecordLength) { 391 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; 392 recordOffsetInFrame_ = 0; 393 } else if (isUnformatted) { 394 // Retain footer in frame for more efficient BACKSPACE 395 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; 396 recordOffsetInFrame_ = sizeof(std::uint32_t); 397 recordLength.reset(); 398 } else { // formatted 399 if (Frame()[recordOffsetInFrame_ + *recordLength] == '\r') { 400 ++recordOffsetInFrame_; 401 } 402 recordOffsetInFrame_ += *recordLength + 1; 403 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ - 1] == '\n'); 404 recordLength.reset(); 405 } 406 } 407 ++currentRecordNumber; 408 BeginRecord(); 409 } 410 411 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 412 if (direction_ == Direction::Input) { 413 FinishReadingRecord(handler); 414 return BeginReadingRecord(handler); 415 } else { // Direction::Output 416 bool ok{true}; 417 if (isFixedRecordLength && recordLength) { 418 // Pad remainder of fixed length record 419 if (furthestPositionInRecord < *recordLength) { 420 WriteFrame( 421 frameOffsetInFile_, recordOffsetInFrame_ + *recordLength, handler); 422 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 423 isUnformatted ? 0 : ' ', *recordLength - furthestPositionInRecord); 424 } 425 } else { 426 positionInRecord = furthestPositionInRecord; 427 if (isUnformatted) { 428 // Append the length of a sequential unformatted variable-length record 429 // as its footer, then overwrite the reserved first four bytes of the 430 // record with its length as its header. These four bytes were skipped 431 // over in BeginUnformattedIO<Output>(). 432 // TODO: Break very large records up into subrecords with negative 433 // headers &/or footers 434 std::uint32_t length; 435 length = furthestPositionInRecord - sizeof length; 436 ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length, 437 sizeof length, handler); 438 positionInRecord = 0; 439 ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length, 440 sizeof length, handler); 441 } else { 442 // Terminate formatted variable length record 443 ok &= Emit("\n", 1, 1, handler); // TODO: Windows CR+LF 444 } 445 } 446 frameOffsetInFile_ += 447 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 448 recordOffsetInFrame_ = 0; 449 impliedEndfile_ = true; 450 ++currentRecordNumber; 451 BeginRecord(); 452 return ok; 453 } 454 } 455 456 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 457 if (access != Access::Sequential) { 458 handler.SignalError(IostatBackspaceNonSequential, 459 "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); 460 } else { 461 if (endfileRecordNumber && currentRecordNumber > *endfileRecordNumber) { 462 // BACKSPACE after ENDFILE 463 } else { 464 DoImpliedEndfile(handler); 465 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) { 466 --currentRecordNumber; 467 if (isFixedRecordLength) { 468 BackspaceFixedRecord(handler); 469 } else if (isUnformatted) { 470 BackspaceVariableUnformattedRecord(handler); 471 } else { 472 BackspaceVariableFormattedRecord(handler); 473 } 474 } 475 } 476 BeginRecord(); 477 } 478 } 479 480 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 481 if (isTerminal()) { 482 Flush(handler); 483 } 484 } 485 486 void ExternalFileUnit::Endfile(IoErrorHandler &handler) { 487 if (access != Access::Sequential) { 488 handler.SignalError(IostatEndfileNonSequential, 489 "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); 490 } else if (!mayWrite()) { 491 handler.SignalError(IostatEndfileUnwritable, 492 "ENDFILE(UNIT=%d) on read-only file", unitNumber()); 493 } else if (endfileRecordNumber && 494 currentRecordNumber > *endfileRecordNumber) { 495 // ENDFILE after ENDFILE 496 } else { 497 DoEndfile(handler); 498 ++currentRecordNumber; 499 } 500 } 501 502 void ExternalFileUnit::Rewind(IoErrorHandler &handler) { 503 if (access == Access::Direct) { 504 handler.SignalError(IostatRewindNonSequential, 505 "REWIND(UNIT=%d) on non-sequential file", unitNumber()); 506 } else { 507 DoImpliedEndfile(handler); 508 SetPosition(0); 509 currentRecordNumber = 1; 510 } 511 } 512 513 void ExternalFileUnit::EndIoStatement() { 514 frameOffsetInFile_ += recordOffsetInFrame_; 515 recordOffsetInFrame_ = 0; 516 io_.reset(); 517 u_.emplace<std::monostate>(); 518 lock_.Drop(); 519 } 520 521 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 522 IoErrorHandler &handler) { 523 std::int32_t header{0}, footer{0}; 524 std::size_t need{recordOffsetInFrame_ + sizeof header}; 525 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 526 // Try to emit informative errors to help debug corrupted files. 527 const char *error{nullptr}; 528 if (got < need) { 529 if (got == recordOffsetInFrame_) { 530 handler.SignalEnd(); 531 } else { 532 error = "Unformatted variable-length sequential file input failed at " 533 "record #%jd (file offset %jd): truncated record header"; 534 } 535 } else { 536 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 537 recordLength = sizeof header + header; // does not include footer 538 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 539 got = ReadFrame(frameOffsetInFile_, need, handler); 540 if (got < need) { 541 error = "Unformatted variable-length sequential file input failed at " 542 "record #%jd (file offset %jd): hit EOF reading record with " 543 "length %jd bytes"; 544 } else { 545 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, 546 sizeof footer); 547 if (footer != header) { 548 error = "Unformatted variable-length sequential file input failed at " 549 "record #%jd (file offset %jd): record header has length %jd " 550 "that does not match record footer (%jd)"; 551 } 552 } 553 } 554 if (error) { 555 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 556 static_cast<std::intmax_t>(frameOffsetInFile_), 557 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 558 // TODO: error recovery 559 } 560 positionInRecord = sizeof header; 561 } 562 563 void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( 564 IoErrorHandler &handler) { 565 if (this == defaultInput && defaultOutput) { 566 defaultOutput->Flush(handler); 567 } 568 std::size_t length{0}; 569 do { 570 std::size_t need{recordOffsetInFrame_ + length + 1}; 571 length = ReadFrame(frameOffsetInFile_, need, handler); 572 if (length < need) { 573 handler.SignalEnd(); 574 break; 575 } 576 } while (!SetSequentialVariableFormattedRecordLength()); 577 } 578 579 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 580 RUNTIME_CHECK(handler, recordLength.has_value()); 581 if (frameOffsetInFile_ < *recordLength) { 582 handler.SignalError(IostatBackspaceAtFirstRecord); 583 } else { 584 frameOffsetInFile_ -= *recordLength; 585 } 586 } 587 588 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 589 IoErrorHandler &handler) { 590 std::int32_t header{0}, footer{0}; 591 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 592 frameOffsetInFile_ += recordOffsetInFrame_; 593 recordOffsetInFrame_ = 0; 594 if (frameOffsetInFile_ <= headerBytes) { 595 handler.SignalError(IostatBackspaceAtFirstRecord); 596 return; 597 } 598 // Error conditions here cause crashes, not file format errors, because the 599 // validity of the file structure before the current record will have been 600 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 601 std::size_t got{ 602 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 603 RUNTIME_CHECK(handler, got >= sizeof footer); 604 std::memcpy(&footer, Frame(), sizeof footer); 605 recordLength = footer; 606 RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes); 607 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 608 if (frameOffsetInFile_ >= headerBytes) { 609 frameOffsetInFile_ -= headerBytes; 610 recordOffsetInFrame_ = headerBytes; 611 } 612 auto need{static_cast<std::size_t>( 613 recordOffsetInFrame_ + sizeof header + *recordLength)}; 614 got = ReadFrame(frameOffsetInFile_, need, handler); 615 RUNTIME_CHECK(handler, got >= need); 616 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 617 RUNTIME_CHECK(handler, header == *recordLength); 618 } 619 620 // There's no portable memrchr(), unfortunately, and strrchr() would 621 // fail on a record with a NUL, so we have to do it the hard way. 622 static const char *FindLastNewline(const char *str, std::size_t length) { 623 for (const char *p{str + length}; p-- > str;) { 624 if (*p == '\n') { 625 return p; 626 } 627 } 628 return nullptr; 629 } 630 631 void ExternalFileUnit::BackspaceVariableFormattedRecord( 632 IoErrorHandler &handler) { 633 // File offset of previous record's newline 634 auto prevNL{ 635 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 636 if (prevNL < 0) { 637 handler.SignalError(IostatBackspaceAtFirstRecord); 638 return; 639 } 640 while (true) { 641 if (frameOffsetInFile_ < prevNL) { 642 if (const char *p{ 643 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 644 recordOffsetInFrame_ = p - Frame() + 1; 645 *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 646 break; 647 } 648 } 649 if (frameOffsetInFile_ == 0) { 650 recordOffsetInFrame_ = 0; 651 *recordLength = prevNL; 652 break; 653 } 654 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 655 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 656 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 657 RUNTIME_CHECK(handler, got >= need); 658 } 659 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 660 if (*recordLength > 0 && 661 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 662 --*recordLength; 663 } 664 } 665 666 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 667 if (impliedEndfile_) { 668 impliedEndfile_ = false; 669 if (access == Access::Sequential && mayPosition()) { 670 DoEndfile(handler); 671 } 672 } 673 } 674 675 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 676 endfileRecordNumber = currentRecordNumber; 677 Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); 678 BeginRecord(); 679 impliedEndfile_ = false; 680 } 681 } // namespace Fortran::runtime::io 682