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