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 if (positionInRecord > furthestPositionInRecord) { 133 std::memset(Frame() + furthestPositionInRecord, ' ', 134 positionInRecord - furthestPositionInRecord); 135 } 136 std::memcpy(Frame() + positionInRecord, data, bytes); 137 positionInRecord += bytes; 138 furthestPositionInRecord = furthestAfter; 139 return true; 140 } 141 142 std::optional<char32_t> ExternalFileUnit::GetCurrentChar( 143 IoErrorHandler &handler) { 144 isReading_ = true; // TODO: manage read/write transitions 145 if (isUnformatted) { 146 handler.Crash("GetCurrentChar() called for unformatted input"); 147 return std::nullopt; 148 } 149 std::size_t chunk{256}; // for stream input 150 if (recordLength.has_value()) { 151 if (positionInRecord >= *recordLength) { 152 return std::nullopt; 153 } 154 chunk = *recordLength - positionInRecord; 155 } 156 auto at{recordOffsetInFrame_ + positionInRecord}; 157 std::size_t need{static_cast<std::size_t>(at + 1)}; 158 std::size_t want{need + chunk}; 159 auto got{ReadFrame(frameOffsetInFile_, want, handler)}; 160 if (got <= need) { 161 endfileRecordNumber = currentRecordNumber; 162 handler.SignalEnd(); 163 return std::nullopt; 164 } 165 const char *p{Frame() + at}; 166 if (isUTF8) { 167 // TODO: UTF-8 decoding 168 } 169 return *p; 170 } 171 172 void ExternalFileUnit::SetLeftTabLimit() { 173 leftTabLimit = furthestPositionInRecord; 174 positionInRecord = furthestPositionInRecord; 175 } 176 177 bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) { 178 bool ok{true}; 179 if (isReading_) { 180 if (access == Access::Sequential) { 181 if (isUnformatted) { 182 NextSequentialUnformattedInputRecord(handler); 183 } else { 184 NextSequentialFormattedInputRecord(handler); 185 } 186 } 187 } else if (!isUnformatted) { 188 if (recordLength.has_value()) { 189 // fill fixed-size record 190 if (furthestPositionInRecord < *recordLength) { 191 WriteFrame(frameOffsetInFile_, *recordLength, handler); 192 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, 193 ' ', *recordLength - furthestPositionInRecord); 194 } 195 } else { 196 positionInRecord = furthestPositionInRecord; 197 ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF 198 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; 199 recordOffsetInFrame_ = 0; 200 } 201 } 202 ++currentRecordNumber; 203 positionInRecord = 0; 204 furthestPositionInRecord = 0; 205 leftTabLimit.reset(); 206 return ok; 207 } 208 209 void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { 210 if (!isReading_) { 211 handler.Crash("ExternalFileUnit::BackspaceRecord() called during writing"); 212 // TODO: create endfile record, &c. 213 } 214 if (access == Access::Sequential) { 215 if (isUnformatted) { 216 BackspaceSequentialUnformattedRecord(handler); 217 } else { 218 BackspaceSequentialFormattedRecord(handler); 219 } 220 } else { 221 // TODO 222 } 223 positionInRecord = 0; 224 furthestPositionInRecord = 0; 225 leftTabLimit.reset(); 226 } 227 228 void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) { 229 if (isTerminal()) { 230 Flush(handler); 231 } 232 } 233 234 void ExternalFileUnit::EndIoStatement() { 235 frameOffsetInFile_ += recordOffsetInFrame_; 236 recordOffsetInFrame_ = 0; 237 io_.reset(); 238 u_.emplace<std::monostate>(); 239 lock_.Drop(); 240 } 241 242 void ExternalFileUnit::NextSequentialUnformattedInputRecord( 243 IoErrorHandler &handler) { 244 std::int32_t header{0}, footer{0}; 245 // Retain previous footer (if any) in frame for more efficient BACKSPACE 246 std::size_t retain{sizeof header}; 247 if (recordLength) { // not first record - advance to next 248 ++currentRecordNumber; 249 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 250 handler.SignalEnd(); 251 return; 252 } 253 frameOffsetInFile_ += 254 recordOffsetInFrame_ + *recordLength + 2 * sizeof header; 255 recordOffsetInFrame_ = 0; 256 } else { 257 retain = 0; 258 } 259 std::size_t need{retain + sizeof header}; 260 std::size_t got{ReadFrame(frameOffsetInFile_ - retain, need, handler)}; 261 // Try to emit informative errors to help debug corrupted files. 262 const char *error{nullptr}; 263 if (got < need) { 264 if (got == retain) { 265 handler.SignalEnd(); 266 } else { 267 error = "Unformatted sequential file input failed at record #%jd (file " 268 "offset %jd): truncated record header"; 269 } 270 } else { 271 std::memcpy(&header, Frame() + retain, sizeof header); 272 need = retain + header + 2 * sizeof header; 273 got = ReadFrame(frameOffsetInFile_ - retain, 274 need + sizeof header /* next one */, handler); 275 if (got < need) { 276 error = "Unformatted sequential file input failed at record #%jd (file " 277 "offset %jd): hit EOF reading record with length %jd bytes"; 278 } else { 279 const char *start{Frame() + retain + sizeof header}; 280 std::memcpy(&footer, start + header, sizeof footer); 281 if (footer != header) { 282 error = "Unformatted sequential file input failed at record #%jd (file " 283 "offset %jd): record header has length %jd that does not match " 284 "record footer (%jd)"; 285 } else { 286 recordLength = header; 287 } 288 } 289 } 290 if (error) { 291 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber), 292 static_cast<std::intmax_t>(frameOffsetInFile_), 293 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer)); 294 } 295 positionInRecord = sizeof header; 296 } 297 298 void ExternalFileUnit::NextSequentialFormattedInputRecord( 299 IoErrorHandler &handler) { 300 static constexpr std::size_t chunk{256}; 301 std::size_t length{0}; 302 if (recordLength.has_value()) { 303 // not first record - advance to next 304 ++currentRecordNumber; 305 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) { 306 handler.SignalEnd(); 307 return; 308 } 309 if (Frame()[*recordLength] == '\r') { 310 ++*recordLength; 311 } 312 recordOffsetInFrame_ += *recordLength + 1; 313 } 314 while (true) { 315 std::size_t got{ReadFrame( 316 frameOffsetInFile_, recordOffsetInFrame_ + length + chunk, handler)}; 317 if (got <= recordOffsetInFrame_ + length) { 318 handler.SignalEnd(); 319 break; 320 } 321 const char *frame{Frame() + recordOffsetInFrame_}; 322 if (const char *nl{reinterpret_cast<const char *>( 323 std::memchr(frame + length, '\n', chunk))}) { 324 recordLength = nl - (frame + length) + 1; 325 if (*recordLength > 0 && frame[*recordLength - 1] == '\r') { 326 --*recordLength; 327 } 328 return; 329 } 330 length += got; 331 } 332 } 333 334 void ExternalFileUnit::BackspaceSequentialUnformattedRecord( 335 IoErrorHandler &handler) { 336 std::int32_t header{0}, footer{0}; 337 RUNTIME_CHECK(handler, currentRecordNumber > 1); 338 --currentRecordNumber; 339 int overhead{static_cast<int>(2 * sizeof header)}; 340 // Error conditions here cause crashes, not file format errors, because the 341 // validity of the file structure before the current record will have been 342 // checked informatively in NextSequentialUnformattedInputRecord(). 343 RUNTIME_CHECK(handler, frameOffsetInFile_ >= overhead); 344 std::size_t got{ 345 ReadFrame(frameOffsetInFile_ - sizeof footer, sizeof footer, handler)}; 346 RUNTIME_CHECK(handler, got >= sizeof footer); 347 std::memcpy(&footer, Frame(), sizeof footer); 348 RUNTIME_CHECK(handler, frameOffsetInFile_ >= footer + overhead); 349 frameOffsetInFile_ -= footer + 2 * sizeof footer; 350 auto extra{std::max<std::size_t>(sizeof footer, frameOffsetInFile_)}; 351 std::size_t want{extra + footer + 2 * sizeof footer}; 352 got = ReadFrame(frameOffsetInFile_ - extra, want, handler); 353 RUNTIME_CHECK(handler, got >= want); 354 std::memcpy(&header, Frame() + extra, sizeof header); 355 RUNTIME_CHECK(handler, header == footer); 356 positionInRecord = sizeof header; 357 recordLength = footer; 358 } 359 360 // There's no portable memrchr(), unfortunately, and strrchr() would 361 // fail on a record with a NUL, so we have to do it the hard way. 362 static const char *FindLastNewline(const char *str, std::size_t length) { 363 for (const char *p{str + length}; p-- > str;) { 364 if (*p == '\n') { 365 return p; 366 } 367 } 368 return nullptr; 369 } 370 371 void ExternalFileUnit::BackspaceSequentialFormattedRecord( 372 IoErrorHandler &handler) { 373 std::int64_t start{frameOffsetInFile_ + recordOffsetInFrame_}; 374 --currentRecordNumber; 375 RUNTIME_CHECK(handler, currentRecordNumber > 0); 376 if (currentRecordNumber == 1) { 377 // To simplify the code below, treat a backspace to the first record 378 // as a special case; 379 RUNTIME_CHECK(handler, start > 0); 380 *recordLength = start - 1; 381 frameOffsetInFile_ = 0; 382 recordOffsetInFrame_ = 0; 383 ReadFrame(0, *recordLength + 1, handler); 384 } else { 385 RUNTIME_CHECK(handler, start > 1); 386 std::int64_t at{start - 2}; // byte before previous record's newline 387 while (true) { 388 if (const char *p{ 389 FindLastNewline(Frame(), at - frameOffsetInFile_ + 1)}) { 390 // This is the newline that ends the record before the previous one. 391 recordOffsetInFrame_ = p - Frame() + 1; 392 *recordLength = start - 1 - (frameOffsetInFile_ + recordOffsetInFrame_); 393 break; 394 } 395 RUNTIME_CHECK(handler, frameOffsetInFile_ > 0); 396 at = frameOffsetInFile_ - 1; 397 if (auto bytesBefore{BytesBufferedBeforeFrame()}) { 398 frameOffsetInFile_ = FrameAt() - bytesBefore; 399 } else { 400 static constexpr int chunk{1024}; 401 frameOffsetInFile_ = std::max<std::int64_t>(0, at - chunk); 402 } 403 std::size_t want{static_cast<std::size_t>(start - frameOffsetInFile_)}; 404 std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)}; 405 RUNTIME_CHECK(handler, got >= want); 406 } 407 } 408 std::size_t want{ 409 static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength + 1)}; 410 RUNTIME_CHECK(handler, FrameLength() >= want); 411 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n'); 412 if (*recordLength > 0 && 413 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') { 414 --*recordLength; 415 } 416 } 417 } // namespace Fortran::runtime::io 418