blob: 0661d55134f464ca3cd1b31c2296ffa968a9e2dc [file] [log] [blame]
peter klauslerf7be2512020-01-23 16:59:27 -08001//===-- 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"
peter klausler3b635712020-02-13 14:41:56 -080010#include "io-error.h"
peter klauslerf7be2512020-01-23 16:59:27 -080011#include "lock.h"
peter klausler3b635712020-02-13 14:41:56 -080012#include "unit-map.h"
peter klauslerf7be2512020-01-23 16:59:27 -080013
14namespace Fortran::runtime::io {
15
peter klausler3b635712020-02-13 14:41:56 -080016// The per-unit data structures are created on demand so that Fortran I/O
17// should work without a Fortran main program.
18static Lock unitMapLock;
19static UnitMap *unitMap{nullptr};
peter klausler95696d52020-02-04 16:55:45 -080020static ExternalFileUnit *defaultOutput{nullptr};
peter klauslerf7be2512020-01-23 16:59:27 -080021
peter klausler95696d52020-02-04 16:55:45 -080022void FlushOutputOnCrash(const Terminator &terminator) {
peter klausler3b635712020-02-13 14:41:56 -080023 if (!defaultOutput) {
24 return;
25 }
26 CriticalSection critical{unitMapLock};
peter klausler95696d52020-02-04 16:55:45 -080027 if (defaultOutput) {
28 IoErrorHandler handler{terminator};
Tim Keith1f879002020-03-28 21:00:16 -070029 handler.HasIoStat(); // prevent nested crash if flush has error
peter klausler95696d52020-02-04 16:55:45 -080030 defaultOutput->Flush(handler);
31 }
32}
33
34ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
peter klausler3b635712020-02-13 14:41:56 -080035 return GetUnitMap().LookUp(unit);
peter klauslerf7be2512020-01-23 16:59:27 -080036}
37
peter klausler95696d52020-02-04 16:55:45 -080038ExternalFileUnit &ExternalFileUnit::LookUpOrCrash(
39 int unit, const Terminator &terminator) {
peter klausler95696d52020-02-04 16:55:45 -080040 ExternalFileUnit *file{LookUp(unit)};
peter klauslerf7be2512020-01-23 16:59:27 -080041 if (!file) {
42 terminator.Crash("Not an open I/O unit number: %d", unit);
43 }
44 return *file;
45}
46
peter klausler3b635712020-02-13 14:41:56 -080047ExternalFileUnit &ExternalFileUnit::LookUpOrCreate(
48 int unit, const Terminator &terminator, bool *wasExtant) {
49 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
peter klauslerf7be2512020-01-23 16:59:27 -080050}
51
peter klausler3b635712020-02-13 14:41:56 -080052ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
53 return GetUnitMap().LookUpForClose(unit);
54}
55
56int ExternalFileUnit::NewUnit(const Terminator &terminator) {
57 return GetUnitMap().NewUnit(terminator).unitNumber();
peter klausler95696d52020-02-04 16:55:45 -080058}
59
60void ExternalFileUnit::OpenUnit(OpenStatus status, Position position,
61 OwningPtr<char> &&newPath, std::size_t newPathLength,
62 IoErrorHandler &handler) {
peter klausler95696d52020-02-04 16:55:45 -080063 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
80void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
peter klausler3b635712020-02-13 14:41:56 -080081 Flush(handler);
82 Close(status, handler);
peter klauslerf7be2512020-01-23 16:59:27 -080083}
84
peter klausler3b635712020-02-13 14:41:56 -080085void ExternalFileUnit::DestroyClosed() {
Tim Keith1f879002020-03-28 21:00:16 -070086 GetUnitMap().DestroyClosed(*this); // destroys *this
peter klausler3b635712020-02-13 14:41:56 -080087}
88
89UnitMap &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)};
peter klauslerf7be2512020-01-23 16:59:27 -0800100 out.Predefine(1);
101 out.set_mayRead(false);
102 out.set_mayWrite(true);
103 out.set_mayPosition(false);
peter klausler95696d52020-02-04 16:55:45 -0800104 defaultOutput = &out;
peter klausler3b635712020-02-13 14:41:56 -0800105 ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)};
peter klauslerf7be2512020-01-23 16:59:27 -0800106 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
peter klausler3b635712020-02-13 14:41:56 -0800111 return *unitMap;
peter klauslerf7be2512020-01-23 16:59:27 -0800112}
113
peter klausler95696d52020-02-04 16:55:45 -0800114void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
peter klausler3b635712020-02-13 14:41:56 -0800115 CriticalSection critical{unitMapLock};
116 if (unitMap) {
117 unitMap->CloseAll(handler);
118 FreeMemoryAndNullify(unitMap);
119 }
peter klausler95696d52020-02-04 16:55:45 -0800120 defaultOutput = nullptr;
peter klauslerf7be2512020-01-23 16:59:27 -0800121}
122
peter klausler95696d52020-02-04 16:55:45 -0800123bool 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))};
peter klausler3b635712020-02-13 14:41:56 -0800127 if (furthestAfter > recordLength.value_or(furthestAfter)) {
128 handler.SignalError(IostatRecordWriteOverrun);
129 return false;
130 }
131 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
peter klauslerf7be2512020-01-23 16:59:27 -0800132 std::memcpy(Frame() + positionInRecord, data, bytes);
133 positionInRecord += bytes;
134 furthestPositionInRecord = furthestAfter;
135 return true;
136}
137
peter klausler3b635712020-02-13 14:41:56 -0800138std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
139 IoErrorHandler &handler) {
Tim Keith1f879002020-03-28 21:00:16 -0700140 isReading_ = true; // TODO: manage read/write transitions
peter klausler3b635712020-02-13 14:41:56 -0800141 if (isUnformatted) {
142 handler.Crash("GetCurrentChar() called for unformatted input");
143 return std::nullopt;
144 }
Tim Keith1f879002020-03-28 21:00:16 -0700145 std::size_t chunk{256}; // for stream input
peter klausler3b635712020-02-13 14:41:56 -0800146 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
peter klausler95696d52020-02-04 16:55:45 -0800168void ExternalFileUnit::SetLeftTabLimit() {
peter klauslerf7be2512020-01-23 16:59:27 -0800169 leftTabLimit = furthestPositionInRecord;
170 positionInRecord = furthestPositionInRecord;
171}
172
peter klausler95696d52020-02-04 16:55:45 -0800173bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
peter klauslerf7be2512020-01-23 16:59:27 -0800174 bool ok{true};
peter klausler3b635712020-02-13 14:41:56 -0800175 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;
Tim Keith1f879002020-03-28 21:00:16 -0700193 ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF
peter klausler3b635712020-02-13 14:41:56 -0800194 frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
195 recordOffsetInFrame_ = 0;
196 }
peter klauslerf7be2512020-01-23 16:59:27 -0800197 }
peter klauslerf7be2512020-01-23 16:59:27 -0800198 ++currentRecordNumber;
199 positionInRecord = 0;
peter klausler95696d52020-02-04 16:55:45 -0800200 furthestPositionInRecord = 0;
peter klauslerf7be2512020-01-23 16:59:27 -0800201 leftTabLimit.reset();
202 return ok;
203}
204
peter klausler3b635712020-02-13 14:41:56 -0800205void 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();
peter klauslerf7be2512020-01-23 16:59:27 -0800222}
223
peter klausler95696d52020-02-04 16:55:45 -0800224void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
225 if (isTerminal()) {
226 Flush(handler);
227 }
228}
229
230void ExternalFileUnit::EndIoStatement() {
peter klausler3b635712020-02-13 14:41:56 -0800231 frameOffsetInFile_ += recordOffsetInFrame_;
232 recordOffsetInFrame_ = 0;
peter klausler95696d52020-02-04 16:55:45 -0800233 io_.reset();
peter klauslerf7be2512020-01-23 16:59:27 -0800234 u_.emplace<std::monostate>();
peter klausler3b635712020-02-13 14:41:56 -0800235 lock_.Drop();
236}
237
238void 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};
Tim Keith1f879002020-03-28 21:00:16 -0700243 if (recordLength) { // not first record - advance to next
peter klausler3b635712020-02-13 14:41:56 -0800244 ++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
294void 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
330void 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.
358static 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
367void 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);
Tim Keith1f879002020-03-28 21:00:16 -0700382 std::int64_t at{start - 2}; // byte before previous record's newline
peter klausler3b635712020-02-13 14:41:56 -0800383 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 }
peter klauslerf7be2512020-01-23 16:59:27 -0800412}
Tim Keith1f879002020-03-28 21:00:16 -0700413} // namespace Fortran::runtime::io