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