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 "io-error.h" 11 #include "lock.h" 12 #include "unit-map.h" 13 14 namespace Fortran::runtime::io { 15 16 // The per-unit data structures are created on demand so that Fortran I/O 17 // should work without a Fortran main program. 18 static Lock unitMapLock; 19 static UnitMap *unitMap{nullptr}; 20 static ExternalFileUnit *defaultOutput{nullptr}; 21 22 void FlushOutputOnCrash(const Terminator &terminator) { 23 if (!defaultOutput) { 24 return; 25 } 26 CriticalSection critical{unitMapLock}; 27 if (defaultOutput) { 28 IoErrorHandler handler{terminator}; 29 handler.HasIoStat(); // prevent nested crash if flush has error 30 defaultOutput->Flush(handler); 31 } 32 } 33 34 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { 35 return GetUnitMap().LookUp(unit); 36 } 37 38 ExternalFileUnit &ExternalFileUnit::LookUpOrCrash( 39 int unit, const Terminator &terminator) { 40 ExternalFileUnit *file{LookUp(unit)}; 41 if (!file) { 42 terminator.Crash("Not an open I/O unit number: %d", unit); 43 } 44 return *file; 45 } 46 47 ExternalFileUnit &ExternalFileUnit::LookUpOrCreate( 48 int unit, const Terminator &terminator, bool *wasExtant) { 49 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); 50 } 51 52 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { 53 return GetUnitMap().LookUpForClose(unit); 54 } 55 56 int ExternalFileUnit::NewUnit(const Terminator &terminator) { 57 return GetUnitMap().NewUnit(terminator).unitNumber(); 58 } 59 60 void ExternalFileUnit::OpenUnit(OpenStatus status, Position position, 61 OwningPtr<char> &&newPath, std::size_t newPathLength, 62 IoErrorHandler &handler) { 63 if (IsOpen()) { 64 if (status == OpenStatus::Old && 65 (!newPath.get() || 66 (path() && pathLength() == newPathLength && 67 std::memcmp(path(), newPath.get(), newPathLength) == 0))) { 68 // OPEN of existing unit, STATUS='OLD', not new FILE= 69 newPath.reset(); 70 return; 71 } 72 // Otherwise, OPEN on open unit with new FILE= implies CLOSE 73 Flush(handler); 74 Close(CloseStatus::Keep, handler); 75 } 76 set_path(std::move(newPath), newPathLength); 77 Open(status, position, handler); 78 } 79 80 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { 81 Flush(handler); 82 Close(status, handler); 83 } 84 85 void ExternalFileUnit::DestroyClosed() { 86 GetUnitMap().DestroyClosed(*this); // destroys *this 87 } 88 89 UnitMap &ExternalFileUnit::GetUnitMap() { 90 if (unitMap) { 91 return *unitMap; 92 } 93 CriticalSection critical{unitMapLock}; 94 if (unitMap) { 95 return *unitMap; 96 } 97 Terminator terminator{__FILE__, __LINE__}; 98 unitMap = &New<UnitMap>{}(terminator); 99 ExternalFileUnit &out{ExternalFileUnit::LookUpOrCreate(6, terminator)}; 100 out.Predefine(1); 101 out.set_mayRead(false); 102 out.set_mayWrite(true); 103 out.set_mayPosition(false); 104 defaultOutput = &out; 105 ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)}; 106 in.Predefine(0); 107 in.set_mayRead(true); 108 in.set_mayWrite(false); 109 in.set_mayPosition(false); 110 // TODO: Set UTF-8 mode from the environment 111 return *unitMap; 112 } 113 114 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { 115 CriticalSection critical{unitMapLock}; 116 if (unitMap) { 117 unitMap->CloseAll(handler); 118 FreeMemoryAndNullify(unitMap); 119 } 120 defaultOutput = nullptr; 121 } 122 123 bool ExternalFileUnit::Emit( 124 const char *data, std::size_t bytes, IoErrorHandler &handler) { 125 auto furthestAfter{std::max(furthestPositionInRecord, 126 positionInRecord + static_cast<std::int64_t>(bytes))}; 127 if (furthestAfter > recordLength.value_or(furthestAfter)) { 128 handler.SignalError(IostatRecordWriteOverrun); 129 return false; 130 } 131 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); 132 std::memcpy(Frame() + positionInRecord, data, bytes); 133 positionInRecord += bytes; 134 furthestPositionInRecord = furthestAfter; 135 return true; 136 } 137 138 std::optional<char32_t> ExternalFileUnit::GetCurrentChar( 139 IoErrorHandler &handler) { 140 isReading_ = true; // TODO: manage read/write transitions 141 if (isUnformatted) { 142 handler.Crash("GetCurrentChar() called for unformatted input"); 143 return std::nullopt; 144 } 145 std::size_t chunk{256}; // for stream input 146 if (recordLength.has_value()) { 147 if (positionInRecord >= *recordLength) { 148 return std::nullopt; 149 } 150 chunk = *recordLength - positionInRecord; 151 } 152 auto at{recordOffsetInFrame_ + positionInRecord}; 153 std::size_t need{static_cast<std::size_t>(at + 1)}; 154 std::size_t want{need + chunk}; 155 auto got{ReadFrame(frameOffsetInFile_, want, handler)}; 156 if (got <= need) { 157 endfileRecordNumber = currentRecordNumber; 158 handler.SignalEnd(); 159 return std::nullopt; 160 } 161 const char *p{Frame() + at}; 162 if (isUTF8) { 163 // TODO: UTF-8 decoding 164 } 165 return *p; 166 } 167 168 void ExternalFileUnit::SetLeftTabLimit() { 169 leftTabLimit = furthestPositionInRecord; 170 positionInRecord = furthestPositionInRecord; 171 } 172 173 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 174 bool ok{true}; 175 if (isReading_) { 176 if (access == Access::Sequential) { 177 if (isUnformatted) { 178 NextSequentialUnformattedInputRecord(handler); 179 } else { 180 NextSequentialFormattedInputRecord(handler); 181 } 182 } 183 } else if (!isUnformatted) { 184 if (recordLength.has_value()) { 185 // fill fixed-size record 186 if (furthestPositionInRecord < *recordLength) { 187 WriteFrame(frameOffsetInFile_, *recordLength, handler); 188 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 189 ' ', *recordLength - furthestPositionInRecord); 190 } 191 } else { 192 positionInRecord = furthestPositionInRecord + 1; 193 ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF 194 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; 195 recordOffsetInFrame_ = 0; 196 } 197 } 198 ++currentRecordNumber; 199 positionInRecord = 0; 200 furthestPositionInRecord = 0; 201 leftTabLimit.reset(); 202 return ok; 203 } 204 205 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 206 if (!isReading_) { 207 handler.Crash("ExternalFileUnit::BackspaceRecord() called during writing"); 208 // TODO: create endfile record, &c. 209 } 210 if (access == Access::Sequential) { 211 if (isUnformatted) { 212 BackspaceSequentialUnformattedRecord(handler); 213 } else { 214 BackspaceSequentialFormattedRecord(handler); 215 } 216 } else { 217 // TODO 218 } 219 positionInRecord = 0; 220 furthestPositionInRecord = 0; 221 leftTabLimit.reset(); 222 } 223 224 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 225 if (isTerminal()) { 226 Flush(handler); 227 } 228 } 229 230 void ExternalFileUnit::EndIoStatement() { 231 frameOffsetInFile_ += recordOffsetInFrame_; 232 recordOffsetInFrame_ = 0; 233 io_.reset(); 234 u_.emplace<std::monostate>(); 235 lock_.Drop(); 236 } 237 238 void ExternalFileUnit::NextSequentialUnformattedInputRecord( 239 IoErrorHandler &handler) { 240 std::int32_t header{0}, footer{0}; 241 // Retain previous footer (if any) in frame for more efficient BACKSPACE 242 std::size_t retain{sizeof header}; 243 if (recordLength) { // not first record - advance to next 244 ++currentRecordNumber; 245 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 246 handler.SignalEnd(); 247 return; 248 } 249 frameOffsetInFile_ += 250 recordOffsetInFrame_ + *recordLength + 2 * sizeof header; 251 recordOffsetInFrame_ = 0; 252 } else { 253 retain = 0; 254 } 255 std::size_t need{retain + sizeof header}; 256 std::size_t got{ReadFrame(frameOffsetInFile_ - retain, need, handler)}; 257 // Try to emit informative errors to help debug corrupted files. 258 const char *error{nullptr}; 259 if (got < need) { 260 if (got == retain) { 261 handler.SignalEnd(); 262 } else { 263 error = "Unformatted sequential file input failed at record #%jd (file " 264 "offset %jd): truncated record header"; 265 } 266 } else { 267 std::memcpy(&header, Frame() + retain, sizeof header); 268 need = retain + header + 2 * sizeof header; 269 got = ReadFrame(frameOffsetInFile_ - retain, 270 need + sizeof header /* next one */, handler); 271 if (got < need) { 272 error = "Unformatted sequential file input failed at record #%jd (file " 273 "offset %jd): hit EOF reading record with length %jd bytes"; 274 } else { 275 const char *start{Frame() + retain + sizeof header}; 276 std::memcpy(&footer, start + header, sizeof footer); 277 if (footer != header) { 278 error = "Unformatted sequential file input failed at record #%jd (file " 279 "offset %jd): record header has length %jd that does not match " 280 "record footer (%jd)"; 281 } else { 282 recordLength = header; 283 } 284 } 285 } 286 if (error) { 287 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 288 static_cast<std::intmax_t>(frameOffsetInFile_), 289 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 290 } 291 positionInRecord = sizeof header; 292 } 293 294 void ExternalFileUnit::NextSequentialFormattedInputRecord( 295 IoErrorHandler &handler) { 296 static constexpr std::size_t chunk{256}; 297 std::size_t length{0}; 298 if (recordLength.has_value()) { 299 // not first record - advance to next 300 ++currentRecordNumber; 301 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 302 handler.SignalEnd(); 303 return; 304 } 305 if (Frame()[*recordLength] == '\r') { 306 ++*recordLength; 307 } 308 recordOffsetInFrame_ += *recordLength + 1; 309 } 310 while (true) { 311 std::size_t got{ReadFrame( 312 frameOffsetInFile_, recordOffsetInFrame_ + length + chunk, handler)}; 313 if (got <= recordOffsetInFrame_ + length) { 314 handler.SignalEnd(); 315 break; 316 } 317 const char *frame{Frame() + recordOffsetInFrame_}; 318 if (const char *nl{reinterpret_cast<const char *>( 319 std::memchr(frame + length, '\n', chunk))}) { 320 recordLength = nl - (frame + length) + 1; 321 if (*recordLength > 0 && frame[*recordLength - 1] == '\r') { 322 --*recordLength; 323 } 324 return; 325 } 326 length += got; 327 } 328 } 329 330 void ExternalFileUnit::BackspaceSequentialUnformattedRecord( 331 IoErrorHandler &handler) { 332 std::int32_t header{0}, footer{0}; 333 RUNTIME_CHECK(handler, currentRecordNumber > 1); 334 --currentRecordNumber; 335 int overhead{static_cast<int>(2 * sizeof header)}; 336 // Error conditions here cause crashes, not file format errors, because the 337 // validity of the file structure before the current record will have been 338 // checked informatively in NextSequentialUnformattedInputRecord(). 339 RUNTIME_CHECK(handler, frameOffsetInFile_ >= overhead); 340 std::size_t got{ 341 ReadFrame(frameOffsetInFile_ - sizeof footer, sizeof footer, handler)}; 342 RUNTIME_CHECK(handler, got >= sizeof footer); 343 std::memcpy(&footer, Frame(), sizeof footer); 344 RUNTIME_CHECK(handler, frameOffsetInFile_ >= footer + overhead); 345 frameOffsetInFile_ -= footer + 2 * sizeof footer; 346 auto extra{std::max<std::size_t>(sizeof footer, frameOffsetInFile_)}; 347 std::size_t want{extra + footer + 2 * sizeof footer}; 348 got = ReadFrame(frameOffsetInFile_ - extra, want, handler); 349 RUNTIME_CHECK(handler, got >= want); 350 std::memcpy(&header, Frame() + extra, sizeof header); 351 RUNTIME_CHECK(handler, header == footer); 352 positionInRecord = sizeof header; 353 recordLength = footer; 354 } 355 356 // There's no portable memrchr(), unfortunately, and strrchr() would 357 // fail on a record with a NUL, so we have to do it the hard way. 358 static const char *FindLastNewline(const char *str, std::size_t length) { 359 for (const char *p{str + length}; p-- > str;) { 360 if (*p == '\n') { 361 return p; 362 } 363 } 364 return nullptr; 365 } 366 367 void ExternalFileUnit::BackspaceSequentialFormattedRecord( 368 IoErrorHandler &handler) { 369 std::int64_t start{frameOffsetInFile_ + recordOffsetInFrame_}; 370 --currentRecordNumber; 371 RUNTIME_CHECK(handler, currentRecordNumber > 0); 372 if (currentRecordNumber == 1) { 373 // To simplify the code below, treat a backspace to the first record 374 // as a special case; 375 RUNTIME_CHECK(handler, start > 0); 376 *recordLength = start - 1; 377 frameOffsetInFile_ = 0; 378 recordOffsetInFrame_ = 0; 379 ReadFrame(0, *recordLength + 1, handler); 380 } else { 381 RUNTIME_CHECK(handler, start > 1); 382 std::int64_t at{start - 2}; // byte before previous record's newline 383 while (true) { 384 if (const char *p{ 385 FindLastNewline(Frame(), at - frameOffsetInFile_ + 1)}) { 386 // This is the newline that ends the record before the previous one. 387 recordOffsetInFrame_ = p - Frame() + 1; 388 *recordLength = start - 1 - (frameOffsetInFile_ + recordOffsetInFrame_); 389 break; 390 } 391 RUNTIME_CHECK(handler, frameOffsetInFile_ > 0); 392 at = frameOffsetInFile_ - 1; 393 if (auto bytesBefore{BytesBufferedBeforeFrame()}) { 394 frameOffsetInFile_ = FrameAt() - bytesBefore; 395 } else { 396 static constexpr int chunk{1024}; 397 frameOffsetInFile_ = std::max<std::int64_t>(0, at - chunk); 398 } 399 std::size_t want{static_cast<std::size_t>(start - frameOffsetInFile_)}; 400 std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)}; 401 RUNTIME_CHECK(handler, got >= want); 402 } 403 } 404 std::size_t want{ 405 static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength + 1)}; 406 RUNTIME_CHECK(handler, FrameLength() >= want); 407 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 408 if (*recordLength > 0 && 409 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 410 --*recordLength; 411 } 412 } 413 } // namespace Fortran::runtime::io 414