xref: /llvm-project/flang/runtime/file.cpp (revision 874a3ba868e3738d6ee21bfe032c89c6c8be3969)
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 "tools.h"
11 #include "flang/Runtime/magic-numbers.h"
12 #include "flang/Runtime/memory.h"
13 #include <algorithm>
14 #include <cerrno>
15 #include <cstring>
16 #include <fcntl.h>
17 #include <stdlib.h>
18 #include <sys/stat.h>
19 #ifdef _WIN32
20 #include "flang/Common/windows-include.h"
21 #include <io.h>
22 #else
23 #include <unistd.h>
24 #endif
25 
26 namespace Fortran::runtime::io {
27 
28 void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {
29   path_ = std::move(path);
30   pathLength_ = bytes;
31 }
32 
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 
63 void OpenFile::Open(OpenStatus status, Fortran::common::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 (fd_ >= 0 && position == Position::Append && !RawSeekToEnd()) {
135     handler.SignalError(IostatOpenBadAppend);
136   }
137   isTerminal_ = fd_ >= 0 && IsATerminal(fd_);
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 (fd_ >= 0 && ::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 
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   isTerminal_ = fd == 2 || IsATerminal(fd_);
167   mayRead_ = fd == 0;
168   mayWrite_ = fd != 0;
169   mayPosition_ = false;
170 #ifdef _WIN32
171   isWindowsTextFile_ = true;
172 #endif
173 }
174 
175 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
176   pending_.reset();
177   knownSize_.reset();
178   switch (status) {
179   case CloseStatus::Keep:
180     break;
181   case CloseStatus::Delete:
182     if (path_.get()) {
183       ::unlink(path_.get());
184     }
185     break;
186   }
187   path_.reset();
188   CloseFd(handler);
189 }
190 
191 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
192     std::size_t maxBytes, IoErrorHandler &handler) {
193   if (maxBytes == 0) {
194     return 0;
195   }
196   CheckOpen(handler);
197   if (!Seek(at, handler)) {
198     return 0;
199   }
200   minBytes = std::min(minBytes, maxBytes);
201   std::size_t got{0};
202   while (got < minBytes) {
203     auto chunk{::read(fd_, buffer + got, maxBytes - got)};
204     if (chunk == 0) {
205       break;
206     } else if (chunk < 0) {
207       auto err{errno};
208       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
209         handler.SignalError(err);
210         break;
211       }
212     } else {
213       SetPosition(position_ + chunk);
214       got += chunk;
215     }
216   }
217   return got;
218 }
219 
220 std::size_t OpenFile::Write(FileOffset at, const char *buffer,
221     std::size_t bytes, IoErrorHandler &handler) {
222   if (bytes == 0) {
223     return 0;
224   }
225   CheckOpen(handler);
226   if (!Seek(at, handler)) {
227     return 0;
228   }
229   std::size_t put{0};
230   while (put < bytes) {
231     auto chunk{::write(fd_, buffer + put, bytes - put)};
232     if (chunk >= 0) {
233       SetPosition(position_ + chunk);
234       put += chunk;
235     } else {
236       auto err{errno};
237       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
238         handler.SignalError(err);
239         break;
240       }
241     }
242   }
243   if (knownSize_ && position_ > *knownSize_) {
244     knownSize_ = position_;
245   }
246   return put;
247 }
248 
249 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
250 #ifdef _WIN32
251   return ::_chsize(fd, at);
252 #else
253   return ::ftruncate(fd, at);
254 #endif
255 }
256 
257 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
258   CheckOpen(handler);
259   if (!knownSize_ || *knownSize_ != at) {
260     if (openfile_ftruncate(fd_, at) != 0) {
261       handler.SignalErrno();
262     }
263     knownSize_ = at;
264   }
265 }
266 
267 // The operation is performed immediately; the results are saved
268 // to be claimed by a later WAIT statement.
269 // TODO: True asynchronicity
270 int OpenFile::ReadAsynchronously(
271     FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
272   CheckOpen(handler);
273   int iostat{0};
274   for (std::size_t got{0}; got < bytes;) {
275 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
276     auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
277 #else
278     auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
279 #endif
280     if (chunk == 0) {
281       iostat = FORTRAN_RUNTIME_IOSTAT_END;
282       break;
283     }
284     if (chunk < 0) {
285       auto err{errno};
286       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
287         iostat = err;
288         break;
289       }
290     } else {
291       at += chunk;
292       got += chunk;
293     }
294   }
295   return PendingResult(handler, iostat);
296 }
297 
298 // TODO: True asynchronicity
299 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
300     std::size_t bytes, IoErrorHandler &handler) {
301   CheckOpen(handler);
302   int iostat{0};
303   for (std::size_t put{0}; put < bytes;) {
304 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
305     auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
306 #else
307     auto chunk{
308         Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
309 #endif
310     if (chunk >= 0) {
311       at += chunk;
312       put += chunk;
313     } else {
314       auto err{errno};
315       if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
316         iostat = err;
317         break;
318       }
319     }
320   }
321   return PendingResult(handler, iostat);
322 }
323 
324 void OpenFile::Wait(int id, IoErrorHandler &handler) {
325   Fortran::common::optional<int> ioStat;
326   Pending *prev{nullptr};
327   for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
328     if (p->id == id) {
329       ioStat = p->ioStat;
330       if (prev) {
331         prev->next.reset(p->next.release());
332       } else {
333         pending_.reset(p->next.release());
334       }
335       break;
336     }
337   }
338   if (ioStat) {
339     handler.SignalError(*ioStat);
340   }
341 }
342 
343 void OpenFile::WaitAll(IoErrorHandler &handler) {
344   while (true) {
345     int ioStat;
346     if (pending_) {
347       ioStat = pending_->ioStat;
348       pending_.reset(pending_->next.release());
349     } else {
350       return;
351     }
352     handler.SignalError(ioStat);
353   }
354 }
355 
356 Position OpenFile::InquirePosition() const {
357   if (openPosition_) { // from OPEN statement
358     return *openPosition_;
359   } else { // unit has been repositioned since opening
360     if (position_ == knownSize_.value_or(position_ + 1)) {
361       return Position::Append;
362     } else if (position_ == 0 && mayPosition_) {
363       return Position::Rewind;
364     } else {
365       return Position::AsIs; // processor-dependent & no common behavior
366     }
367   }
368 }
369 
370 void OpenFile::CheckOpen(const Terminator &terminator) {
371   RUNTIME_CHECK(terminator, fd_ >= 0);
372 }
373 
374 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
375   if (at == position_) {
376     return true;
377   } else if (RawSeek(at)) {
378     SetPosition(at);
379     return true;
380   } else {
381     handler.SignalError(IostatCannotReposition);
382     return false;
383   }
384 }
385 
386 bool OpenFile::RawSeek(FileOffset at) {
387 #ifdef _LARGEFILE64_SOURCE
388   return ::lseek64(fd_, at, SEEK_SET) == at;
389 #else
390   return ::lseek(fd_, at, SEEK_SET) == at;
391 #endif
392 }
393 
394 bool OpenFile::RawSeekToEnd() {
395 #ifdef _LARGEFILE64_SOURCE
396   std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
397 #else
398   std::int64_t at{::lseek(fd_, 0, SEEK_END)};
399 #endif
400   if (at >= 0) {
401     knownSize_ = at;
402     return true;
403   } else {
404     return false;
405   }
406 }
407 
408 int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
409   int id{nextId_++};
410   pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
411   return id;
412 }
413 
414 void OpenFile::CloseFd(IoErrorHandler &handler) {
415   if (fd_ >= 0) {
416     if (fd_ <= 2) {
417       // don't actually close a standard file descriptor, we might need it
418     } else {
419       if (::close(fd_) != 0) {
420         handler.SignalErrno();
421       }
422     }
423     fd_ = -1;
424   }
425 }
426 
427 #if !defined(RT_DEVICE_COMPILATION)
428 bool IsATerminal(int fd) { return ::isatty(fd); }
429 
430 #if defined(_WIN32) && !defined(F_OK)
431 // Access flags are normally defined in unistd.h, which unavailable under
432 // Windows. Instead, define the flags as documented at
433 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
434 // On Mingw, io.h does define these same constants - so check whether they
435 // already are defined before defining these.
436 #define F_OK 00
437 #define W_OK 02
438 #define R_OK 04
439 #endif
440 
441 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
442 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
443 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
444 bool MayReadAndWrite(const char *path) {
445   return ::access(path, R_OK | W_OK) == 0;
446 }
447 
448 std::int64_t SizeInBytes(const char *path) {
449 #ifndef _WIN32
450   struct stat buf;
451   if (::stat(path, &buf) == 0) {
452     return buf.st_size;
453   }
454 #else // TODO: _WIN32
455 #endif
456   // No Fortran compiler signals an error
457   return -1;
458 }
459 #else // defined(RT_DEVICE_COMPILATION)
460 RT_API_ATTRS bool IsATerminal(int fd) {
461   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
462 }
463 RT_API_ATTRS bool IsExtant(const char *path) {
464   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
465 }
466 RT_API_ATTRS bool MayRead(const char *path) {
467   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
468 }
469 RT_API_ATTRS bool MayWrite(const char *path) {
470   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
471 }
472 RT_API_ATTRS bool MayReadAndWrite(const char *path) {
473   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
474 }
475 RT_API_ATTRS std::int64_t SizeInBytes(const char *path) {
476   Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
477 }
478 #endif // defined(RT_DEVICE_COMPILATION)
479 
480 } // namespace Fortran::runtime::io
481