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