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
26namespace Fortran::runtime::io {
27
28void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {
29 path_ = std::move(path);
30 pathLength_ = bytes;
31}
32
33static 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(template: path)};
53#endif
54 if (fd < 0) {
55 handler.SignalErrno();
56 }
57#ifndef _WIN32
58 ::unlink(name: path);
59#endif
60 return fd;
61}
62
63void 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 (position == Position::Append && !RawSeekToEnd()) {
135 handler.SignalError(IostatOpenBadAppend);
136 }
137 isTerminal_ = IsATerminal(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: fd_, buf: &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
158void 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_ = IsATerminal(fd_) == 1;
167 mayRead_ = fd == 0;
168 mayWrite_ = fd != 0;
169 mayPosition_ = false;
170#ifdef _WIN32
171 isWindowsTextFile_ = true;
172#endif
173}
174
175void 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
191std::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(a: minBytes, b: maxBytes);
201 std::size_t got{0};
202 while (got < minBytes) {
203 auto chunk{::read(fd: fd_, buf: buffer + got, nbytes: 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
220std::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: fd_, buf: buffer + put, n: 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
249inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
250#ifdef _WIN32
251 return ::_chsize(fd, at);
252#else
253 return ::ftruncate(fd: fd, length: at);
254#endif
255}
256
257void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
258 CheckOpen(handler);
259 if (!knownSize_ || *knownSize_ != at) {
260 if (openfile_ftruncate(fd: 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
270int 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: fd_, buf: buffer + got, nbytes: bytes - got, offset: 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
299int 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: fd_, buf: buffer + put, n: bytes - put, offset: 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
324void 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
343void 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
356Position 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
370void OpenFile::CheckOpen(const Terminator &terminator) {
371 RUNTIME_CHECK(terminator, fd_ >= 0);
372}
373
374bool 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
386bool OpenFile::RawSeek(FileOffset at) {
387#ifdef _LARGEFILE64_SOURCE
388 return ::lseek64(fd: fd_, offset: at, SEEK_SET) == at;
389#else
390 return ::lseek(fd_, at, SEEK_SET) == at;
391#endif
392}
393
394bool OpenFile::RawSeekToEnd() {
395#ifdef _LARGEFILE64_SOURCE
396 std::int64_t at{::lseek64(fd: fd_, offset: 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
408int 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
414void 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: fd_) != 0) {
420 handler.SignalErrno();
421 }
422 }
423 fd_ = -1;
424 }
425}
426
427#if !defined(RT_DEVICE_COMPILATION)
428bool IsATerminal(int fd) { return ::isatty(fd: 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
441bool IsExtant(const char *path) { return ::access(name: path, F_OK) == 0; }
442bool MayRead(const char *path) { return ::access(name: path, R_OK) == 0; }
443bool MayWrite(const char *path) { return ::access(name: path, W_OK) == 0; }
444bool MayReadAndWrite(const char *path) {
445 return ::access(name: path, R_OK | W_OK) == 0;
446}
447
448std::int64_t SizeInBytes(const char *path) {
449#ifndef _WIN32
450 struct stat buf;
451 if (::stat(file: path, buf: &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)
460bool IsATerminal(int fd) {
461 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
462}
463bool IsExtant(const char *path) {
464 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
465}
466bool MayRead(const char *path) {
467 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
468}
469bool MayWrite(const char *path) {
470 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
471}
472bool MayReadAndWrite(const char *path) {
473 Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
474}
475std::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

source code of flang/runtime/file.cpp