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