1 //===-- runtime/file.cpp --------------------------------------------------===//
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 "file.h"
10 #include "flang/Runtime/magic-numbers.h"
11 #include "flang/Runtime/memory.h"
12 #include <algorithm>
13 #include <cerrno>
14 #include <cstring>
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #include <sys/stat.h>
18 #ifdef _WIN32
19 #define NOMINMAX
20 #include <io.h>
21 #include <windows.h>
22 #else
23 #include <unistd.h>
24 #endif
25 
26 namespace Fortran::runtime::io {
27 
28 void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {
29   path_ = std::move(path);
30   pathLength_ = bytes;
31 }
32 
33 static int openfile_mkstemp(IoErrorHandler &handler) {
34 #ifdef _WIN32
35   const unsigned int uUnique{0};
36   // GetTempFileNameA needs a directory name < MAX_PATH-14 characters in length.
37   // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea
38   char tempDirName[MAX_PATH - 14];
39   char tempFileName[MAX_PATH];
40   unsigned long nBufferLength{sizeof(tempDirName)};
41   nBufferLength = ::GetTempPathA(nBufferLength, tempDirName);
42   if (nBufferLength > sizeof(tempDirName) || nBufferLength == 0) {
43     return -1;
44   }
45   if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) {
46     return -1;
47   }
48   int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,
49       _S_IREAD | _S_IWRITE)};
50 #else
51   char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
52   int fd{::mkstemp(path)};
53 #endif
54   if (fd < 0) {
55     handler.SignalErrno();
56   }
57 #ifndef _WIN32
58   ::unlink(path);
59 #endif
60   return fd;
61 }
62 
63 void OpenFile::Open(OpenStatus status, std::optional<Action> action,
64     Position position, IoErrorHandler &handler) {
65   if (fd_ >= 0 &&
66       (status == OpenStatus::Old || status == OpenStatus::Unknown)) {
67     return;
68   }
69   CloseFd(handler);
70   if (status == OpenStatus::Scratch) {
71     if (path_.get()) {
72       handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
73       path_.reset();
74     }
75     if (!action) {
76       action = Action::ReadWrite;
77     }
78     fd_ = openfile_mkstemp(handler);
79   } else {
80     if (!path_.get()) {
81       handler.SignalError("FILE= is required");
82       return;
83     }
84     int flags{0};
85 #ifdef _WIN32
86     // We emit explicit CR+LF line endings and cope with them on input
87     // for formatted files, since we can't yet always know now at OPEN
88     // time whether the file is formatted or not.
89     flags |= O_BINARY;
90 #endif
91     if (status != OpenStatus::Old) {
92       flags |= O_CREAT;
93     }
94     if (status == OpenStatus::New) {
95       flags |= O_EXCL;
96     } else if (status == OpenStatus::Replace) {
97       flags |= O_TRUNC;
98     }
99     if (!action) {
100       // Try to open read/write, back off to read-only on failure
101       fd_ = ::open(path_.get(), flags | O_RDWR, 0600);
102       if (fd_ >= 0) {
103         action = Action::ReadWrite;
104       } else {
105         action = Action::Read;
106       }
107     }
108     if (fd_ < 0) {
109       switch (*action) {
110       case Action::Read:
111         flags |= O_RDONLY;
112         break;
113       case Action::Write:
114         flags |= O_WRONLY;
115         break;
116       case Action::ReadWrite:
117         flags |= O_RDWR;
118         break;
119       }
120       fd_ = ::open(path_.get(), flags, 0600);
121       if (fd_ < 0) {
122         handler.SignalErrno();
123       }
124     }
125   }
126   RUNTIME_CHECK(handler, action.has_value());
127   pending_.reset();
128   if (position == Position::Append && !RawSeekToEnd()) {
129     handler.SignalErrno();
130   }
131   isTerminal_ = ::isatty(fd_) == 1;
132   mayRead_ = *action != Action::Write;
133   mayWrite_ = *action != Action::Read;
134   if (status == OpenStatus::Old || status == OpenStatus::Unknown) {
135     knownSize_.reset();
136 #ifndef _WIN32
137     struct stat buf;
138     if (::fstat(fd_, &buf) == 0) {
139       mayPosition_ = S_ISREG(buf.st_mode);
140       knownSize_ = buf.st_size;
141     }
142 #else // TODO: _WIN32
143     mayPosition_ = true;
144 #endif
145   } else {
146     knownSize_ = 0;
147     mayPosition_ = true;
148   }
149   openPosition_ = position; // for INQUIRE(POSITION=)
150 }
151 
152 void OpenFile::Predefine(int fd) {
153   fd_ = fd;
154   path_.reset();
155   pathLength_ = 0;
156   position_ = 0;
157   knownSize_.reset();
158   nextId_ = 0;
159   pending_.reset();
160   mayRead_ = fd == 0;
161   mayWrite_ = fd != 0;
162   mayPosition_ = false;
163 #ifdef _WIN32
164   isWindowsTextFile_ = true;
165 #endif
166 }
167 
168 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
169   pending_.reset();
170   knownSize_.reset();
171   switch (status) {
172   case CloseStatus::Keep:
173     break;
174   case CloseStatus::Delete:
175     if (path_.get()) {
176       ::unlink(path_.get());
177     }
178     break;
179   }
180   path_.reset();
181   CloseFd(handler);
182 }
183 
184 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
185     std::size_t maxBytes, IoErrorHandler &handler) {
186   if (maxBytes == 0) {
187     return 0;
188   }
189   CheckOpen(handler);
190   if (!Seek(at, handler)) {
191     return 0;
192   }
193   minBytes = std::min(minBytes, maxBytes);
194   std::size_t got{0};
195   while (got < minBytes) {
196     auto chunk{::read(fd_, buffer + got, maxBytes - got)};
197     if (chunk == 0) {
198       break;
199     } else if (chunk < 0) {
200       auto err{errno};
201       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
202         handler.SignalError(err);
203         break;
204       }
205     } else {
206       SetPosition(position_ + chunk);
207       got += chunk;
208     }
209   }
210   return got;
211 }
212 
213 std::size_t OpenFile::Write(FileOffset at, const char *buffer,
214     std::size_t bytes, IoErrorHandler &handler) {
215   if (bytes == 0) {
216     return 0;
217   }
218   CheckOpen(handler);
219   if (!Seek(at, handler)) {
220     return 0;
221   }
222   std::size_t put{0};
223   while (put < bytes) {
224     auto chunk{::write(fd_, buffer + put, bytes - put)};
225     if (chunk >= 0) {
226       SetPosition(position_ + chunk);
227       put += chunk;
228     } else {
229       auto err{errno};
230       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
231         handler.SignalError(err);
232         break;
233       }
234     }
235   }
236   if (knownSize_ && position_ > *knownSize_) {
237     knownSize_ = position_;
238   }
239   return put;
240 }
241 
242 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
243 #ifdef _WIN32
244   return ::_chsize(fd, at);
245 #else
246   return ::ftruncate(fd, at);
247 #endif
248 }
249 
250 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
251   CheckOpen(handler);
252   if (!knownSize_ || *knownSize_ != at) {
253     if (openfile_ftruncate(fd_, at) != 0) {
254       handler.SignalErrno();
255     }
256     knownSize_ = at;
257   }
258 }
259 
260 // The operation is performed immediately; the results are saved
261 // to be claimed by a later WAIT statement.
262 // TODO: True asynchronicity
263 int OpenFile::ReadAsynchronously(
264     FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
265   CheckOpen(handler);
266   int iostat{0};
267   for (std::size_t got{0}; got < bytes;) {
268 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
269     auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
270 #else
271     auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
272 #endif
273     if (chunk == 0) {
274       iostat = FORTRAN_RUNTIME_IOSTAT_END;
275       break;
276     }
277     if (chunk < 0) {
278       auto err{errno};
279       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
280         iostat = err;
281         break;
282       }
283     } else {
284       at += chunk;
285       got += chunk;
286     }
287   }
288   return PendingResult(handler, iostat);
289 }
290 
291 // TODO: True asynchronicity
292 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
293     std::size_t bytes, IoErrorHandler &handler) {
294   CheckOpen(handler);
295   int iostat{0};
296   for (std::size_t put{0}; put < bytes;) {
297 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
298     auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
299 #else
300     auto chunk{
301         Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
302 #endif
303     if (chunk >= 0) {
304       at += chunk;
305       put += chunk;
306     } else {
307       auto err{errno};
308       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
309         iostat = err;
310         break;
311       }
312     }
313   }
314   return PendingResult(handler, iostat);
315 }
316 
317 void OpenFile::Wait(int id, IoErrorHandler &handler) {
318   std::optional<int> ioStat;
319   Pending *prev{nullptr};
320   for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
321     if (p->id == id) {
322       ioStat = p->ioStat;
323       if (prev) {
324         prev->next.reset(p->next.release());
325       } else {
326         pending_.reset(p->next.release());
327       }
328       break;
329     }
330   }
331   if (ioStat) {
332     handler.SignalError(*ioStat);
333   }
334 }
335 
336 void OpenFile::WaitAll(IoErrorHandler &handler) {
337   while (true) {
338     int ioStat;
339     if (pending_) {
340       ioStat = pending_->ioStat;
341       pending_.reset(pending_->next.release());
342     } else {
343       return;
344     }
345     handler.SignalError(ioStat);
346   }
347 }
348 
349 Position OpenFile::InquirePosition() const {
350   if (openPosition_) { // from OPEN statement
351     return *openPosition_;
352   } else { // unit has been repositioned since opening
353     if (position_ == knownSize_.value_or(position_ + 1)) {
354       return Position::Append;
355     } else if (position_ == 0 && mayPosition_) {
356       return Position::Rewind;
357     } else {
358       return Position::AsIs; // processor-dependent & no common behavior
359     }
360   }
361 }
362 
363 void OpenFile::CheckOpen(const Terminator &terminator) {
364   RUNTIME_CHECK(terminator, fd_ >= 0);
365 }
366 
367 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
368   if (at == position_) {
369     return true;
370   } else if (RawSeek(at)) {
371     SetPosition(at);
372     return true;
373   } else {
374     handler.SignalErrno();
375     return false;
376   }
377 }
378 
379 bool OpenFile::RawSeek(FileOffset at) {
380 #ifdef _LARGEFILE64_SOURCE
381   return ::lseek64(fd_, at, SEEK_SET) == at;
382 #else
383   return ::lseek(fd_, at, SEEK_SET) == at;
384 #endif
385 }
386 
387 bool OpenFile::RawSeekToEnd() {
388 #ifdef _LARGEFILE64_SOURCE
389   std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
390 #else
391   std::int64_t at{::lseek(fd_, 0, SEEK_END)};
392 #endif
393   if (at >= 0) {
394     knownSize_ = at;
395     return true;
396   } else {
397     return false;
398   }
399 }
400 
401 int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
402   int id{nextId_++};
403   pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
404   return id;
405 }
406 
407 void OpenFile::CloseFd(IoErrorHandler &handler) {
408   if (fd_ >= 0) {
409     if (fd_ <= 2) {
410       // don't actually close a standard file descriptor, we might need it
411     } else {
412       if (::close(fd_) != 0) {
413         handler.SignalErrno();
414       }
415     }
416     fd_ = -1;
417   }
418 }
419 
420 bool IsATerminal(int fd) { return ::isatty(fd); }
421 
422 #ifdef WIN32
423 // Access flags are normally defined in unistd.h, which unavailable under
424 // Windows. Instead, define the flags as documented at
425 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
426 #define F_OK 00
427 #define W_OK 02
428 #define R_OK 04
429 #endif
430 
431 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
432 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
433 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
434 bool MayReadAndWrite(const char *path) {
435   return ::access(path, R_OK | W_OK) == 0;
436 }
437 } // namespace Fortran::runtime::io
438