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(
49       tempFileName, _O_CREAT | _O_TEMPORARY | _O_RDWR, _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   if (fd_ >= 0) {
70     if (fd_ <= 2) {
71       // don't actually close a standard file descriptor, we might need it
72     } else {
73       if (::close(fd_) != 0) {
74         handler.SignalErrno();
75       }
76     }
77     fd_ = -1;
78   }
79   if (status == OpenStatus::Scratch) {
80     if (path_.get()) {
81       handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
82       path_.reset();
83     }
84     if (!action) {
85       action = Action::ReadWrite;
86     }
87     fd_ = openfile_mkstemp(handler);
88   } else {
89     if (!path_.get()) {
90       handler.SignalError("FILE= is required");
91       return;
92     }
93     int flags{0};
94     if (status != OpenStatus::Old) {
95       flags |= O_CREAT;
96     }
97     if (status == OpenStatus::New) {
98       flags |= O_EXCL;
99     } else if (status == OpenStatus::Replace) {
100       flags |= O_TRUNC;
101     }
102     if (!action) {
103       // Try to open read/write, back off to read-only on failure
104       fd_ = ::open(path_.get(), flags | O_RDWR, 0600);
105       if (fd_ >= 0) {
106         action = Action::ReadWrite;
107       } else {
108         action = Action::Read;
109       }
110     }
111     if (fd_ < 0) {
112       switch (*action) {
113       case Action::Read:
114         flags |= O_RDONLY;
115         break;
116       case Action::Write:
117         flags |= O_WRONLY;
118         break;
119       case Action::ReadWrite:
120         flags |= O_RDWR;
121         break;
122       }
123       fd_ = ::open(path_.get(), flags, 0600);
124       if (fd_ < 0) {
125         handler.SignalErrno();
126       }
127     }
128   }
129   RUNTIME_CHECK(handler, action.has_value());
130   pending_.reset();
131   if (position == Position::Append && !RawSeekToEnd()) {
132     handler.SignalErrno();
133   }
134   isTerminal_ = ::isatty(fd_) == 1;
135   mayRead_ = *action != Action::Write;
136   mayWrite_ = *action != Action::Read;
137   if (status == OpenStatus::Old || status == OpenStatus::Unknown) {
138     knownSize_.reset();
139 #ifndef _WIN32
140     struct stat buf;
141     if (::fstat(fd_, &buf) == 0) {
142       mayPosition_ = S_ISREG(buf.st_mode);
143       knownSize_ = buf.st_size;
144     }
145 #else // TODO: _WIN32
146     mayPosition_ = true;
147 #endif
148   } else {
149     knownSize_ = 0;
150     mayPosition_ = true;
151   }
152 }
153 
154 void OpenFile::Predefine(int fd) {
155   fd_ = fd;
156   path_.reset();
157   pathLength_ = 0;
158   position_ = 0;
159   knownSize_.reset();
160   nextId_ = 0;
161   pending_.reset();
162   mayRead_ = fd == 0;
163   mayWrite_ = fd != 0;
164   mayPosition_ = false;
165 }
166 
167 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
168   CheckOpen(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   if (fd_ >= 0) {
182     if (::close(fd_) != 0) {
183       handler.SignalErrno();
184     }
185     fd_ = -1;
186   }
187 }
188 
189 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
190     std::size_t maxBytes, IoErrorHandler &handler) {
191   if (maxBytes == 0) {
192     return 0;
193   }
194   CheckOpen(handler);
195   if (!Seek(at, handler)) {
196     return 0;
197   }
198   minBytes = std::min(minBytes, maxBytes);
199   std::size_t got{0};
200   while (got < minBytes) {
201     auto chunk{::read(fd_, buffer + got, maxBytes - got)};
202     if (chunk == 0) {
203       break;
204     } else if (chunk < 0) {
205       auto err{errno};
206       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
207         handler.SignalError(err);
208         break;
209       }
210     } else {
211       position_ += chunk;
212       got += chunk;
213     }
214   }
215   return got;
216 }
217 
218 std::size_t OpenFile::Write(FileOffset at, const char *buffer,
219     std::size_t bytes, IoErrorHandler &handler) {
220   if (bytes == 0) {
221     return 0;
222   }
223   CheckOpen(handler);
224   if (!Seek(at, handler)) {
225     return 0;
226   }
227   std::size_t put{0};
228   while (put < bytes) {
229     auto chunk{::write(fd_, buffer + put, bytes - put)};
230     if (chunk >= 0) {
231       position_ += chunk;
232       put += chunk;
233     } else {
234       auto err{errno};
235       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
236         handler.SignalError(err);
237         break;
238       }
239     }
240   }
241   if (knownSize_ && position_ > *knownSize_) {
242     knownSize_ = position_;
243   }
244   return put;
245 }
246 
247 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
248 #ifdef _WIN32
249   return ::_chsize(fd, at);
250 #else
251   return ::ftruncate(fd, at);
252 #endif
253 }
254 
255 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
256   CheckOpen(handler);
257   if (!knownSize_ || *knownSize_ != at) {
258     if (openfile_ftruncate(fd_, at) != 0) {
259       handler.SignalErrno();
260     }
261     knownSize_ = at;
262   }
263 }
264 
265 // The operation is performed immediately; the results are saved
266 // to be claimed by a later WAIT statement.
267 // TODO: True asynchronicity
268 int OpenFile::ReadAsynchronously(
269     FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
270   CheckOpen(handler);
271   int iostat{0};
272   for (std::size_t got{0}; got < bytes;) {
273 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
274     auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
275 #else
276     auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
277 #endif
278     if (chunk == 0) {
279       iostat = FORTRAN_RUNTIME_IOSTAT_END;
280       break;
281     }
282     if (chunk < 0) {
283       auto err{errno};
284       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
285         iostat = err;
286         break;
287       }
288     } else {
289       at += chunk;
290       got += chunk;
291     }
292   }
293   return PendingResult(handler, iostat);
294 }
295 
296 // TODO: True asynchronicity
297 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
298     std::size_t bytes, IoErrorHandler &handler) {
299   CheckOpen(handler);
300   int iostat{0};
301   for (std::size_t put{0}; put < bytes;) {
302 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
303     auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
304 #else
305     auto chunk{
306         Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
307 #endif
308     if (chunk >= 0) {
309       at += chunk;
310       put += chunk;
311     } else {
312       auto err{errno};
313       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
314         iostat = err;
315         break;
316       }
317     }
318   }
319   return PendingResult(handler, iostat);
320 }
321 
322 void OpenFile::Wait(int id, IoErrorHandler &handler) {
323   std::optional<int> ioStat;
324   Pending *prev{nullptr};
325   for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
326     if (p->id == id) {
327       ioStat = p->ioStat;
328       if (prev) {
329         prev->next.reset(p->next.release());
330       } else {
331         pending_.reset(p->next.release());
332       }
333       break;
334     }
335   }
336   if (ioStat) {
337     handler.SignalError(*ioStat);
338   }
339 }
340 
341 void OpenFile::WaitAll(IoErrorHandler &handler) {
342   while (true) {
343     int ioStat;
344     if (pending_) {
345       ioStat = pending_->ioStat;
346       pending_.reset(pending_->next.release());
347     } else {
348       return;
349     }
350     handler.SignalError(ioStat);
351   }
352 }
353 
354 void OpenFile::CheckOpen(const Terminator &terminator) {
355   RUNTIME_CHECK(terminator, fd_ >= 0);
356 }
357 
358 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
359   if (at == position_) {
360     return true;
361   } else if (RawSeek(at)) {
362     position_ = at;
363     return true;
364   } else {
365     handler.SignalErrno();
366     return false;
367   }
368 }
369 
370 bool OpenFile::RawSeek(FileOffset at) {
371 #ifdef _LARGEFILE64_SOURCE
372   return ::lseek64(fd_, at, SEEK_SET) == at;
373 #else
374   return ::lseek(fd_, at, SEEK_SET) == at;
375 #endif
376 }
377 
378 bool OpenFile::RawSeekToEnd() {
379 #ifdef _LARGEFILE64_SOURCE
380   std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
381 #else
382   std::int64_t at{::lseek(fd_, 0, SEEK_END)};
383 #endif
384   if (at >= 0) {
385     knownSize_ = at;
386     return true;
387   } else {
388     return false;
389   }
390 }
391 
392 int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
393   int id{nextId_++};
394   pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
395   return id;
396 }
397 
398 bool IsATerminal(int fd) { return ::isatty(fd); }
399 
400 #ifdef WIN32
401 // Access flags are normally defined in unistd.h, which unavailable under
402 // Windows. Instead, define the flags as documented at
403 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
404 #define F_OK 00
405 #define W_OK 02
406 #define R_OK 04
407 #endif
408 
409 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
410 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
411 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
412 bool MayReadAndWrite(const char *path) {
413   return ::access(path, R_OK | W_OK) == 0;
414 }
415 } // namespace Fortran::runtime::io
416