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