blob: 85d83ec50bd9c25c045eeee1faa77d82c3574b2f [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 klausler8f2c5c42020-07-21 17:37:35 -070010#include "environment.h"
peter klausler3b635712020-02-13 14:41:56 -080011#include "io-error.h"
peter klauslerf7be2512020-01-23 16:59:27 -080012#include "lock.h"
peter klausler3b635712020-02-13 14:41:56 -080013#include "unit-map.h"
peter klauslerbd43fa22020-07-14 11:28:03 -070014#include <cstdio>
peter klausler8f2c5c42020-07-21 17:37:35 -070015#include <utility>
peter klauslerf7be2512020-01-23 16:59:27 -080016
17namespace Fortran::runtime::io {
18
peter klausler3b635712020-02-13 14:41:56 -080019// The per-unit data structures are created on demand so that Fortran I/O
20// should work without a Fortran main program.
21static Lock unitMapLock;
22static UnitMap *unitMap{nullptr};
peter klausler00063542020-07-03 12:38:22 -070023static ExternalFileUnit *defaultInput{nullptr};
peter klausler95696d52020-02-04 16:55:45 -080024static ExternalFileUnit *defaultOutput{nullptr};
peter klauslerf7be2512020-01-23 16:59:27 -080025
peter klausler95696d52020-02-04 16:55:45 -080026void FlushOutputOnCrash(const Terminator &terminator) {
peter klausler3b635712020-02-13 14:41:56 -080027 if (!defaultOutput) {
28 return;
29 }
30 CriticalSection critical{unitMapLock};
peter klausler95696d52020-02-04 16:55:45 -080031 if (defaultOutput) {
32 IoErrorHandler handler{terminator};
Tim Keith1f879002020-03-28 21:00:16 -070033 handler.HasIoStat(); // prevent nested crash if flush has error
peter klausler95696d52020-02-04 16:55:45 -080034 defaultOutput->Flush(handler);
35 }
36}
37
38ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
peter klausler3b635712020-02-13 14:41:56 -080039 return GetUnitMap().LookUp(unit);
peter klauslerf7be2512020-01-23 16:59:27 -080040}
41
peter klausler95696d52020-02-04 16:55:45 -080042ExternalFileUnit &ExternalFileUnit::LookUpOrCrash(
43 int unit, const Terminator &terminator) {
peter klausler95696d52020-02-04 16:55:45 -080044 ExternalFileUnit *file{LookUp(unit)};
peter klauslerf7be2512020-01-23 16:59:27 -080045 if (!file) {
46 terminator.Crash("Not an open I/O unit number: %d", unit);
47 }
48 return *file;
49}
50
peter klausler3b635712020-02-13 14:41:56 -080051ExternalFileUnit &ExternalFileUnit::LookUpOrCreate(
peter klauslerbd43fa22020-07-14 11:28:03 -070052 int unit, const Terminator &terminator, bool &wasExtant) {
peter klausler3b635712020-02-13 14:41:56 -080053 return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
peter klauslerf7be2512020-01-23 16:59:27 -080054}
55
peter klauslerbd43fa22020-07-14 11:28:03 -070056ExternalFileUnit &ExternalFileUnit::LookUpOrCreateAnonymous(
57 int unit, Direction dir, bool isUnformatted, const Terminator &terminator) {
58 bool exists{false};
59 ExternalFileUnit &result{
60 GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
61 if (!exists) {
peter klauslerbd43fa22020-07-14 11:28:03 -070062 IoErrorHandler handler{terminator};
peter klausler675ad1b2020-08-03 11:35:29 -070063 result.OpenAnonymousUnit(
64 dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
65 Action::ReadWrite, Position::Rewind, Convert::Native, handler);
peter klauslerbd43fa22020-07-14 11:28:03 -070066 result.isUnformatted = isUnformatted;
67 }
68 return result;
69}
70
peter klausler675ad1b2020-08-03 11:35:29 -070071ExternalFileUnit *ExternalFileUnit::LookUp(const char *path) {
72 return GetUnitMap().LookUp(path);
73}
74
peter klauslerbd43fa22020-07-14 11:28:03 -070075ExternalFileUnit &ExternalFileUnit::CreateNew(
76 int unit, const Terminator &terminator) {
77 bool wasExtant{false};
78 ExternalFileUnit &result{
79 GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
80 RUNTIME_CHECK(terminator, !wasExtant);
81 return result;
82}
83
peter klausler3b635712020-02-13 14:41:56 -080084ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
85 return GetUnitMap().LookUpForClose(unit);
86}
87
88int ExternalFileUnit::NewUnit(const Terminator &terminator) {
89 return GetUnitMap().NewUnit(terminator).unitNumber();
peter klausler95696d52020-02-04 16:55:45 -080090}
91
peter klauslerea4758a2020-07-17 11:24:29 -070092void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional<Action> action,
93 Position position, OwningPtr<char> &&newPath, std::size_t newPathLength,
peter klausler8f2c5c42020-07-21 17:37:35 -070094 Convert convert, IoErrorHandler &handler) {
95 if (executionEnvironment.conversion != Convert::Unknown) {
96 convert = executionEnvironment.conversion;
97 }
98 swapEndianness_ = convert == Convert::Swap ||
99 (convert == Convert::LittleEndian && !isHostLittleEndian) ||
100 (convert == Convert::BigEndian && isHostLittleEndian);
peter klausler95696d52020-02-04 16:55:45 -0800101 if (IsOpen()) {
102 if (status == OpenStatus::Old &&
103 (!newPath.get() ||
104 (path() && pathLength() == newPathLength &&
105 std::memcmp(path(), newPath.get(), newPathLength) == 0))) {
106 // OPEN of existing unit, STATUS='OLD', not new FILE=
107 newPath.reset();
108 return;
109 }
110 // Otherwise, OPEN on open unit with new FILE= implies CLOSE
peter klausler00063542020-07-03 12:38:22 -0700111 DoImpliedEndfile(handler);
peter klausler95696d52020-02-04 16:55:45 -0800112 Flush(handler);
113 Close(CloseStatus::Keep, handler);
114 }
115 set_path(std::move(newPath), newPathLength);
peter klauslerea4758a2020-07-17 11:24:29 -0700116 Open(status, action, position, handler);
peter klausler00063542020-07-03 12:38:22 -0700117 auto totalBytes{knownSize()};
118 if (access == Access::Direct) {
119 if (!isFixedRecordLength || !recordLength) {
120 handler.SignalError(IostatOpenBadRecl,
121 "OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
122 unitNumber());
123 } else if (*recordLength <= 0) {
124 handler.SignalError(IostatOpenBadRecl,
125 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
126 unitNumber(), static_cast<std::intmax_t>(*recordLength));
peter klausler675ad1b2020-08-03 11:35:29 -0700127 } else if (totalBytes && (*totalBytes % *recordLength != 0)) {
peter klausler00063542020-07-03 12:38:22 -0700128 handler.SignalError(IostatOpenBadAppend,
129 "OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
130 "even divisor of the file size %jd",
131 unitNumber(), static_cast<std::intmax_t>(*recordLength),
132 static_cast<std::intmax_t>(*totalBytes));
133 }
134 }
peter klauslere29c9d72020-10-01 09:50:48 -0700135 endfileRecordNumber.reset();
136 currentRecordNumber = 1;
137 if (totalBytes && recordLength && *recordLength) {
138 endfileRecordNumber = 1 + (*totalBytes / *recordLength);
139 }
peter klausler00063542020-07-03 12:38:22 -0700140 if (position == Position::Append) {
peter klauslere29c9d72020-10-01 09:50:48 -0700141 if (!endfileRecordNumber) {
peter klausler00063542020-07-03 12:38:22 -0700142 // Fake it so that we can backspace relative from the end
peter klauslere29c9d72020-10-01 09:50:48 -0700143 endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
peter klausler00063542020-07-03 12:38:22 -0700144 }
145 currentRecordNumber = *endfileRecordNumber;
peter klausler00063542020-07-03 12:38:22 -0700146 }
peter klausler95696d52020-02-04 16:55:45 -0800147}
148
peter klausler675ad1b2020-08-03 11:35:29 -0700149void ExternalFileUnit::OpenAnonymousUnit(OpenStatus status,
150 std::optional<Action> action, Position position, Convert convert,
151 IoErrorHandler &handler) {
152 // I/O to an unconnected unit reads/creates a local file, e.g. fort.7
153 std::size_t pathMaxLen{32};
154 auto path{SizedNew<char>{handler}(pathMaxLen)};
155 std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
156 OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
157 convert, handler);
158}
159
peter klausler95696d52020-02-04 16:55:45 -0800160void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700161 DoImpliedEndfile(handler);
peter klausler3b635712020-02-13 14:41:56 -0800162 Flush(handler);
163 Close(status, handler);
peter klauslerf7be2512020-01-23 16:59:27 -0800164}
165
peter klausler3b635712020-02-13 14:41:56 -0800166void ExternalFileUnit::DestroyClosed() {
Tim Keith1f879002020-03-28 21:00:16 -0700167 GetUnitMap().DestroyClosed(*this); // destroys *this
peter klausler3b635712020-02-13 14:41:56 -0800168}
169
peter klausler00063542020-07-03 12:38:22 -0700170bool ExternalFileUnit::SetDirection(
171 Direction direction, IoErrorHandler &handler) {
172 if (direction == Direction::Input) {
173 if (mayRead()) {
174 direction_ = Direction::Input;
175 return true;
176 } else {
177 handler.SignalError(IostatReadFromWriteOnly,
178 "READ(UNIT=%d) with ACTION='WRITE'", unitNumber());
179 return false;
180 }
181 } else {
182 if (mayWrite()) {
183 direction_ = Direction::Output;
184 return true;
185 } else {
186 handler.SignalError(IostatWriteToReadOnly,
187 "WRITE(UNIT=%d) with ACTION='READ'", unitNumber());
188 return false;
189 }
190 }
191}
192
peter klausler3b635712020-02-13 14:41:56 -0800193UnitMap &ExternalFileUnit::GetUnitMap() {
194 if (unitMap) {
195 return *unitMap;
196 }
197 CriticalSection critical{unitMapLock};
198 if (unitMap) {
199 return *unitMap;
200 }
201 Terminator terminator{__FILE__, __LINE__};
peter klausler00063542020-07-03 12:38:22 -0700202 IoErrorHandler handler{terminator};
peter klausler98d576c2020-07-02 18:35:20 -0700203 unitMap = New<UnitMap>{terminator}().release();
peter klauslerbd43fa22020-07-14 11:28:03 -0700204 ExternalFileUnit &out{ExternalFileUnit::CreateNew(6, terminator)};
peter klauslerf7be2512020-01-23 16:59:27 -0800205 out.Predefine(1);
peter klausler00063542020-07-03 12:38:22 -0700206 out.SetDirection(Direction::Output, handler);
peter klausler95696d52020-02-04 16:55:45 -0800207 defaultOutput = &out;
peter klauslerbd43fa22020-07-14 11:28:03 -0700208 ExternalFileUnit &in{ExternalFileUnit::CreateNew(5, terminator)};
peter klauslerf7be2512020-01-23 16:59:27 -0800209 in.Predefine(0);
peter klausler00063542020-07-03 12:38:22 -0700210 in.SetDirection(Direction::Input, handler);
211 defaultInput = &in;
peter klauslerf7be2512020-01-23 16:59:27 -0800212 // TODO: Set UTF-8 mode from the environment
peter klausler3b635712020-02-13 14:41:56 -0800213 return *unitMap;
peter klauslerf7be2512020-01-23 16:59:27 -0800214}
215
peter klausler95696d52020-02-04 16:55:45 -0800216void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
peter klausler3b635712020-02-13 14:41:56 -0800217 CriticalSection critical{unitMapLock};
218 if (unitMap) {
219 unitMap->CloseAll(handler);
220 FreeMemoryAndNullify(unitMap);
221 }
peter klausler95696d52020-02-04 16:55:45 -0800222 defaultOutput = nullptr;
peter klauslerf7be2512020-01-23 16:59:27 -0800223}
224
peter klausler00063542020-07-03 12:38:22 -0700225void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
226 CriticalSection critical{unitMapLock};
227 if (unitMap) {
228 unitMap->FlushAll(handler);
229 }
230}
231
peter klausler8f2c5c42020-07-21 17:37:35 -0700232static void SwapEndianness(
233 char *data, std::size_t bytes, std::size_t elementBytes) {
234 if (elementBytes > 1) {
235 auto half{elementBytes >> 1};
236 for (std::size_t j{0}; j + elementBytes <= bytes; j += elementBytes) {
237 for (std::size_t k{0}; k < half; ++k) {
238 std::swap(data[j + k], data[j + elementBytes - 1 - k]);
239 }
240 }
241 }
242}
243
244bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
245 std::size_t elementBytes, IoErrorHandler &handler) {
peter klausler95696d52020-02-04 16:55:45 -0800246 auto furthestAfter{std::max(furthestPositionInRecord,
247 positionInRecord + static_cast<std::int64_t>(bytes))};
peter klausler3b635712020-02-13 14:41:56 -0800248 if (furthestAfter > recordLength.value_or(furthestAfter)) {
peter klausler00063542020-07-03 12:38:22 -0700249 handler.SignalError(IostatRecordWriteOverrun,
250 "Attempt to write %zd bytes to position %jd in a fixed-size record of "
251 "%jd bytes",
252 bytes, static_cast<std::intmax_t>(positionInRecord),
253 static_cast<std::intmax_t>(*recordLength));
peter klausler3b635712020-02-13 14:41:56 -0800254 return false;
255 }
256 WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
peter klausler27505562020-06-18 12:19:49 -0700257 if (positionInRecord > furthestPositionInRecord) {
peter klausler00063542020-07-03 12:38:22 -0700258 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord, ' ',
peter klausler27505562020-06-18 12:19:49 -0700259 positionInRecord - furthestPositionInRecord);
260 }
peter klausler8f2c5c42020-07-21 17:37:35 -0700261 char *to{Frame() + recordOffsetInFrame_ + positionInRecord};
262 std::memcpy(to, data, bytes);
263 if (swapEndianness_) {
264 SwapEndianness(to, bytes, elementBytes);
265 }
peter klauslerf7be2512020-01-23 16:59:27 -0800266 positionInRecord += bytes;
267 furthestPositionInRecord = furthestAfter;
268 return true;
269}
270
peter klausler8f2c5c42020-07-21 17:37:35 -0700271bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
272 std::size_t elementBytes, IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700273 RUNTIME_CHECK(handler, direction_ == Direction::Input);
274 auto furthestAfter{std::max(furthestPositionInRecord,
275 positionInRecord + static_cast<std::int64_t>(bytes))};
276 if (furthestAfter > recordLength.value_or(furthestAfter)) {
277 handler.SignalError(IostatRecordReadOverrun,
278 "Attempt to read %zd bytes at position %jd in a record of %jd bytes",
279 bytes, static_cast<std::intmax_t>(positionInRecord),
280 static_cast<std::intmax_t>(*recordLength));
281 return false;
282 }
283 auto need{recordOffsetInFrame_ + furthestAfter};
284 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
285 if (got >= need) {
286 std::memcpy(data, Frame() + recordOffsetInFrame_ + positionInRecord, bytes);
peter klausler8f2c5c42020-07-21 17:37:35 -0700287 if (swapEndianness_) {
288 SwapEndianness(data, bytes, elementBytes);
289 }
peter klausler00063542020-07-03 12:38:22 -0700290 positionInRecord += bytes;
291 furthestPositionInRecord = furthestAfter;
292 return true;
293 } else {
peter klauslercdfb95a2020-10-01 09:32:48 -0700294 // EOF or error: can be handled & has been signaled
peter klausler00063542020-07-03 12:38:22 -0700295 endfileRecordNumber = currentRecordNumber;
296 return false;
297 }
298}
299
peter klausler3b635712020-02-13 14:41:56 -0800300std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
301 IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700302 RUNTIME_CHECK(handler, direction_ == Direction::Input);
303 if (const char *p{FrameNextInput(handler, 1)}) {
304 // TODO: UTF-8 decoding; may have to get more bytes in a loop
305 return *p;
peter klausler3b635712020-02-13 14:41:56 -0800306 }
peter klausler00063542020-07-03 12:38:22 -0700307 return std::nullopt;
308}
309
310const char *ExternalFileUnit::FrameNextInput(
311 IoErrorHandler &handler, std::size_t bytes) {
312 RUNTIME_CHECK(handler, !isUnformatted);
313 if (static_cast<std::int64_t>(positionInRecord + bytes) <=
314 recordLength.value_or(positionInRecord + bytes)) {
315 auto at{recordOffsetInFrame_ + positionInRecord};
316 auto need{static_cast<std::size_t>(at + bytes)};
317 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
318 SetSequentialVariableFormattedRecordLength();
319 if (got >= need) {
320 return Frame() + at;
peter klausler3b635712020-02-13 14:41:56 -0800321 }
peter klausler3b635712020-02-13 14:41:56 -0800322 handler.SignalEnd();
peter klausler00063542020-07-03 12:38:22 -0700323 endfileRecordNumber = currentRecordNumber;
peter klausler3b635712020-02-13 14:41:56 -0800324 }
peter klausler00063542020-07-03 12:38:22 -0700325 return nullptr;
326}
327
328bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() {
329 if (recordLength || access != Access::Sequential) {
330 return true;
peter klausler3b635712020-02-13 14:41:56 -0800331 }
peter klausler00063542020-07-03 12:38:22 -0700332 if (FrameLength() > recordOffsetInFrame_) {
333 const char *record{Frame() + recordOffsetInFrame_};
334 if (const char *nl{reinterpret_cast<const char *>(
335 std::memchr(record, '\n', FrameLength() - recordOffsetInFrame_))}) {
336 recordLength = nl - record;
337 if (*recordLength > 0 && record[*recordLength - 1] == '\r') {
338 --*recordLength;
339 }
340 return true;
341 }
342 }
343 return false;
peter klausler3b635712020-02-13 14:41:56 -0800344}
345
peter klausler95696d52020-02-04 16:55:45 -0800346void ExternalFileUnit::SetLeftTabLimit() {
peter klauslerf7be2512020-01-23 16:59:27 -0800347 leftTabLimit = furthestPositionInRecord;
348 positionInRecord = furthestPositionInRecord;
349}
350
peter klausler00063542020-07-03 12:38:22 -0700351void ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
352 RUNTIME_CHECK(handler, direction_ == Direction::Input);
peter klauslere24f0ac2020-09-30 12:53:00 -0700353 if (beganReadingRecord_) {
354 return;
355 }
356 beganReadingRecord_ = true;
peter klausler00063542020-07-03 12:38:22 -0700357 if (access == Access::Sequential) {
358 if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
359 handler.SignalEnd();
360 } else if (isFixedRecordLength) {
361 RUNTIME_CHECK(handler, recordLength.has_value());
362 auto need{static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength)};
363 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
364 if (got < need) {
365 handler.SignalEnd();
peter klausler3b635712020-02-13 14:41:56 -0800366 }
peter klausler00063542020-07-03 12:38:22 -0700367 } else if (isUnformatted) {
368 BeginSequentialVariableUnformattedInputRecord(handler);
369 } else { // formatted
370 BeginSequentialVariableFormattedInputRecord(handler);
peter klausler3b635712020-02-13 14:41:56 -0800371 }
peter klauslerf7be2512020-01-23 16:59:27 -0800372 }
peter klausler00063542020-07-03 12:38:22 -0700373}
374
peter klauslere24f0ac2020-09-30 12:53:00 -0700375void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
376 RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_);
377 beganReadingRecord_ = false;
peter klauslere29c9d72020-10-01 09:50:48 -0700378 if (handler.GetIoStat() != IostatOk) {
379 // avoid bogus crashes in END/ERR circumstances
380 } else if (access == Access::Sequential) {
peter klauslere24f0ac2020-09-30 12:53:00 -0700381 RUNTIME_CHECK(handler, recordLength.has_value());
382 if (isFixedRecordLength) {
383 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength;
384 recordOffsetInFrame_ = 0;
385 } else if (isUnformatted) {
386 // Retain footer in frame for more efficient BACKSPACE
387 frameOffsetInFile_ += recordOffsetInFrame_ + *recordLength;
388 recordOffsetInFrame_ = sizeof(std::uint32_t);
389 recordLength.reset();
390 } else { // formatted
391 if (Frame()[recordOffsetInFrame_ + *recordLength] == '\r') {
392 ++recordOffsetInFrame_;
393 }
394 recordOffsetInFrame_ += *recordLength + 1;
395 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ - 1] == '\n');
396 recordLength.reset();
397 }
398 }
399 ++currentRecordNumber;
400 BeginRecord();
401}
402
peter klausler00063542020-07-03 12:38:22 -0700403bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
404 bool ok{true};
405 if (direction_ == Direction::Input) {
peter klauslere24f0ac2020-09-30 12:53:00 -0700406 FinishReadingRecord(handler);
407 BeginReadingRecord(handler);
peter klausler00063542020-07-03 12:38:22 -0700408 } else { // Direction::Output
peter klauslera94d9432020-10-01 10:59:09 -0700409 if (isFixedRecordLength && recordLength) {
410 // Pad remainder of fixed length record
411 if (furthestPositionInRecord < *recordLength) {
412 WriteFrame(
413 frameOffsetInFile_, recordOffsetInFrame_ + *recordLength, handler);
414 std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
415 isUnformatted ? 0 : ' ', *recordLength - furthestPositionInRecord);
416 }
417 } else {
418 positionInRecord = furthestPositionInRecord;
419 if (isUnformatted) {
420 // Append the length of a sequential unformatted variable-length record
421 // as its footer, then overwrite the reserved first four bytes of the
422 // record with its length as its header. These four bytes were skipped
423 // over in BeginUnformattedIO<Output>().
424 // TODO: Break very large records up into subrecords with negative
425 // headers &/or footers
426 std::uint32_t length;
427 length = furthestPositionInRecord - sizeof length;
428 ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length,
429 sizeof length, handler);
430 positionInRecord = 0;
431 ok &= Emit(reinterpret_cast<const char *>(&length), sizeof length,
432 sizeof length, handler);
peter klausler00063542020-07-03 12:38:22 -0700433 } else {
peter klauslera94d9432020-10-01 10:59:09 -0700434 // Terminate formatted variable length record
peter klausler8f2c5c42020-07-21 17:37:35 -0700435 ok &= Emit("\n", 1, 1, handler); // TODO: Windows CR+LF
peter klausler00063542020-07-03 12:38:22 -0700436 }
437 }
438 frameOffsetInFile_ +=
439 recordOffsetInFrame_ + recordLength.value_or(furthestPositionInRecord);
440 recordOffsetInFrame_ = 0;
441 impliedEndfile_ = true;
peter klauslere24f0ac2020-09-30 12:53:00 -0700442 ++currentRecordNumber;
443 BeginRecord();
peter klausler00063542020-07-03 12:38:22 -0700444 }
peter klauslerf7be2512020-01-23 16:59:27 -0800445 return ok;
446}
447
peter klausler3b635712020-02-13 14:41:56 -0800448void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700449 if (access != Access::Sequential) {
450 handler.SignalError(IostatBackspaceNonSequential,
451 "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber());
peter klausler3b635712020-02-13 14:41:56 -0800452 } else {
peter klauslere29c9d72020-10-01 09:50:48 -0700453 if (endfileRecordNumber && currentRecordNumber > *endfileRecordNumber) {
454 // BACKSPACE after ENDFILE
peter klausler00063542020-07-03 12:38:22 -0700455 } else {
peter klauslere29c9d72020-10-01 09:50:48 -0700456 DoImpliedEndfile(handler);
457 if (frameOffsetInFile_ + recordOffsetInFrame_ > 0) {
458 --currentRecordNumber;
459 if (isFixedRecordLength) {
460 BackspaceFixedRecord(handler);
461 } else if (isUnformatted) {
462 BackspaceVariableUnformattedRecord(handler);
463 } else {
464 BackspaceVariableFormattedRecord(handler);
465 }
466 }
peter klausler00063542020-07-03 12:38:22 -0700467 }
peter klauslere29c9d72020-10-01 09:50:48 -0700468 BeginRecord();
peter klausler3b635712020-02-13 14:41:56 -0800469 }
peter klauslerf7be2512020-01-23 16:59:27 -0800470}
471
peter klausler95696d52020-02-04 16:55:45 -0800472void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
473 if (isTerminal()) {
474 Flush(handler);
475 }
476}
477
peter klausler00063542020-07-03 12:38:22 -0700478void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
479 if (access != Access::Sequential) {
480 handler.SignalError(IostatEndfileNonSequential,
481 "ENDFILE(UNIT=%d) on non-sequential file", unitNumber());
482 } else if (!mayWrite()) {
483 handler.SignalError(IostatEndfileUnwritable,
484 "ENDFILE(UNIT=%d) on read-only file", unitNumber());
peter klauslere29c9d72020-10-01 09:50:48 -0700485 } else if (endfileRecordNumber &&
486 currentRecordNumber > *endfileRecordNumber) {
487 // ENDFILE after ENDFILE
peter klausler00063542020-07-03 12:38:22 -0700488 } else {
489 DoEndfile(handler);
peter klauslere29c9d72020-10-01 09:50:48 -0700490 ++currentRecordNumber;
peter klausler00063542020-07-03 12:38:22 -0700491 }
492}
493
494void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
495 if (access == Access::Direct) {
496 handler.SignalError(IostatRewindNonSequential,
497 "REWIND(UNIT=%d) on non-sequential file", unitNumber());
498 } else {
499 DoImpliedEndfile(handler);
500 SetPosition(0);
501 currentRecordNumber = 1;
peter klausler00063542020-07-03 12:38:22 -0700502 }
503}
504
peter klausler95696d52020-02-04 16:55:45 -0800505void ExternalFileUnit::EndIoStatement() {
peter klausler3b635712020-02-13 14:41:56 -0800506 frameOffsetInFile_ += recordOffsetInFrame_;
507 recordOffsetInFrame_ = 0;
peter klausler95696d52020-02-04 16:55:45 -0800508 io_.reset();
peter klauslerf7be2512020-01-23 16:59:27 -0800509 u_.emplace<std::monostate>();
peter klausler3b635712020-02-13 14:41:56 -0800510 lock_.Drop();
511}
512
peter klausler00063542020-07-03 12:38:22 -0700513void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
peter klausler3b635712020-02-13 14:41:56 -0800514 IoErrorHandler &handler) {
515 std::int32_t header{0}, footer{0};
peter klausler00063542020-07-03 12:38:22 -0700516 std::size_t need{recordOffsetInFrame_ + sizeof header};
517 std::size_t got{ReadFrame(frameOffsetInFile_, need, handler)};
peter klausler3b635712020-02-13 14:41:56 -0800518 // Try to emit informative errors to help debug corrupted files.
519 const char *error{nullptr};
520 if (got < need) {
peter klausler00063542020-07-03 12:38:22 -0700521 if (got == recordOffsetInFrame_) {
peter klausler3b635712020-02-13 14:41:56 -0800522 handler.SignalEnd();
523 } else {
peter klausler00063542020-07-03 12:38:22 -0700524 error = "Unformatted variable-length sequential file input failed at "
525 "record #%jd (file offset %jd): truncated record header";
peter klausler3b635712020-02-13 14:41:56 -0800526 }
527 } else {
peter klausler00063542020-07-03 12:38:22 -0700528 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
529 recordLength = sizeof header + header; // does not include footer
530 need = recordOffsetInFrame_ + *recordLength + sizeof footer;
531 got = ReadFrame(frameOffsetInFile_, need, handler);
peter klausler3b635712020-02-13 14:41:56 -0800532 if (got < need) {
peter klausler00063542020-07-03 12:38:22 -0700533 error = "Unformatted variable-length sequential file input failed at "
534 "record #%jd (file offset %jd): hit EOF reading record with "
535 "length %jd bytes";
peter klausler3b635712020-02-13 14:41:56 -0800536 } else {
peter klausler00063542020-07-03 12:38:22 -0700537 std::memcpy(&footer, Frame() + recordOffsetInFrame_ + *recordLength,
538 sizeof footer);
peter klausler3b635712020-02-13 14:41:56 -0800539 if (footer != header) {
peter klausler00063542020-07-03 12:38:22 -0700540 error = "Unformatted variable-length sequential file input failed at "
541 "record #%jd (file offset %jd): record header has length %jd "
542 "that does not match record footer (%jd)";
peter klausler3b635712020-02-13 14:41:56 -0800543 }
544 }
545 }
546 if (error) {
547 handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
548 static_cast<std::intmax_t>(frameOffsetInFile_),
549 static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
peter klausler00063542020-07-03 12:38:22 -0700550 // TODO: error recovery
peter klausler3b635712020-02-13 14:41:56 -0800551 }
552 positionInRecord = sizeof header;
553}
554
peter klausler00063542020-07-03 12:38:22 -0700555void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
peter klausler3b635712020-02-13 14:41:56 -0800556 IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700557 if (this == defaultInput && defaultOutput) {
558 defaultOutput->Flush(handler);
peter klausler3b635712020-02-13 14:41:56 -0800559 }
peter klausler00063542020-07-03 12:38:22 -0700560 std::size_t length{0};
561 do {
562 std::size_t need{recordOffsetInFrame_ + length + 1};
563 length = ReadFrame(frameOffsetInFile_, need, handler);
564 if (length < need) {
peter klausler3b635712020-02-13 14:41:56 -0800565 handler.SignalEnd();
566 break;
567 }
peter klausler00063542020-07-03 12:38:22 -0700568 } while (!SetSequentialVariableFormattedRecordLength());
569}
570
571void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
572 RUNTIME_CHECK(handler, recordLength.has_value());
573 if (frameOffsetInFile_ < *recordLength) {
574 handler.SignalError(IostatBackspaceAtFirstRecord);
575 } else {
576 frameOffsetInFile_ -= *recordLength;
peter klausler3b635712020-02-13 14:41:56 -0800577 }
578}
579
peter klausler00063542020-07-03 12:38:22 -0700580void ExternalFileUnit::BackspaceVariableUnformattedRecord(
peter klausler3b635712020-02-13 14:41:56 -0800581 IoErrorHandler &handler) {
582 std::int32_t header{0}, footer{0};
peter klausler00063542020-07-03 12:38:22 -0700583 auto headerBytes{static_cast<std::int64_t>(sizeof header)};
584 frameOffsetInFile_ += recordOffsetInFrame_;
585 recordOffsetInFrame_ = 0;
586 if (frameOffsetInFile_ <= headerBytes) {
587 handler.SignalError(IostatBackspaceAtFirstRecord);
588 return;
589 }
peter klausler3b635712020-02-13 14:41:56 -0800590 // Error conditions here cause crashes, not file format errors, because the
591 // validity of the file structure before the current record will have been
peter klausler00063542020-07-03 12:38:22 -0700592 // checked informatively in NextSequentialVariableUnformattedInputRecord().
peter klausler3b635712020-02-13 14:41:56 -0800593 std::size_t got{
peter klausler00063542020-07-03 12:38:22 -0700594 ReadFrame(frameOffsetInFile_ - headerBytes, headerBytes, handler)};
peter klausler3b635712020-02-13 14:41:56 -0800595 RUNTIME_CHECK(handler, got >= sizeof footer);
596 std::memcpy(&footer, Frame(), sizeof footer);
peter klausler3b635712020-02-13 14:41:56 -0800597 recordLength = footer;
peter klausler00063542020-07-03 12:38:22 -0700598 RUNTIME_CHECK(handler, frameOffsetInFile_ >= *recordLength + 2 * headerBytes);
599 frameOffsetInFile_ -= *recordLength + 2 * headerBytes;
600 if (frameOffsetInFile_ >= headerBytes) {
601 frameOffsetInFile_ -= headerBytes;
602 recordOffsetInFrame_ = headerBytes;
603 }
604 auto need{static_cast<std::size_t>(
605 recordOffsetInFrame_ + sizeof header + *recordLength)};
606 got = ReadFrame(frameOffsetInFile_, need, handler);
607 RUNTIME_CHECK(handler, got >= need);
608 std::memcpy(&header, Frame() + recordOffsetInFrame_, sizeof header);
609 RUNTIME_CHECK(handler, header == *recordLength);
peter klausler3b635712020-02-13 14:41:56 -0800610}
611
612// There's no portable memrchr(), unfortunately, and strrchr() would
613// fail on a record with a NUL, so we have to do it the hard way.
614static const char *FindLastNewline(const char *str, std::size_t length) {
615 for (const char *p{str + length}; p-- > str;) {
616 if (*p == '\n') {
617 return p;
618 }
619 }
620 return nullptr;
621}
622
peter klausler00063542020-07-03 12:38:22 -0700623void ExternalFileUnit::BackspaceVariableFormattedRecord(
peter klausler3b635712020-02-13 14:41:56 -0800624 IoErrorHandler &handler) {
peter klausler00063542020-07-03 12:38:22 -0700625 // File offset of previous record's newline
626 auto prevNL{
627 frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
628 if (prevNL < 0) {
629 handler.SignalError(IostatBackspaceAtFirstRecord);
630 return;
631 }
632 while (true) {
633 if (frameOffsetInFile_ < prevNL) {
peter klausler3b635712020-02-13 14:41:56 -0800634 if (const char *p{
peter klausler00063542020-07-03 12:38:22 -0700635 FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
peter klausler3b635712020-02-13 14:41:56 -0800636 recordOffsetInFrame_ = p - Frame() + 1;
peter klausler00063542020-07-03 12:38:22 -0700637 *recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
peter klausler3b635712020-02-13 14:41:56 -0800638 break;
639 }
peter klausler3b635712020-02-13 14:41:56 -0800640 }
peter klausler00063542020-07-03 12:38:22 -0700641 if (frameOffsetInFile_ == 0) {
642 recordOffsetInFrame_ = 0;
643 *recordLength = prevNL;
644 break;
645 }
646 frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
647 auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
648 auto got{ReadFrame(frameOffsetInFile_, need, handler)};
649 RUNTIME_CHECK(handler, got >= need);
peter klausler3b635712020-02-13 14:41:56 -0800650 }
peter klausler3b635712020-02-13 14:41:56 -0800651 RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n');
652 if (*recordLength > 0 &&
653 Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
654 --*recordLength;
655 }
peter klauslerf7be2512020-01-23 16:59:27 -0800656}
peter klausler00063542020-07-03 12:38:22 -0700657
658void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) {
659 if (impliedEndfile_) {
660 impliedEndfile_ = false;
661 if (access == Access::Sequential && mayPosition()) {
662 DoEndfile(handler);
663 }
664 }
665}
666
667void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) {
668 endfileRecordNumber = currentRecordNumber;
669 Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler);
670 BeginRecord();
671 impliedEndfile_ = false;
672}
Tim Keith1f879002020-03-28 21:00:16 -0700673} // namespace Fortran::runtime::io