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 *defaultInput{nullptr}; 21 static ExternalFileUnit *defaultOutput{nullptr}; 22 23 void FlushOutputOnCrash(const Terminator &terminator) { 24 if (!defaultOutput) { 25 return; 26 } 27 CriticalSection critical{unitMapLock}; 28 if (defaultOutput) { 29 IoErrorHandler handler{terminator}; 30 handler.HasIoStat(); // prevent nested crash if flush has error 31 defaultOutput->Flush(handler); 32 } 33 } 34 35 ExternalFileUnit *ExternalFileUnit::LookUp(int unit) { 36 return GetUnitMap().LookUp(unit); 37 } 38 39 ExternalFileUnit &ExternalFileUnit::LookUpOrCrash( 40 int unit, const Terminator &terminator) { 41 ExternalFileUnit *file{LookUp(unit)}; 42 if (!file) { 43 terminator.Crash("Not an open I/O unit number: %d", unit); 44 } 45 return *file; 46 } 47 48 ExternalFileUnit &ExternalFileUnit::LookUpOrCreate( 49 int unit, const Terminator &terminator, bool *wasExtant) { 50 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant); 51 } 52 53 ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) { 54 return GetUnitMap().LookUpForClose(unit); 55 } 56 57 int ExternalFileUnit::NewUnit(const Terminator &terminator) { 58 return GetUnitMap().NewUnit(terminator).unitNumber(); 59 } 60 61 void ExternalFileUnit::OpenUnit(OpenStatus status, Position position, 62 OwningPtr<char> &&newPath, std::size_t newPathLength, 63 IoErrorHandler &handler) { 64 if (IsOpen()) { 65 if (status == OpenStatus::Old && 66 (!newPath.get() || 67 (path() && pathLength() == newPathLength && 68 std::memcmp(path(), newPath.get(), newPathLength) == 0))) { 69 // OPEN of existing unit, STATUS='OLD', not new FILE= 70 newPath.reset(); 71 return; 72 } 73 // Otherwise, OPEN on open unit with new FILE= implies CLOSE 74 DoImpliedEndfile(handler); 75 Flush(handler); 76 Close(CloseStatus::Keep, handler); 77 } 78 set_path(std::move(newPath), newPathLength); 79 Open(status, position, handler); 80 auto totalBytes{knownSize()}; 81 if (access == Access::Direct) { 82 if (!isFixedRecordLength || !recordLength) { 83 handler.SignalError(IostatOpenBadRecl, 84 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known", 85 unitNumber()); 86 } else if (*recordLength <= 0) { 87 handler.SignalError(IostatOpenBadRecl, 88 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid", 89 unitNumber(), static_cast<std::intmax_t>(*recordLength)); 90 } else if (!totalBytes) { 91 handler.SignalError(IostatOpenUnknownSize, 92 "OPEN(UNIT=%d,ACCESS='DIRECT'): file size is not known"); 93 } else if (*totalBytes % *recordLength != 0) { 94 handler.SignalError(IostatOpenBadAppend, 95 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an " 96 "even divisor of the file size %jd", 97 unitNumber(), static_cast<std::intmax_t>(*recordLength), 98 static_cast<std::intmax_t>(*totalBytes)); 99 } 100 } 101 if (position == Position::Append) { 102 if (*totalBytes && recordLength && *recordLength) { 103 endfileRecordNumber = 1 + (*totalBytes / *recordLength); 104 } else { 105 // Fake it so that we can backspace relative from the end 106 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 1; 107 } 108 currentRecordNumber = *endfileRecordNumber; 109 } else { 110 currentRecordNumber = 1; 111 } 112 } 113 114 void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) { 115 DoImpliedEndfile(handler); 116 Flush(handler); 117 Close(status, handler); 118 } 119 120 void ExternalFileUnit::DestroyClosed() { 121 GetUnitMap().DestroyClosed(*this); // destroys *this 122 } 123 124 bool ExternalFileUnit::SetDirection( 125 Direction direction, IoErrorHandler &handler) { 126 if (direction == Direction::Input) { 127 if (mayRead()) { 128 direction_ = Direction::Input; 129 return true; 130 } else { 131 handler.SignalError(IostatReadFromWriteOnly, 132 "READ(UNIT=%d) with ACTION='WRITE'", unitNumber()); 133 return false; 134 } 135 } else { 136 if (mayWrite()) { 137 direction_ = Direction::Output; 138 return true; 139 } else { 140 handler.SignalError(IostatWriteToReadOnly, 141 "WRITE(UNIT=%d) with ACTION='READ'", unitNumber()); 142 return false; 143 } 144 } 145 } 146 147 UnitMap &ExternalFileUnit::GetUnitMap() { 148 if (unitMap) { 149 return *unitMap; 150 } 151 CriticalSection critical{unitMapLock}; 152 if (unitMap) { 153 return *unitMap; 154 } 155 Terminator terminator{__FILE__, __LINE__}; 156 IoErrorHandler handler{terminator}; 157 unitMap = New<UnitMap>{terminator}().release(); 158 ExternalFileUnit &out{ExternalFileUnit::LookUpOrCreate(6, terminator)}; 159 out.Predefine(1); 160 out.set_mayRead(false); 161 out.set_mayWrite(true); 162 out.set_mayPosition(false); 163 out.SetDirection(Direction::Output, handler); 164 defaultOutput = &out; 165 ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)}; 166 in.Predefine(0); 167 in.set_mayRead(true); 168 in.set_mayWrite(false); 169 in.set_mayPosition(false); 170 in.SetDirection(Direction::Input, handler); 171 defaultInput = ∈ 172 // TODO: Set UTF-8 mode from the environment 173 return *unitMap; 174 } 175 176 void ExternalFileUnit::CloseAll(IoErrorHandler &handler) { 177 CriticalSection critical{unitMapLock}; 178 if (unitMap) { 179 unitMap->CloseAll(handler); 180 FreeMemoryAndNullify(unitMap); 181 } 182 defaultOutput = nullptr; 183 } 184 185 void ExternalFileUnit::FlushAll(IoErrorHandler &handler) { 186 CriticalSection critical{unitMapLock}; 187 if (unitMap) { 188 unitMap->FlushAll(handler); 189 } 190 } 191 192 bool ExternalFileUnit::Emit( 193 const char *data, std::size_t bytes, IoErrorHandler &handler) { 194 auto furthestAfter{std::max(furthestPositionInRecord, 195 positionInRecord + static_cast<std::int64_t>(bytes))}; 196 if (furthestAfter > recordLength.value_or(furthestAfter)) { 197 handler.SignalError(IostatRecordWriteOverrun, 198 "Attempt to write %zd bytes to position %jd in a fixed-size record of " 199 "%jd bytes", 200 bytes, static_cast<std::intmax_t>(positionInRecord), 201 static_cast<std::intmax_t>(*recordLength)); 202 return false; 203 } 204 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler); 205 if (positionInRecord > furthestPositionInRecord) { 206 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ', 207 positionInRecord - furthestPositionInRecord); 208 } 209 std::memcpy(Frame() + recordOffsetInFrame_ + positionInRecord, data, bytes); 210 positionInRecord += bytes; 211 furthestPositionInRecord = furthestAfter; 212 return true; 213 } 214 215 bool ExternalFileUnit::Receive( 216 char *data, std::size_t bytes, IoErrorHandler &handler) { 217 RUNTIME_CHECK(handler, direction_ == Direction::Input); 218 auto furthestAfter{std::max(furthestPositionInRecord, 219 positionInRecord + static_cast<std::int64_t>(bytes))}; 220 if (furthestAfter > recordLength.value_or(furthestAfter)) { 221 handler.SignalError(IostatRecordReadOverrun, 222 "Attempt to read %zd bytes at position %jd in a record of %jd bytes", 223 bytes, static_cast<std::intmax_t>(positionInRecord), 224 static_cast<std::intmax_t>(*recordLength)); 225 return false; 226 } 227 auto need{recordOffsetInFrame_ + furthestAfter}; 228 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 229 if (got >= need) { 230 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes); 231 positionInRecord += bytes; 232 furthestPositionInRecord = furthestAfter; 233 return true; 234 } else { 235 handler.SignalEnd(); 236 endfileRecordNumber = currentRecordNumber; 237 return false; 238 } 239 } 240 241 std::optional<char32_t> ExternalFileUnit::GetCurrentChar( 242 IoErrorHandler &handler) { 243 RUNTIME_CHECK(handler, direction_ == Direction::Input); 244 if (const char *p{FrameNextInput(handler, 1)}) { 245 // TODO: UTF-8 decoding; may have to get more bytes in a loop 246 return *p; 247 } 248 return std::nullopt; 249 } 250 251 const char *ExternalFileUnit::FrameNextInput( 252 IoErrorHandler &handler, std::size_t bytes) { 253 RUNTIME_CHECK(handler, !isUnformatted); 254 if (static_cast<std::int64_t>(positionInRecord + bytes) <= 255 recordLength.value_or(positionInRecord + bytes)) { 256 auto at{recordOffsetInFrame_ + positionInRecord}; 257 auto need{static_cast<std::size_t>(at + bytes)}; 258 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 259 SetSequentialVariableFormattedRecordLength(); 260 if (got >= need) { 261 return Frame() + at; 262 } 263 handler.SignalEnd(); 264 endfileRecordNumber = currentRecordNumber; 265 } 266 return nullptr; 267 } 268 269 bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() { 270 if (recordLength || access != Access::Sequential) { 271 return true; 272 } 273 if (FrameLength() > recordOffsetInFrame_) { 274 const char *record{Frame() + recordOffsetInFrame_}; 275 if (const char *nl{reinterpret_cast<const char *>( 276 std::memchr(record, '\n', FrameLength() - recordOffsetInFrame_))}) { 277 recordLength = nl - record; 278 if (*recordLength > 0 && record[*recordLength - 1] == '\r') { 279 --*recordLength; 280 } 281 return true; 282 } 283 } 284 return false; 285 } 286 287 void ExternalFileUnit::SetLeftTabLimit() { 288 leftTabLimit = furthestPositionInRecord; 289 positionInRecord = furthestPositionInRecord; 290 } 291 292 void ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) { 293 RUNTIME_CHECK(handler, direction_ == Direction::Input); 294 if (access == Access::Sequential) { 295 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 296 handler.SignalEnd(); 297 } else if (isFixedRecordLength) { 298 RUNTIME_CHECK(handler, recordLength.has_value()); 299 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)}; 300 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 301 if (got < need) { 302 handler.SignalEnd(); 303 } 304 } else if (isUnformatted) { 305 BeginSequentialVariableUnformattedInputRecord(handler); 306 } else { // formatted 307 BeginSequentialVariableFormattedInputRecord(handler); 308 } 309 } 310 } 311 312 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 313 bool ok{true}; 314 if (direction_ == Direction::Input) { 315 if (access == Access::Sequential) { 316 RUNTIME_CHECK(handler, recordLength.has_value()); 317 if (isFixedRecordLength) { 318 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; 319 recordOffsetInFrame_ = 0; 320 } else if (isUnformatted) { 321 // Retain footer in frame for more efficient BACKSPACE 322 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength; 323 recordOffsetInFrame_ = sizeof(std::uint32_t); 324 recordLength.reset(); 325 } else { // formatted 326 if (Frame()[recordOffsetInFrame_ + *recordLength] == '\r') { 327 ++recordOffsetInFrame_; 328 } 329 recordOffsetInFrame_ += *recordLength + 1; 330 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ - 1] == '\n'); 331 recordLength.reset(); 332 } 333 } 334 } else { // Direction::Output 335 if (!isUnformatted) { 336 if (isFixedRecordLength && recordLength) { 337 if (furthestPositionInRecord < *recordLength) { 338 WriteFrame(frameOffsetInFile_, *recordLength, handler); 339 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 340 ' ', *recordLength - furthestPositionInRecord); 341 } 342 } else { 343 positionInRecord = furthestPositionInRecord; 344 ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF 345 } 346 } 347 frameOffsetInFile_ += 348 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord); 349 recordOffsetInFrame_ = 0; 350 impliedEndfile_ = true; 351 } 352 ++currentRecordNumber; 353 BeginRecord(); 354 return ok; 355 } 356 357 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 358 if (access != Access::Sequential) { 359 handler.SignalError(IostatBackspaceNonSequential, 360 "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); 361 } else { 362 DoImpliedEndfile(handler); 363 --currentRecordNumber; 364 BeginRecord(); 365 if (isFixedRecordLength) { 366 BackspaceFixedRecord(handler); 367 } else if (isUnformatted) { 368 BackspaceVariableUnformattedRecord(handler); 369 } else { 370 BackspaceVariableFormattedRecord(handler); 371 } 372 } 373 } 374 375 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 376 if (isTerminal()) { 377 Flush(handler); 378 } 379 } 380 381 void ExternalFileUnit::Endfile(IoErrorHandler &handler) { 382 if (access != Access::Sequential) { 383 handler.SignalError(IostatEndfileNonSequential, 384 "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); 385 } else if (!mayWrite()) { 386 handler.SignalError(IostatEndfileUnwritable, 387 "ENDFILE(UNIT=%d) on read-only file", unitNumber()); 388 } else { 389 DoEndfile(handler); 390 } 391 } 392 393 void ExternalFileUnit::Rewind(IoErrorHandler &handler) { 394 if (access == Access::Direct) { 395 handler.SignalError(IostatRewindNonSequential, 396 "REWIND(UNIT=%d) on non-sequential file", unitNumber()); 397 } else { 398 DoImpliedEndfile(handler); 399 SetPosition(0); 400 currentRecordNumber = 1; 401 // TODO: reset endfileRecordNumber? 402 } 403 } 404 405 void ExternalFileUnit::EndIoStatement() { 406 frameOffsetInFile_ += recordOffsetInFrame_; 407 recordOffsetInFrame_ = 0; 408 io_.reset(); 409 u_.emplace<std::monostate>(); 410 lock_.Drop(); 411 } 412 413 void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord( 414 IoErrorHandler &handler) { 415 std::int32_t header{0}, footer{0}; 416 std::size_t need{recordOffsetInFrame_ + sizeof header}; 417 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)}; 418 // Try to emit informative errors to help debug corrupted files. 419 const char *error{nullptr}; 420 if (got < need) { 421 if (got == recordOffsetInFrame_) { 422 handler.SignalEnd(); 423 } else { 424 error = "Unformatted variable-length sequential file input failed at " 425 "record #%jd (file offset %jd): truncated record header"; 426 } 427 } else { 428 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 429 recordLength = sizeof header + header; // does not include footer 430 need = recordOffsetInFrame_ + *recordLength + sizeof footer; 431 got = ReadFrame(frameOffsetInFile_, need, handler); 432 if (got < need) { 433 error = "Unformatted variable-length sequential file input failed at " 434 "record #%jd (file offset %jd): hit EOF reading record with " 435 "length %jd bytes"; 436 } else { 437 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength, 438 sizeof footer); 439 if (footer != header) { 440 error = "Unformatted variable-length sequential file input failed at " 441 "record #%jd (file offset %jd): record header has length %jd " 442 "that does not match record footer (%jd)"; 443 } 444 } 445 } 446 if (error) { 447 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 448 static_cast<std::intmax_t>(frameOffsetInFile_), 449 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 450 // TODO: error recovery 451 } 452 positionInRecord = sizeof header; 453 } 454 455 void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( 456 IoErrorHandler &handler) { 457 if (this == defaultInput && defaultOutput) { 458 defaultOutput->Flush(handler); 459 } 460 std::size_t length{0}; 461 do { 462 std::size_t need{recordOffsetInFrame_ + length + 1}; 463 length = ReadFrame(frameOffsetInFile_, need, handler); 464 if (length < need) { 465 handler.SignalEnd(); 466 break; 467 } 468 } while (!SetSequentialVariableFormattedRecordLength()); 469 } 470 471 void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { 472 RUNTIME_CHECK(handler, recordLength.has_value()); 473 if (frameOffsetInFile_ < *recordLength) { 474 handler.SignalError(IostatBackspaceAtFirstRecord); 475 } else { 476 frameOffsetInFile_ -= *recordLength; 477 } 478 } 479 480 void ExternalFileUnit::BackspaceVariableUnformattedRecord( 481 IoErrorHandler &handler) { 482 std::int32_t header{0}, footer{0}; 483 auto headerBytes{static_cast<std::int64_t>(sizeof header)}; 484 frameOffsetInFile_ += recordOffsetInFrame_; 485 recordOffsetInFrame_ = 0; 486 if (frameOffsetInFile_ <= headerBytes) { 487 handler.SignalError(IostatBackspaceAtFirstRecord); 488 return; 489 } 490 // Error conditions here cause crashes, not file format errors, because the 491 // validity of the file structure before the current record will have been 492 // checked informatively in NextSequentialVariableUnformattedInputRecord(). 493 std::size_t got{ 494 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)}; 495 RUNTIME_CHECK(handler, got >= sizeof footer); 496 std::memcpy(&footer, Frame(), sizeof footer); 497 recordLength = footer; 498 RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes); 499 frameOffsetInFile_ -= *recordLength + 2 * headerBytes; 500 if (frameOffsetInFile_ >= headerBytes) { 501 frameOffsetInFile_ -= headerBytes; 502 recordOffsetInFrame_ = headerBytes; 503 } 504 auto need{static_cast<std::size_t>( 505 recordOffsetInFrame_ + sizeof header + *recordLength)}; 506 got = ReadFrame(frameOffsetInFile_, need, handler); 507 RUNTIME_CHECK(handler, got >= need); 508 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header); 509 RUNTIME_CHECK(handler, header == *recordLength); 510 } 511 512 // There's no portable memrchr(), unfortunately, and strrchr() would 513 // fail on a record with a NUL, so we have to do it the hard way. 514 static const char *FindLastNewline(const char *str, std::size_t length) { 515 for (const char *p{str + length}; p-- > str;) { 516 if (*p == '\n') { 517 return p; 518 } 519 } 520 return nullptr; 521 } 522 523 void ExternalFileUnit::BackspaceVariableFormattedRecord( 524 IoErrorHandler &handler) { 525 // File offset of previous record's newline 526 auto prevNL{ 527 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1}; 528 if (prevNL < 0) { 529 handler.SignalError(IostatBackspaceAtFirstRecord); 530 return; 531 } 532 while (true) { 533 if (frameOffsetInFile_ < prevNL) { 534 if (const char *p{ 535 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) { 536 recordOffsetInFrame_ = p - Frame() + 1; 537 *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_); 538 break; 539 } 540 } 541 if (frameOffsetInFile_ == 0) { 542 recordOffsetInFrame_ = 0; 543 *recordLength = prevNL; 544 break; 545 } 546 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024); 547 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)}; 548 auto got{ReadFrame(frameOffsetInFile_, need, handler)}; 549 RUNTIME_CHECK(handler, got >= need); 550 } 551 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 552 if (*recordLength > 0 && 553 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 554 --*recordLength; 555 } 556 } 557 558 void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { 559 if (impliedEndfile_) { 560 impliedEndfile_ = false; 561 if (access == Access::Sequential && mayPosition()) { 562 DoEndfile(handler); 563 } 564 } 565 } 566 567 void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { 568 endfileRecordNumber = currentRecordNumber; 569 Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); 570 BeginRecord(); 571 impliedEndfile_ = false; 572 } 573 } // namespace Fortran::runtime::io 574