1 //===-- runtime/unit.cpp --------------------------------------------------===// 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 // Implementation of ExternalFileUnit common for both 10 // RT_USE_PSEUDO_FILE_UNIT=0 and RT_USE_PSEUDO_FILE_UNIT=1. 11 // 12 //===----------------------------------------------------------------------===// 13 #include "unit.h" 14 #include "io-error.h" 15 #include "lock.h" 16 #include "tools.h" 17 #include <limits> 18 #include <utility> 19 20 namespace Fortran::runtime::io { 21 22 #ifndef FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS 23 RT_OFFLOAD_VAR_GROUP_BEGIN 24 RT_VAR_ATTRS ExternalFileUnit *defaultInput{nullptr}; // unit 5 25 RT_VAR_ATTRS ExternalFileUnit *defaultOutput{nullptr}; // unit 6 26 RT_VAR_ATTRS ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension 27 RT_OFFLOAD_VAR_GROUP_END 28 #endif // FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS 29 30 RT_OFFLOAD_API_GROUP_BEGIN 31 32 static inline RT_API_ATTRS void SwapEndianness( 33 char *data, std::size_t bytes, std::size_t elementBytes) { 34 if (elementBytes > 1) { 35 auto half{elementBytes >> 1}; 36 for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) { 37 for (std::size_t k{0}; k < half; ++k) { 38 RT_DIAG_PUSH 39 RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN 40 std::swap(data[j + k], data[j + elementBytes - 1 - k]); 41 RT_DIAG_POP 42 } 43 } 44 } 45 } 46 47 bool ExternalFileUnit::Emit(const char *data, std::size_t bytes, 48 std::size_t elementBytes, IoErrorHandler &handler) { 49 auto furthestAfter{std::max(furthestPositionInRecord, 50 positionInRecord + static_cast<std::int64_t>(bytes))}; 51 if (openRecl) { 52 // Check for fixed-length record overrun, but allow for 53 // sequential record termination. 54 int extra{0}; 55 int header{0}; 56 if (access == Access::Sequential) { 57 if (isUnformatted.value_or(false)) { 58 // record header + footer 59 header = static_cast<int>(sizeof(std::uint32_t)); 60 extra = 2 * header; 61 } else { 62 #ifdef _WIN32 63 if (!isWindowsTextFile()) { 64 ++extra; // carriage return (CR) 65 } 66 #endif 67 ++extra; // newline (LF) 68 } 69 } 70 if (furthestAfter > extra + *openRecl) { 71 handler.SignalError(IostatRecordWriteOverrun, 72 "Attempt to write %zd bytes to position %jd in a fixed-size record " 73 "of %jd bytes", 74 bytes, static_cast<std::intmax_t>(positionInRecord - header), 75 static_cast<std::intmax_t>(*openRecl)); 76 return false; 77 } 78 } 79 if (recordLength) { 80 // It is possible for recordLength to have a value now for a 81 // variable-length output record if the previous operation 82 // was a BACKSPACE or non advancing input statement. 83 recordLength.reset(); 84 beganReadingRecord_ = false; 85 } 86 if (IsAfterEndfile()) { 87 handler.SignalError(IostatWriteAfterEndfile); 88 return false; 89 } 90 CheckDirectAccess(handler); 91 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); 92 if (positionInRecord > furthestPositionInRecord) { 93 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', 94 positionInRecord - furthestPositionInRecord); 95 } 96 char *to{Frame() + recordOffsetInFrame_ + positionInRecord}; 97 std::memcpy(to, data, bytes); 98 if (swapEndianness_) { 99 SwapEndianness(to, bytes, elementBytes); 100 } 101 positionInRecord += bytes; 102 furthestPositionInRecord = furthestAfter; 103 anyWriteSinceLastPositioning_ = true; 104 return true; 105 } 106 107 bool ExternalFileUnit::Receive(char *data, std::size_t bytes, 108 std::size_t elementBytes, IoErrorHandler &handler) { 109 RUNTIME_CHECK(handler, direction_ == Direction::Input); 110 auto furthestAfter{std::max(furthestPositionInRecord, 111 positionInRecord + static_cast<std::int64_t>(bytes))}; 112 if (furthestAfter > recordLength.value_or(furthestAfter)) { 113 handler.SignalError(IostatRecordReadOverrun, 114 "Attempt to read %zd bytes at position %jd in a record of %jd bytes", 115 bytes, static_cast<std::intmax_t>(positionInRecord), 116 static_cast<std::intmax_t>(*recordLength)); 117 return false; 118 } 119 auto need{recordOffsetInFrame_ + furthestAfter}; 120 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 121 if (got >= need) { 122 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes); 123 if (swapEndianness_) { 124 SwapEndianness(data, bytes, elementBytes); 125 } 126 positionInRecord += bytes; 127 furthestPositionInRecord = furthestAfter; 128 return true; 129 } else { 130 HitEndOnRead(handler); 131 return false; 132 } 133 } 134 135 std::size_t ExternalFileUnit::GetNextInputBytes( 136 const char *&p, IoErrorHandler &handler) { 137 RUNTIME_CHECK(handler, direction_ == Direction::Input); 138 std::size_t length{1}; 139 if (auto recl{EffectiveRecordLength()}) { 140 if (positionInRecord < *recl) { 141 length = *recl - positionInRecord; 142 } else { 143 p = nullptr; 144 return 0; 145 } 146 } 147 p = FrameNextInput(handler, length); 148 return p ? length : 0; 149 } 150 151 std::size_t ExternalFileUnit::ViewBytesInRecord( 152 const char *&p, bool forward) const { 153 p = nullptr; 154 auto recl{recordLength.value_or(positionInRecord)}; 155 if (forward) { 156 if (positionInRecord < recl) { 157 p = Frame() + recordOffsetInFrame_ + positionInRecord; 158 return recl - positionInRecord; 159 } 160 } else { 161 if (positionInRecord <= recl) { 162 p = Frame() + recordOffsetInFrame_ + positionInRecord; 163 } 164 return positionInRecord - leftTabLimit.value_or(0); 165 } 166 return 0; 167 } 168 169 const char *ExternalFileUnit::FrameNextInput( 170 IoErrorHandler &handler, std::size_t bytes) { 171 RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted); 172 if (static_cast<std::int64_t>(positionInRecord + bytes) <= 173 recordLength.value_or(positionInRecord + bytes)) { 174 auto at{recordOffsetInFrame_ + positionInRecord}; 175 auto need{static_cast<std::size_t>(at + bytes)}; 176 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 177 SetVariableFormattedRecordLength(); 178 if (got >= need) { 179 return Frame() + at; 180 } 181 HitEndOnRead(handler); 182 } 183 return nullptr; 184 } 185 186 bool ExternalFileUnit::SetVariableFormattedRecordLength() { 187 if (recordLength || access == Access::Direct) { 188 return true; 189 } else if (FrameLength() > recordOffsetInFrame_) { 190 const char *record{Frame() + recordOffsetInFrame_}; 191 std::size_t bytes{FrameLength() - recordOffsetInFrame_}; 192 if (const char *nl{FindCharacter(record, '\n', bytes)}) { 193 recordLength = nl - record; 194 if (*recordLength > 0 && record[*recordLength - 1] == '\r') { 195 --*recordLength; 196 } 197 return true; 198 } 199 } 200 return false; 201 } 202 203 bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { 204 RUNTIME_CHECK(handler, direction_ == Direction::Input); 205 if (!beganReadingRecord_) { 206 beganReadingRecord_ = true; 207 // Don't use IsAtEOF() to check for an EOF condition here, just detect 208 // it from a failed or short read from the file. IsAtEOF() could be 209 // wrong for formatted input if actual newline characters had been 210 // written in-band by previous WRITEs before a REWIND. In fact, 211 // now that we know that the unit is being used for input (again), 212 // it's best to reset endfileRecordNumber and ensure IsAtEOF() will 213 // now be true on return only if it gets set by HitEndOnRead(). 214 endfileRecordNumber.reset(); 215 if (access == Access::Direct) { 216 CheckDirectAccess(handler); 217 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)}; 218 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 219 if (got >= need) { 220 recordLength = openRecl; 221 } else { 222 recordLength.reset(); 223 HitEndOnRead(handler); 224 } 225 } else { 226 if (anyWriteSinceLastPositioning_ && access == Access::Sequential) { 227 // Most Fortran implementations allow a READ after a WRITE; 228 // the read then just hits an EOF. 229 DoEndfile<false, Direction::Input>(handler); 230 } 231 recordLength.reset(); 232 RUNTIME_CHECK(handler, isUnformatted.has_value()); 233 if (*isUnformatted) { 234 if (access == Access::Sequential) { 235 BeginSequentialVariableUnformattedInputRecord(handler); 236 } 237 } else { // formatted sequential or stream 238 BeginVariableFormattedInputRecord(handler); 239 } 240 } 241 } 242 RUNTIME_CHECK(handler, 243 recordLength.has_value() || !IsRecordFile() || handler.InError()); 244 return !handler.InError(); 245 } 246 247 void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) { 248 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); 249 beganReadingRecord_ = false; 250 if (handler.GetIoStat() == IostatEnd || 251 (IsRecordFile() && !recordLength.has_value())) { 252 // Avoid bogus crashes in END/ERR circumstances; but 253 // still increment the current record number so that 254 // an attempted read of an endfile record, followed by 255 // a BACKSPACE, will still be at EOF. 256 ++currentRecordNumber; 257 } else if (IsRecordFile()) { 258 recordOffsetInFrame_ += *recordLength; 259 if (access != Access::Direct) { 260 RUNTIME_CHECK(handler, isUnformatted.has_value()); 261 recordLength.reset(); 262 if (isUnformatted.value_or(false)) { 263 // Retain footer in frame for more efficient BACKSPACE 264 frameOffsetInFile_ += recordOffsetInFrame_; 265 recordOffsetInFrame_ = sizeof(std::uint32_t); 266 } else { // formatted 267 if (FrameLength() > recordOffsetInFrame_ && 268 Frame()[recordOffsetInFrame_] == '\r') { 269 ++recordOffsetInFrame_; 270 } 271 if (FrameLength() > recordOffsetInFrame_ && 272 Frame()[recordOffsetInFrame_] == '\n') { 273 ++recordOffsetInFrame_; 274 } 275 if (!pinnedFrame || mayPosition()) { 276 frameOffsetInFile_ += recordOffsetInFrame_; 277 recordOffsetInFrame_ = 0; 278 } 279 } 280 } 281 ++currentRecordNumber; 282 } else { // unformatted stream 283 furthestPositionInRecord = 284 std::max(furthestPositionInRecord, positionInRecord); 285 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; 286 recordOffsetInFrame_ = 0; 287 } 288 BeginRecord(); 289 leftTabLimit.reset(); 290 } 291 292 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 293 if (direction_ == Direction::Input) { 294 FinishReadingRecord(handler); 295 return BeginReadingRecord(handler); 296 } else { // Direction::Output 297 bool ok{true}; 298 RUNTIME_CHECK(handler, isUnformatted.has_value()); 299 positionInRecord = furthestPositionInRecord; 300 if (access == Access::Direct) { 301 if (furthestPositionInRecord < 302 openRecl.value_or(furthestPositionInRecord)) { 303 // Pad remainder of fixed length record 304 WriteFrame( 305 frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler); 306 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 307 isUnformatted.value_or(false) ? 0 : ' ', 308 *openRecl - furthestPositionInRecord); 309 furthestPositionInRecord = *openRecl; 310 } 311 } else if (*isUnformatted) { 312 if (access == Access::Sequential) { 313 // Append the length of a sequential unformatted variable-length record 314 // as its footer, then overwrite the reserved first four bytes of the 315 // record with its length as its header. These four bytes were skipped 316 // over in BeginUnformattedIO<Output>(). 317 // TODO: Break very large records up into subrecords with negative 318 // headers &/or footers 319 std::uint32_t length; 320 length = furthestPositionInRecord - sizeof length; 321 ok = ok && 322 Emit(reinterpret_cast<const char *>(&length), sizeof length, 323 sizeof length, handler); 324 positionInRecord = 0; 325 ok = ok && 326 Emit(reinterpret_cast<const char *>(&length), sizeof length, 327 sizeof length, handler); 328 } else { 329 // Unformatted stream: nothing to do 330 } 331 } else if (handler.GetIoStat() != IostatOk && 332 furthestPositionInRecord == 0) { 333 // Error in formatted variable length record, and no output yet; do 334 // nothing, like most other Fortran compilers do. 335 return true; 336 } else { 337 // Terminate formatted variable length record 338 const char *lineEnding{"\n"}; 339 std::size_t lineEndingBytes{1}; 340 #ifdef _WIN32 341 if (!isWindowsTextFile()) { 342 lineEnding = "\r\n"; 343 lineEndingBytes = 2; 344 } 345 #endif 346 ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler); 347 } 348 leftTabLimit.reset(); 349 if (IsAfterEndfile()) { 350 return false; 351 } 352 CommitWrites(); 353 ++currentRecordNumber; 354 if (access != Access::Direct) { 355 impliedEndfile_ = IsRecordFile(); 356 if (IsAtEOF()) { 357 endfileRecordNumber.reset(); 358 } 359 } 360 return ok; 361 } 362 } 363 364 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 365 if (access == Access::Direct || !IsRecordFile()) { 366 handler.SignalError(IostatBackspaceNonSequential, 367 "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream", 368 unitNumber()); 369 } else { 370 if (IsAfterEndfile()) { 371 // BACKSPACE after explicit ENDFILE 372 currentRecordNumber = *endfileRecordNumber; 373 } else if (leftTabLimit && direction_ == Direction::Input) { 374 // BACKSPACE after non-advancing input 375 leftTabLimit.reset(); 376 } else { 377 DoImpliedEndfile(handler); 378 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) { 379 --currentRecordNumber; 380 if (openRecl && access == Access::Direct) { 381 BackspaceFixedRecord(handler); 382 } else { 383 RUNTIME_CHECK(handler, isUnformatted.has_value()); 384 if (isUnformatted.value_or(false)) { 385 BackspaceVariableUnformattedRecord(handler); 386 } else { 387 BackspaceVariableFormattedRecord(handler); 388 } 389 } 390 } 391 } 392 BeginRecord(); 393 anyWriteSinceLastPositioning_ = false; 394 } 395 } 396 397 void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) { 398 if (!mayPosition()) { 399 auto frameAt{FrameAt()}; 400 if (frameOffsetInFile_ >= frameAt && 401 frameOffsetInFile_ < 402 static_cast<std::int64_t>(frameAt + FrameLength())) { 403 // A Flush() that's about to happen to a non-positionable file 404 // needs to advance frameOffsetInFile_ to prevent attempts at 405 // impossible seeks 406 CommitWrites(); 407 leftTabLimit.reset(); 408 } 409 } 410 Flush(handler); 411 } 412 413 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 414 if (isTerminal()) { 415 FlushOutput(handler); 416 } 417 } 418 419 void ExternalFileUnit::Endfile(IoErrorHandler &handler) { 420 if (access == Access::Direct) { 421 handler.SignalError(IostatEndfileDirect, 422 "ENDFILE(UNIT=%d) on direct-access file", unitNumber()); 423 } else if (!mayWrite()) { 424 handler.SignalError(IostatEndfileUnwritable, 425 "ENDFILE(UNIT=%d) on read-only file", unitNumber()); 426 } else if (IsAfterEndfile()) { 427 // ENDFILE after ENDFILE 428 } else { 429 DoEndfile(handler); 430 if (IsRecordFile() && access != Access::Direct) { 431 // Explicit ENDFILE leaves position *after* the endfile record 432 RUNTIME_CHECK(handler, endfileRecordNumber.has_value()); 433 currentRecordNumber = *endfileRecordNumber + 1; 434 } 435 } 436 } 437 438 void ExternalFileUnit::Rewind(IoErrorHandler &handler) { 439 if (access == Access::Direct) { 440 handler.SignalError(IostatRewindNonSequential, 441 "REWIND(UNIT=%d) on non-sequential file", unitNumber()); 442 } else { 443 DoImpliedEndfile(handler); 444 SetPosition(0, handler); 445 currentRecordNumber = 1; 446 leftTabLimit.reset(); 447 anyWriteSinceLastPositioning_ = false; 448 } 449 } 450 451 void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) { 452 frameOffsetInFile_ = pos; 453 recordOffsetInFrame_ = 0; 454 if (access == Access::Direct) { 455 directAccessRecWasSet_ = true; 456 } 457 BeginRecord(); 458 } 459 460 bool ExternalFileUnit::SetStreamPos( 461 std::int64_t oneBasedPos, IoErrorHandler &handler) { 462 if (access != Access::Stream) { 463 handler.SignalError("POS= may not appear unless ACCESS='STREAM'"); 464 return false; 465 } 466 if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11) 467 handler.SignalError( 468 "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos)); 469 return false; 470 } 471 // A backwards POS= implies truncation after writing, at least in 472 // Intel and NAG. 473 if (static_cast<std::size_t>(oneBasedPos - 1) < 474 frameOffsetInFile_ + recordOffsetInFrame_) { 475 DoImpliedEndfile(handler); 476 } 477 SetPosition(oneBasedPos - 1, handler); 478 // We no longer know which record we're in. Set currentRecordNumber to 479 // a large value from whence we can both advance and backspace. 480 currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2; 481 endfileRecordNumber.reset(); 482 return true; 483 } 484 485 bool ExternalFileUnit::SetDirectRec( 486 std::int64_t oneBasedRec, IoErrorHandler &handler) { 487 if (access != Access::Direct) { 488 handler.SignalError("REC= may not appear unless ACCESS='DIRECT'"); 489 return false; 490 } 491 if (!openRecl) { 492 handler.SignalError("RECL= was not specified"); 493 return false; 494 } 495 if (oneBasedRec < 1) { 496 handler.SignalError( 497 "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec)); 498 return false; 499 } 500 currentRecordNumber = oneBasedRec; 501 SetPosition((oneBasedRec - 1) * *openRecl, handler); 502 return true; 503 } 504 505 void ExternalFileUnit::EndIoStatement() { 506 io_.reset(); 507 u_.emplace<std::monostate>(); 508 lock_.Drop(); 509 } 510 511 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 512 IoErrorHandler &handler) { 513 RUNTIME_CHECK(handler, access == Access::Sequential); 514 std::int32_t header{0}, footer{0}; 515 std::size_t need{recordOffsetInFrame_ + sizeof header}; 516 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 517 // Try to emit informative errors to help debug corrupted files. 518 const char *error{nullptr}; 519 if (got < need) { 520 if (got == recordOffsetInFrame_) { 521 HitEndOnRead(handler); 522 } else { 523 error = "Unformatted variable-length sequential file input failed at " 524 "record #%jd (file offset %jd): truncated record header"; 525 } 526 } else { 527 header = ReadHeaderOrFooter(recordOffsetInFrame_); 528 recordLength = sizeof header + header; // does not include footer 529 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 530 got = ReadFrame(frameOffsetInFile_, need, handler); 531 if (got < need) { 532 error = "Unformatted variable-length sequential file input failed at " 533 "record #%jd (file offset %jd): hit EOF reading record with " 534 "length %jd bytes"; 535 } else { 536 footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength); 537 if (footer != header) { 538 error = "Unformatted variable-length sequential file input failed at " 539 "record #%jd (file offset %jd): record header has length %jd " 540 "that does not match record footer (%jd)"; 541 } 542 } 543 } 544 if (error) { 545 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 546 static_cast<std::intmax_t>(frameOffsetInFile_), 547 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 548 // TODO: error recovery 549 } 550 positionInRecord = sizeof header; 551 } 552 553 void ExternalFileUnit::BeginVariableFormattedInputRecord( 554 IoErrorHandler &handler) { 555 if (this == defaultInput) { 556 if (defaultOutput) { 557 defaultOutput->FlushOutput(handler); 558 } 559 if (errorOutput) { 560 errorOutput->FlushOutput(handler); 561 } 562 } 563 std::size_t length{0}; 564 do { 565 std::size_t need{length + 1}; 566 length = 567 ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) - 568 recordOffsetInFrame_; 569 if (length < need) { 570 if (length > 0) { 571 // final record w/o \n 572 recordLength = length; 573 unterminatedRecord = true; 574 } else { 575 HitEndOnRead(handler); 576 } 577 break; 578 } 579 } while (!SetVariableFormattedRecordLength()); 580 } 581 582 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 583 RUNTIME_CHECK(handler, openRecl.has_value()); 584 if (frameOffsetInFile_ < *openRecl) { 585 handler.SignalError(IostatBackspaceAtFirstRecord); 586 } else { 587 frameOffsetInFile_ -= *openRecl; 588 } 589 } 590 591 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 592 IoErrorHandler &handler) { 593 std::int32_t header{0}; 594 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 595 frameOffsetInFile_ += recordOffsetInFrame_; 596 recordOffsetInFrame_ = 0; 597 if (frameOffsetInFile_ <= headerBytes) { 598 handler.SignalError(IostatBackspaceAtFirstRecord); 599 return; 600 } 601 // Error conditions here cause crashes, not file format errors, because the 602 // validity of the file structure before the current record will have been 603 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 604 std::size_t got{ 605 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 606 if (static_cast<std::int64_t>(got) < headerBytes) { 607 handler.SignalError(IostatShortRead); 608 return; 609 } 610 recordLength = ReadHeaderOrFooter(0); 611 if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) { 612 handler.SignalError(IostatBadUnformattedRecord); 613 return; 614 } 615 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 616 auto need{static_cast<std::size_t>( 617 recordOffsetInFrame_ + sizeof header + *recordLength)}; 618 got = ReadFrame(frameOffsetInFile_, need, handler); 619 if (got < need) { 620 handler.SignalError(IostatShortRead); 621 return; 622 } 623 header = ReadHeaderOrFooter(recordOffsetInFrame_); 624 if (header != *recordLength) { 625 handler.SignalError(IostatBadUnformattedRecord); 626 return; 627 } 628 } 629 630 // There's no portable memrchr(), unfortunately, and strrchr() would 631 // fail on a record with a NUL, so we have to do it the hard way. 632 static RT_API_ATTRS const char *FindLastNewline( 633 const char *str, std::size_t length) { 634 for (const char *p{str + length}; p >= str; p--) { 635 if (*p == '\n') { 636 return p; 637 } 638 } 639 return nullptr; 640 } 641 642 void ExternalFileUnit::BackspaceVariableFormattedRecord( 643 IoErrorHandler &handler) { 644 // File offset of previous record's newline 645 auto prevNL{ 646 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 647 if (prevNL < 0) { 648 handler.SignalError(IostatBackspaceAtFirstRecord); 649 return; 650 } 651 while (true) { 652 if (frameOffsetInFile_ < prevNL) { 653 if (const char *p{ 654 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 655 recordOffsetInFrame_ = p - Frame() + 1; 656 recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 657 break; 658 } 659 } 660 if (frameOffsetInFile_ == 0) { 661 recordOffsetInFrame_ = 0; 662 recordLength = prevNL; 663 break; 664 } 665 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 666 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 667 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 668 if (got < need) { 669 handler.SignalError(IostatShortRead); 670 return; 671 } 672 } 673 if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') { 674 handler.SignalError(IostatMissingTerminator); 675 return; 676 } 677 if (*recordLength > 0 && 678 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 679 --*recordLength; 680 } 681 } 682 683 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 684 if (access != Access::Direct) { 685 if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) { 686 // Flush a partial record after non-advancing output 687 impliedEndfile_ = true; 688 } 689 if (impliedEndfile_ && mayPosition()) { 690 DoEndfile(handler); 691 } 692 } 693 impliedEndfile_ = false; 694 } 695 696 template <bool ANY_DIR, Direction DIR> 697 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 698 if (IsRecordFile() && access != Access::Direct) { 699 furthestPositionInRecord = 700 std::max(positionInRecord, furthestPositionInRecord); 701 if (leftTabLimit) { // last I/O was non-advancing 702 if (access == Access::Sequential && direction_ == Direction::Output) { 703 if constexpr (ANY_DIR || DIR == Direction::Output) { 704 // When DoEndfile() is called from BeginReadingRecord(), 705 // this call to AdvanceRecord() may appear as a recursion 706 // though it may never happen. Expose the call only 707 // under the constexpr direction check. 708 AdvanceRecord(handler); 709 } else { 710 // This check always fails if we are here. 711 RUNTIME_CHECK(handler, direction_ != Direction::Output); 712 } 713 } else { // Access::Stream or input 714 leftTabLimit.reset(); 715 ++currentRecordNumber; 716 } 717 } 718 endfileRecordNumber = currentRecordNumber; 719 } 720 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; 721 recordOffsetInFrame_ = 0; 722 FlushOutput(handler); 723 Truncate(frameOffsetInFile_, handler); 724 TruncateFrame(frameOffsetInFile_, handler); 725 BeginRecord(); 726 impliedEndfile_ = false; 727 anyWriteSinceLastPositioning_ = false; 728 } 729 730 template void ExternalFileUnit::DoEndfile(IoErrorHandler &handler); 731 template void ExternalFileUnit::DoEndfile<false, Direction::Output>( 732 IoErrorHandler &handler); 733 template void ExternalFileUnit::DoEndfile<false, Direction::Input>( 734 IoErrorHandler &handler); 735 736 void ExternalFileUnit::CommitWrites() { 737 frameOffsetInFile_ += 738 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 739 recordOffsetInFrame_ = 0; 740 BeginRecord(); 741 } 742 743 bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) { 744 if (access == Access::Direct) { 745 RUNTIME_CHECK(handler, openRecl); 746 if (!directAccessRecWasSet_) { 747 handler.SignalError( 748 "No REC= was specified for a data transfer with ACCESS='DIRECT'"); 749 return false; 750 } 751 } 752 return true; 753 } 754 755 void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) { 756 handler.SignalEnd(); 757 if (IsRecordFile() && access != Access::Direct) { 758 endfileRecordNumber = currentRecordNumber; 759 } 760 } 761 762 ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) { 763 OwningPtr<ChildIo> current{std::move(child_)}; 764 Terminator &terminator{parent.GetIoErrorHandler()}; 765 OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))}; 766 child_.reset(next.release()); 767 return *child_; 768 } 769 770 void ExternalFileUnit::PopChildIo(ChildIo &child) { 771 if (child_.get() != &child) { 772 child.parent().GetIoErrorHandler().Crash( 773 "ChildIo being popped is not top of stack"); 774 } 775 child_.reset(child.AcquirePrevious().release()); // deletes top child 776 } 777 778 std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) { 779 std::int32_t word; 780 char *wordPtr{reinterpret_cast<char *>(&word)}; 781 std::memcpy(wordPtr, Frame() + frameOffset, sizeof word); 782 if (swapEndianness_) { 783 SwapEndianness(wordPtr, sizeof word, sizeof word); 784 } 785 return word; 786 } 787 788 void ChildIo::EndIoStatement() { 789 io_.reset(); 790 u_.emplace<std::monostate>(); 791 } 792 793 Iostat ChildIo::CheckFormattingAndDirection( 794 bool unformatted, Direction direction) { 795 bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()}; 796 bool parentIsFormatted{parentIsInput 797 ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() != 798 nullptr 799 : parent_.get_if<FormattedIoStatementState<Direction::Output>>() != 800 nullptr}; 801 bool parentIsUnformatted{!parentIsFormatted}; 802 if (unformatted != parentIsUnformatted) { 803 return unformatted ? IostatUnformattedChildOnFormattedParent 804 : IostatFormattedChildOnUnformattedParent; 805 } else if (parentIsInput != (direction == Direction::Input)) { 806 return parentIsInput ? IostatChildOutputToInputParent 807 : IostatChildInputFromOutputParent; 808 } else { 809 return IostatOk; 810 } 811 } 812 813 RT_OFFLOAD_API_GROUP_END 814 } // namespace Fortran::runtime::io 815