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