brintos

brintos / llvm-project-archived public Read only

0
0
Text · 29.9 KiB · bc98cfd Raw
882 lines · cpp
1//===-- lib/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-exception6//7//===----------------------------------------------------------------------===//8//9// Implementation of ExternalFileUnit common for both10// RT_USE_PSEUDO_FILE_UNIT=0 and RT_USE_PSEUDO_FILE_UNIT=1.11//12//===----------------------------------------------------------------------===//13#include "unit.h"14#include "flang-rt/runtime/io-error.h"15#include "flang-rt/runtime/lock.h"16#include "flang-rt/runtime/tools.h"17#include <limits>18#include <utility>19 20namespace Fortran::runtime::io {21 22#ifndef FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS23RT_OFFLOAD_VAR_GROUP_BEGIN24RT_VAR_ATTRS ExternalFileUnit *defaultInput{nullptr}; // unit 525RT_VAR_ATTRS ExternalFileUnit *defaultOutput{nullptr}; // unit 626RT_VAR_ATTRS ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension27RT_OFFLOAD_VAR_GROUP_END28#endif // FLANG_RUNTIME_NO_GLOBAL_VAR_DEFS29 30RT_OFFLOAD_API_GROUP_BEGIN31 32static 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_PUSH39        RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN40        std::swap(data[j + k], data[j + elementBytes - 1 - k]);41        RT_DIAG_POP42      }43    }44  }45}46 47bool 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 for53    // 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 + footer59        header = static_cast<int>(sizeof(std::uint32_t));60        extra = 2 * header;61      } else {62#ifdef _WIN3263        if (!isWindowsTextFile()) {64          ++extra; // carriage return (CR)65        }66#endif67        ++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 a81    // variable-length output record if the previous operation82    // 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    runtime::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,94        ' ', positionInRecord - furthestPositionInRecord);95  }96  char *to{Frame() + recordOffsetInFrame_ + positionInRecord};97  runtime::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 107bool 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    runtime::memcpy(123        data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);124    if (swapEndianness_) {125      SwapEndianness(data, bytes, elementBytes);126    }127    positionInRecord += bytes;128    furthestPositionInRecord = furthestAfter;129    return true;130  } else {131    HitEndOnRead(handler);132    return false;133  }134}135 136std::size_t ExternalFileUnit::GetNextInputBytes(137    const char *&p, IoErrorHandler &handler) {138  RUNTIME_CHECK(handler, direction_ == Direction::Input);139  if (access == Access::Sequential &&140      positionInRecord < recordLength.value_or(positionInRecord)) {141    // Fast path for variable-length formatted input: the whole record142    // must be in frame as a result of newline detection for record length.143    p = Frame() + recordOffsetInFrame_ + positionInRecord;144    return *recordLength - positionInRecord;145  }146  std::size_t length{1};147  if (auto recl{EffectiveRecordLength()}) {148    if (positionInRecord < *recl) {149      length = *recl - positionInRecord;150    } else {151      p = nullptr;152      return 0;153    }154  }155  p = FrameNextInput(handler, length);156  return p ? length : 0;157}158 159std::size_t ExternalFileUnit::ViewBytesInRecord(160    const char *&p, bool forward) const {161  p = nullptr;162  auto recl{recordLength.value_or(positionInRecord)};163  if (forward) {164    if (positionInRecord < recl) {165      p = Frame() + recordOffsetInFrame_ + positionInRecord;166      return recl - positionInRecord;167    }168  } else {169    if (positionInRecord <= recl) {170      p = Frame() + recordOffsetInFrame_ + positionInRecord;171    }172    return positionInRecord - leftTabLimit.value_or(0);173  }174  return 0;175}176 177const char *ExternalFileUnit::FrameNextInput(178    IoErrorHandler &handler, std::size_t bytes) {179  RUNTIME_CHECK(handler, isUnformatted.has_value() && !*isUnformatted);180  if (static_cast<std::int64_t>(positionInRecord + bytes) <=181      recordLength.value_or(positionInRecord + bytes)) {182    auto at{recordOffsetInFrame_ + positionInRecord};183    auto need{static_cast<std::size_t>(at + bytes)};184    auto got{ReadFrame(frameOffsetInFile_, need, handler)};185    SetVariableFormattedRecordLength();186    if (got >= need) {187      return Frame() + at;188    }189    HitEndOnRead(handler);190  }191  return nullptr;192}193 194bool ExternalFileUnit::SetVariableFormattedRecordLength() {195  if (recordLength || access == Access::Direct) {196    return true;197  } else if (FrameLength() > recordOffsetInFrame_) {198    const char *record{Frame() + recordOffsetInFrame_};199    std::size_t bytes{FrameLength() - recordOffsetInFrame_};200    if (const char *nl{FindCharacter(record, '\n', bytes)}) {201      recordLength = nl - record;202      if (*recordLength > 0 && record[*recordLength - 1] == '\r') {203        --*recordLength;204      }205      return true;206    }207  }208  return false;209}210 211bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {212  RUNTIME_CHECK(handler, direction_ == Direction::Input);213  if (!beganReadingRecord_) {214    beganReadingRecord_ = true;215    // Don't use IsAtEOF() to check for an EOF condition here, just detect216    // it from a failed or short read from the file.  IsAtEOF() could be217    // wrong for formatted input if actual newline characters had been218    // written in-band by previous WRITEs before a REWIND.  In fact,219    // now that we know that the unit is being used for input (again),220    // it's best to reset endfileRecordNumber and ensure IsAtEOF() will221    // now be true on return only if it gets set by HitEndOnRead().222    endfileRecordNumber.reset();223    if (access == Access::Direct) {224      CheckDirectAccess(handler);225      auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *openRecl)};226      auto got{ReadFrame(frameOffsetInFile_, need, handler)};227      if (got >= need) {228        recordLength = openRecl;229      } else {230        recordLength.reset();231        HitEndOnRead(handler);232      }233    } else {234      if (anyWriteSinceLastPositioning_ && access == Access::Sequential) {235        // Most Fortran implementations allow a READ after a WRITE;236        // the read then just hits an EOF.237        DoEndfile<false, Direction::Input>(handler);238      }239      recordLength.reset();240      RUNTIME_CHECK(handler, isUnformatted.has_value());241      if (*isUnformatted) {242        if (access == Access::Sequential) {243          BeginSequentialVariableUnformattedInputRecord(handler);244        }245      } else { // formatted sequential or stream246        BeginVariableFormattedInputRecord(handler);247      }248    }249  }250  RUNTIME_CHECK(handler,251      recordLength.has_value() || !IsRecordFile() || handler.InError());252  return !handler.InError();253}254 255void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {256  RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);257  beganReadingRecord_ = false;258  if (handler.GetIoStat() == IostatEnd ||259      (IsRecordFile() && !recordLength.has_value())) {260    // Avoid bogus crashes in END/ERR circumstances; but261    // still increment the current record number so that262    // an attempted read of an endfile record, followed by263    // a BACKSPACE, will still be at EOF.264    ++currentRecordNumber;265  } else if (IsRecordFile()) {266    recordOffsetInFrame_ += *recordLength;267    if (access != Access::Direct) {268      RUNTIME_CHECK(handler, isUnformatted.has_value());269      recordLength.reset();270      if (isUnformatted.value_or(false)) {271        // Retain footer in frame for more efficient BACKSPACE272        frameOffsetInFile_ += recordOffsetInFrame_;273        recordOffsetInFrame_ = sizeof(std::uint32_t);274      } else { // formatted275        if (FrameLength() > recordOffsetInFrame_ &&276            Frame()[recordOffsetInFrame_] == '\r') {277          ++recordOffsetInFrame_;278        }279        if (FrameLength() > recordOffsetInFrame_ &&280            Frame()[recordOffsetInFrame_] == '\n') {281          ++recordOffsetInFrame_;282        }283        if (!pinnedFrame || mayPosition()) {284          frameOffsetInFile_ += recordOffsetInFrame_;285          recordOffsetInFrame_ = 0;286        }287      }288    }289    ++currentRecordNumber;290  } else { // unformatted stream291    furthestPositionInRecord =292        std::max(furthestPositionInRecord, positionInRecord);293    frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;294    recordOffsetInFrame_ = 0;295  }296  BeginRecord();297  leftTabLimit.reset();298}299 300bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {301  if (direction_ == Direction::Input) {302    FinishReadingRecord(handler);303    return BeginReadingRecord(handler);304  } else { // Direction::Output305    bool ok{true};306    RUNTIME_CHECK(handler, isUnformatted.has_value());307    positionInRecord = furthestPositionInRecord;308    if (access == Access::Direct) {309      if (furthestPositionInRecord <310          openRecl.value_or(furthestPositionInRecord)) {311        // Pad remainder of fixed length record312        WriteFrame(313            frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler);314        runtime::memset(315            Frame() + recordOffsetInFrame_ + furthestPositionInRecord,316            isUnformatted.value_or(false) ? 0 : ' ',317            *openRecl - furthestPositionInRecord);318        furthestPositionInRecord = *openRecl;319      }320    } else if (*isUnformatted) {321      if (access == Access::Sequential) {322        // Append the length of a sequential unformatted variable-length record323        // as its footer, then overwrite the reserved first four bytes of the324        // record with its length as its header.  These four bytes were skipped325        // over in BeginUnformattedIO<Output>().326        // TODO: Break very large records up into subrecords with negative327        // headers &/or footers328        std::uint32_t length;329        length = furthestPositionInRecord - sizeof length;330        ok = ok &&331            Emit(reinterpret_cast<const char *>(&length), sizeof length,332                sizeof length, handler);333        positionInRecord = 0;334        ok = ok &&335            Emit(reinterpret_cast<const char *>(&length), sizeof length,336                sizeof length, handler);337      } else {338        // Unformatted stream: nothing to do339      }340    } else if (handler.GetIoStat() != IostatOk &&341        furthestPositionInRecord == 0) {342      // Error in formatted variable length record, and no output yet; do343      // nothing, like most other Fortran compilers do.344      return true;345    } else {346      // Terminate formatted variable length record347      const char *lineEnding{"\n"};348      std::size_t lineEndingBytes{1};349#ifdef _WIN32350      if (!isWindowsTextFile()) {351        lineEnding = "\r\n";352        lineEndingBytes = 2;353      }354#endif355      ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler);356    }357    leftTabLimit.reset();358    if (IsAfterEndfile()) {359      return false;360    }361    CommitWrites();362    ++currentRecordNumber;363    if (access != Access::Direct) {364      impliedEndfile_ = IsRecordFile();365      if (IsAtEOF()) {366        endfileRecordNumber.reset();367      }368    }369    return ok;370  }371}372 373void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {374  if (access == Access::Direct || !IsRecordFile()) {375    handler.SignalError(IostatBackspaceNonSequential,376        "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream",377        unitNumber());378  } else {379    if (IsAfterEndfile()) {380      // BACKSPACE after explicit ENDFILE381      currentRecordNumber = *endfileRecordNumber;382    } else if (leftTabLimit && direction_ == Direction::Input) {383      // BACKSPACE after non-advancing input384      leftTabLimit.reset();385    } else {386      DoImpliedEndfile(handler);387      if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {388        --currentRecordNumber;389        if (openRecl && access == Access::Direct) {390          BackspaceFixedRecord(handler);391        } else {392          RUNTIME_CHECK(handler, isUnformatted.has_value());393          if (isUnformatted.value_or(false)) {394            BackspaceVariableUnformattedRecord(handler);395          } else {396            BackspaceVariableFormattedRecord(handler);397          }398        }399      }400    }401    BeginRecord();402    anyWriteSinceLastPositioning_ = false;403  }404}405 406void ExternalFileUnit::FlushOutput(IoErrorHandler &handler) {407  if (!mayPosition()) {408    auto frameAt{FrameAt()};409    if (frameOffsetInFile_ >= frameAt &&410        frameOffsetInFile_ <411            static_cast<std::int64_t>(frameAt + FrameLength())) {412      // A Flush() that's about to happen to a non-positionable file413      // needs to advance frameOffsetInFile_ to prevent attempts at414      // impossible seeks415      CommitWrites();416      leftTabLimit.reset();417    }418  }419  Flush(handler);420}421 422void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {423  if (isTerminal()) {424    FlushOutput(handler);425  }426}427 428void ExternalFileUnit::Endfile(IoErrorHandler &handler) {429  if (access == Access::Direct) {430    handler.SignalError(IostatEndfileDirect,431        "ENDFILE(UNIT=%d) on direct-access file", unitNumber());432  } else if (!mayWrite()) {433    handler.SignalError(IostatEndfileUnwritable,434        "ENDFILE(UNIT=%d) on read-only file", unitNumber());435  } else if (IsAfterEndfile()) {436    // ENDFILE after ENDFILE437  } else {438    DoEndfile(handler);439    if (IsRecordFile() && access != Access::Direct) {440      // Explicit ENDFILE leaves position *after* the endfile record441      RUNTIME_CHECK(handler, endfileRecordNumber.has_value());442      currentRecordNumber = *endfileRecordNumber + 1;443    }444  }445}446 447void ExternalFileUnit::Rewind(IoErrorHandler &handler) {448  if (access == Access::Direct) {449    handler.SignalError(IostatRewindNonSequential,450        "REWIND(UNIT=%d) on non-sequential file", unitNumber());451  } else {452    DoImpliedEndfile(handler);453    SetPosition(0);454    currentRecordNumber = 1;455    anyWriteSinceLastPositioning_ = false;456  }457}458 459void ExternalFileUnit::SetPosition(std::int64_t pos) {460  frameOffsetInFile_ = pos;461  recordOffsetInFrame_ = 0;462  if (access == Access::Direct) {463    directAccessRecWasSet_ = true;464  }465  BeginRecord();466  beganReadingRecord_ = false; // for positioning after nonadvancing input467  leftTabLimit.reset();468}469 470void ExternalFileUnit::Sought(std::int64_t zeroBasedPos) {471  SetPosition(zeroBasedPos);472  if (zeroBasedPos == 0) {473    currentRecordNumber = 1;474  } else {475    // We no longer know which record we're in.  Set currentRecordNumber to476    // a large value from whence we can both advance and backspace.477    currentRecordNumber = std::numeric_limits<std::int64_t>::max() / 2;478    endfileRecordNumber.reset();479  }480}481 482bool ExternalFileUnit::SetStreamPos(483    std::int64_t oneBasedPos, IoErrorHandler &handler) {484  if (access != Access::Stream) {485    handler.SignalError("POS= may not appear unless ACCESS='STREAM'");486    return false;487  }488  if (oneBasedPos < 1) { // POS=1 is beginning of file (12.6.2.11)489    handler.SignalError(490        "POS=%zd is invalid", static_cast<std::intmax_t>(oneBasedPos));491    return false;492  }493  // A backwards POS= implies truncation after writing, at least in494  // Intel and NAG.495  if (static_cast<std::size_t>(oneBasedPos - 1) <496      frameOffsetInFile_ + recordOffsetInFrame_) {497    DoImpliedEndfile(handler);498  }499  Sought(oneBasedPos - 1);500  return true;501}502 503// GNU FSEEK extension504RT_API_ATTRS bool ExternalFileUnit::Fseek(std::int64_t zeroBasedPos,505    enum FseekWhence whence, IoErrorHandler &handler) {506  if (whence == FseekEnd) {507    Flush(handler); // updates knownSize_508    if (auto size{knownSize()}) {509      zeroBasedPos += *size;510    } else {511      return false;512    }513  } else if (whence == FseekCurrent) {514    zeroBasedPos += InquirePos() - 1;515  }516  if (zeroBasedPos >= 0) {517    Sought(zeroBasedPos);518    return true;519  } else {520    return false;521  }522}523 524bool ExternalFileUnit::SetDirectRec(525    std::int64_t oneBasedRec, IoErrorHandler &handler) {526  if (access != Access::Direct) {527    handler.SignalError("REC= may not appear unless ACCESS='DIRECT'");528    return false;529  }530  if (!openRecl) {531    handler.SignalError("RECL= was not specified");532    return false;533  }534  if (oneBasedRec < 1) {535    handler.SignalError(536        "REC=%zd is invalid", static_cast<std::intmax_t>(oneBasedRec));537    return false;538  }539  currentRecordNumber = oneBasedRec;540  SetPosition((oneBasedRec - 1) * *openRecl);541  return true;542}543 544void ExternalFileUnit::EndIoStatement() {545  io_.reset();546  u_.emplace<std::monostate>();547  lock_.Drop();548}549 550void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(551    IoErrorHandler &handler) {552  RUNTIME_CHECK(handler, access == Access::Sequential);553  std::uint32_t header{0}, footer{0};554  std::size_t need{recordOffsetInFrame_ + sizeof header};555  std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};556  // Try to emit informative errors to help debug corrupted files.557  const char *error{nullptr};558  if (got < need) {559    if (got == recordOffsetInFrame_) {560      HitEndOnRead(handler);561    } else {562      error = "Unformatted variable-length sequential file input failed at "563              "record #%jd (file offset %jd): truncated record header";564    }565  } else {566    header = ReadHeaderOrFooter(recordOffsetInFrame_);567    recordLength = sizeof header + header; // does not include footer568    need = recordOffsetInFrame_ + *recordLength + sizeof footer;569    got = ReadFrame(frameOffsetInFile_, need, handler);570    if (got >= need) {571      footer = ReadHeaderOrFooter(recordOffsetInFrame_ + *recordLength);572    }573    if (frameOffsetInFile_ == 0 && recordOffsetInFrame_ == 0 &&574        (got < need || footer != header)) {575      // Maybe an omitted or incorrect byte swap flag setting?576      // Try it the other way, since this is the first record.577      // (N.B. Won't work on files starting with empty records, but there's578      // no good way to know later if all preceding records were empty.)579      swapEndianness_ = !swapEndianness_;580      std::uint32_t header2{ReadHeaderOrFooter(0)};581      std::size_t recordLength2{sizeof header2 + header2};582      std::size_t need2{recordLength2 + sizeof footer};583      std::size_t got2{ReadFrame(0, need2, handler)};584      if (got2 >= need2) {585        std::uint32_t footer2{ReadHeaderOrFooter(recordLength2)};586        if (footer2 == header2) {587          error = "Unformatted variable-length sequential file input "588                  "failed on the first record, probably due to a need "589                  "for byte order data conversion; consider adding "590                  "CONVERT='SWAP' to the OPEN statement or adding "591                  "FORT_CONVERT=SWAP to the execution environment";592        }593      }594      swapEndianness_ = !swapEndianness_;595    }596    if (error) {597    } else if (got < need) {598      error = "Unformatted variable-length sequential file input failed at "599              "record #%jd (file offset %jd): hit EOF reading record with "600              "length %jd bytes";601    } else if (footer != header) {602      error = "Unformatted variable-length sequential file input failed at "603              "record #%jd (file offset %jd): record header has length %jd "604              "that does not match record footer (%jd)";605    }606  }607  if (error) {608    handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),609        static_cast<std::intmax_t>(frameOffsetInFile_),610        static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));611    // TODO: error recovery612  }613  positionInRecord = sizeof header;614}615 616void ExternalFileUnit::BeginVariableFormattedInputRecord(617    IoErrorHandler &handler) {618  if (this == defaultInput) {619    if (defaultOutput) {620      defaultOutput->FlushOutput(handler);621    }622    if (errorOutput) {623      errorOutput->FlushOutput(handler);624    }625  }626  std::size_t length{0};627  do {628    std::size_t need{length + 1};629    length =630        ReadFrame(frameOffsetInFile_, recordOffsetInFrame_ + need, handler) -631        recordOffsetInFrame_;632    if (length < need) {633      if (length > 0) {634        // final record w/o \n635        recordLength = length;636        unterminatedRecord = true;637      } else {638        HitEndOnRead(handler);639      }640      break;641    }642  } while (!SetVariableFormattedRecordLength());643}644 645void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {646  RUNTIME_CHECK(handler, openRecl.has_value());647  if (frameOffsetInFile_ < *openRecl) {648    handler.SignalError(IostatBackspaceAtFirstRecord);649  } else {650    frameOffsetInFile_ -= *openRecl;651  }652}653 654void ExternalFileUnit::BackspaceVariableUnformattedRecord(655    IoErrorHandler &handler) {656  std::uint32_t header{0};657  auto headerBytes{static_cast<std::int64_t>(sizeof header)};658  frameOffsetInFile_ += recordOffsetInFrame_;659  recordOffsetInFrame_ = 0;660  if (frameOffsetInFile_ <= headerBytes) {661    handler.SignalError(IostatBackspaceAtFirstRecord);662    return;663  }664  // Error conditions here cause crashes, not file format errors, because the665  // validity of the file structure before the current record will have been666  // checked informatively in NextSequentialVariableUnformattedInputRecord().667  std::size_t got{668      ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};669  if (static_cast<std::int64_t>(got) < headerBytes) {670    handler.SignalError(IostatShortRead);671    return;672  }673  recordLength = ReadHeaderOrFooter(0);674  if (frameOffsetInFile_ < *recordLength + 2 * headerBytes) {675    handler.SignalError(IostatBadUnformattedRecord);676    return;677  }678  frameOffsetInFile_ -= *recordLength + 2 * headerBytes;679  auto need{static_cast<std::size_t>(680      recordOffsetInFrame_ + sizeof header + *recordLength)};681  got = ReadFrame(frameOffsetInFile_, need, handler);682  if (got < need) {683    handler.SignalError(IostatShortRead);684    return;685  }686  header = ReadHeaderOrFooter(recordOffsetInFrame_);687  if (header != *recordLength) {688    handler.SignalError(IostatBadUnformattedRecord);689    return;690  }691}692 693// There's no portable memrchr(), unfortunately, and strrchr() would694// fail on a record with a NUL, so we have to do it the hard way.695static RT_API_ATTRS const char *FindLastNewline(696    const char *str, std::size_t length) {697  for (const char *p{str + length}; p >= str; p--) {698    if (*p == '\n') {699      return p;700    }701  }702  return nullptr;703}704 705void ExternalFileUnit::BackspaceVariableFormattedRecord(706    IoErrorHandler &handler) {707  // File offset of previous record's newline708  auto prevNL{709      frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};710  if (prevNL < 0) {711    handler.SignalError(IostatBackspaceAtFirstRecord);712    return;713  }714  while (true) {715    if (frameOffsetInFile_ < prevNL) {716      if (const char *p{717              FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {718        recordOffsetInFrame_ = p - Frame() + 1;719        recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);720        break;721      }722    }723    if (frameOffsetInFile_ == 0) {724      recordOffsetInFrame_ = 0;725      recordLength = prevNL;726      break;727    }728    frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);729    auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};730    auto got{ReadFrame(frameOffsetInFile_, need, handler)};731    if (got < need) {732      handler.SignalError(IostatShortRead);733      return;734    }735  }736  if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {737    handler.SignalError(IostatMissingTerminator);738    return;739  }740  if (*recordLength > 0 &&741      Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {742    --*recordLength;743  }744}745 746void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {747  if (access != Access::Direct) {748    if (!impliedEndfile_ && leftTabLimit && direction_ == Direction::Output) {749      // Flush a partial record after non-advancing output750      impliedEndfile_ = true;751    }752    if (impliedEndfile_ && mayPosition()) {753      DoEndfile(handler);754    }755  }756  impliedEndfile_ = false;757}758 759template <bool ANY_DIR, Direction DIR>760void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {761  if (IsRecordFile() && access != Access::Direct) {762    furthestPositionInRecord =763        std::max(positionInRecord, furthestPositionInRecord);764    if (leftTabLimit) { // last I/O was non-advancing765      if (access == Access::Sequential && direction_ == Direction::Output) {766        if constexpr (ANY_DIR || DIR == Direction::Output) {767          // When DoEndfile() is called from BeginReadingRecord(),768          // this call to AdvanceRecord() may appear as a recursion769          // though it may never happen. Expose the call only770          // under the constexpr direction check.771          AdvanceRecord(handler);772        } else {773          // This check always fails if we are here.774          RUNTIME_CHECK(handler, direction_ != Direction::Output);775        }776      } else { // Access::Stream or input777        leftTabLimit.reset();778        ++currentRecordNumber;779      }780    }781    endfileRecordNumber = currentRecordNumber;782  }783  frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;784  recordOffsetInFrame_ = 0;785  FlushOutput(handler);786  if (access != Access::Stream || executionEnvironment.truncateStream) {787    // Stream output after positioning truncates with some compilers.788    Truncate(frameOffsetInFile_, handler);789    TruncateFrame(frameOffsetInFile_, handler);790  }791  BeginRecord();792  impliedEndfile_ = false;793  anyWriteSinceLastPositioning_ = false;794}795 796template void ExternalFileUnit::DoEndfile(IoErrorHandler &handler);797template void ExternalFileUnit::DoEndfile<false, Direction::Output>(798    IoErrorHandler &handler);799template void ExternalFileUnit::DoEndfile<false, Direction::Input>(800    IoErrorHandler &handler);801 802void ExternalFileUnit::CommitWrites() {803  frameOffsetInFile_ +=804      recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);805  recordOffsetInFrame_ = 0;806  BeginRecord();807}808 809bool ExternalFileUnit::CheckDirectAccess(IoErrorHandler &handler) {810  if (access == Access::Direct) {811    RUNTIME_CHECK(handler, openRecl);812    if (!directAccessRecWasSet_) {813      handler.SignalError(814          "No REC= was specified for a data transfer with ACCESS='DIRECT'");815      return false;816    }817  }818  return true;819}820 821void ExternalFileUnit::HitEndOnRead(IoErrorHandler &handler) {822  handler.SignalEnd();823  if (IsRecordFile() && access != Access::Direct) {824    endfileRecordNumber = currentRecordNumber;825  }826}827 828ChildIo &ExternalFileUnit::PushChildIo(IoStatementState &parent) {829  OwningPtr<ChildIo> current{std::move(child_)};830  Terminator &terminator{parent.GetIoErrorHandler()};831  OwningPtr<ChildIo> next{New<ChildIo>{terminator}(parent, std::move(current))};832  child_.reset(next.release());833  leftTabLimit = positionInRecord;834  return *child_;835}836 837void ExternalFileUnit::PopChildIo(ChildIo &child) {838  if (child_.get() != &child) {839    child.parent().GetIoErrorHandler().Crash(840        "ChildIo being popped is not top of stack");841  }842  child_.reset(child.AcquirePrevious().release()); // deletes top child843}844 845std::uint32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {846  std::uint32_t word;847  char *wordPtr{reinterpret_cast<char *>(&word)};848  runtime::memcpy(wordPtr, Frame() + frameOffset, sizeof word);849  if (swapEndianness_) {850    SwapEndianness(wordPtr, sizeof word, sizeof word);851  }852  return word;853}854 855void ChildIo::EndIoStatement() {856  io_.reset();857  u_.emplace<std::monostate>();858}859 860Iostat ChildIo::CheckFormattingAndDirection(861    bool unformatted, Direction direction) {862  bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};863  bool parentIsFormatted{parentIsInput864          ? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=865              nullptr866          : parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=867              nullptr};868  bool parentIsUnformatted{!parentIsFormatted};869  if (unformatted != parentIsUnformatted) {870    return unformatted ? IostatUnformattedChildOnFormattedParent871                       : IostatFormattedChildOnUnformattedParent;872  } else if (parentIsInput != (direction == Direction::Input)) {873    return parentIsInput ? IostatChildOutputToInputParent874                         : IostatChildInputFromOutputParent;875  } else {876    return IostatOk;877  }878}879 880RT_OFFLOAD_API_GROUP_END881} // namespace Fortran::runtime::io882