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