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