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