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 #define NOMINMAX
19 #include <io.h>
20 #include <windows.h>
21 #else
22 #include <sys/stat.h>
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_TEMPORARY, _S_IREAD | _S_IWRITE)};
49 #else
50 char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
51 int fd{::mkstemp(path)};
52 #endif
53 if (fd < 0) {
54 handler.SignalErrno();
55 }
56 #ifndef _WIN32
57 ::unlink(path);
58 #endif
59 return fd;
60 }
61
Open(OpenStatus status,std::optional<Action> action,Position position,IoErrorHandler & handler)62 void OpenFile::Open(OpenStatus status, std::optional<Action> action,
63 Position position, IoErrorHandler &handler) {
64 if (fd_ >= 0 &&
65 (status == OpenStatus::Old || status == OpenStatus::Unknown)) {
66 return;
67 }
68 if (fd_ >= 0) {
69 if (fd_ <= 2) {
70 // don't actually close a standard file descriptor, we might need it
71 } else {
72 if (::close(fd_) != 0) {
73 handler.SignalErrno();
74 }
75 }
76 fd_ = -1;
77 }
78 if (status == OpenStatus::Scratch) {
79 if (path_.get()) {
80 handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
81 path_.reset();
82 }
83 if (!action) {
84 action = Action::ReadWrite;
85 }
86 fd_ = openfile_mkstemp(handler);
87 } else {
88 if (!path_.get()) {
89 handler.SignalError("FILE= is required");
90 return;
91 }
92 int flags{0};
93 if (status != OpenStatus::Old) {
94 flags |= O_CREAT;
95 }
96 if (status == OpenStatus::New) {
97 flags |= O_EXCL;
98 } else if (status == OpenStatus::Replace) {
99 flags |= O_TRUNC;
100 }
101 if (!action) {
102 // Try to open read/write, back off to read-only on failure
103 fd_ = ::open(path_.get(), flags | O_RDWR, 0600);
104 if (fd_ >= 0) {
105 action = Action::ReadWrite;
106 } else {
107 action = Action::Read;
108 }
109 }
110 if (fd_ < 0) {
111 switch (*action) {
112 case Action::Read:
113 flags |= O_RDONLY;
114 break;
115 case Action::Write:
116 flags |= O_WRONLY;
117 break;
118 case Action::ReadWrite:
119 flags |= O_RDWR;
120 break;
121 }
122 fd_ = ::open(path_.get(), flags, 0600);
123 if (fd_ < 0) {
124 handler.SignalErrno();
125 }
126 }
127 }
128 RUNTIME_CHECK(handler, action.has_value());
129 pending_.reset();
130 if (position == Position::Append && !RawSeekToEnd()) {
131 handler.SignalErrno();
132 }
133 isTerminal_ = ::isatty(fd_) == 1;
134 mayRead_ = *action != Action::Write;
135 mayWrite_ = *action != Action::Read;
136 if (status == OpenStatus::Old || status == OpenStatus::Unknown) {
137 knownSize_.reset();
138 #ifndef _WIN32
139 struct stat buf;
140 if (::fstat(fd_, &buf) == 0) {
141 mayPosition_ = S_ISREG(buf.st_mode);
142 knownSize_ = buf.st_size;
143 }
144 #else // TODO: _WIN32
145 mayPosition_ = true;
146 #endif
147 } else {
148 knownSize_ = 0;
149 mayPosition_ = true;
150 }
151 }
152
Predefine(int fd)153 void OpenFile::Predefine(int fd) {
154 fd_ = fd;
155 path_.reset();
156 pathLength_ = 0;
157 position_ = 0;
158 knownSize_.reset();
159 nextId_ = 0;
160 pending_.reset();
161 mayRead_ = fd == 0;
162 mayWrite_ = fd != 0;
163 mayPosition_ = false;
164 }
165
Close(CloseStatus status,IoErrorHandler & handler)166 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
167 CheckOpen(handler);
168 pending_.reset();
169 knownSize_.reset();
170 switch (status) {
171 case CloseStatus::Keep:
172 break;
173 case CloseStatus::Delete:
174 if (path_.get()) {
175 ::unlink(path_.get());
176 }
177 break;
178 }
179 path_.reset();
180 if (fd_ >= 0) {
181 if (::close(fd_) != 0) {
182 handler.SignalErrno();
183 }
184 fd_ = -1;
185 }
186 }
187
Read(FileOffset at,char * buffer,std::size_t minBytes,std::size_t maxBytes,IoErrorHandler & handler)188 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
189 std::size_t maxBytes, IoErrorHandler &handler) {
190 if (maxBytes == 0) {
191 return 0;
192 }
193 CheckOpen(handler);
194 if (!Seek(at, handler)) {
195 return 0;
196 }
197 minBytes = std::min(minBytes, maxBytes);
198 std::size_t got{0};
199 while (got < minBytes) {
200 auto chunk{::read(fd_, buffer + got, maxBytes - got)};
201 if (chunk == 0) {
202 handler.SignalEnd();
203 break;
204 } else if (chunk < 0) {
205 auto err{errno};
206 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
207 handler.SignalError(err);
208 break;
209 }
210 } else {
211 position_ += chunk;
212 got += chunk;
213 }
214 }
215 return got;
216 }
217
Write(FileOffset at,const char * buffer,std::size_t bytes,IoErrorHandler & handler)218 std::size_t OpenFile::Write(FileOffset at, const char *buffer,
219 std::size_t bytes, IoErrorHandler &handler) {
220 if (bytes == 0) {
221 return 0;
222 }
223 CheckOpen(handler);
224 if (!Seek(at, handler)) {
225 return 0;
226 }
227 std::size_t put{0};
228 while (put < bytes) {
229 auto chunk{::write(fd_, buffer + put, bytes - put)};
230 if (chunk >= 0) {
231 position_ += chunk;
232 put += chunk;
233 } else {
234 auto err{errno};
235 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
236 handler.SignalError(err);
237 break;
238 }
239 }
240 }
241 if (knownSize_ && position_ > *knownSize_) {
242 knownSize_ = position_;
243 }
244 return put;
245 }
246
openfile_ftruncate(int fd,OpenFile::FileOffset at)247 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
248 #ifdef _WIN32
249 return !::_chsize(fd, at);
250 #else
251 return ::ftruncate(fd, at);
252 #endif
253 }
254
Truncate(FileOffset at,IoErrorHandler & handler)255 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
256 CheckOpen(handler);
257 if (!knownSize_ || *knownSize_ != at) {
258 if (openfile_ftruncate(fd_, at) != 0) {
259 handler.SignalErrno();
260 }
261 knownSize_ = at;
262 }
263 }
264
265 // The operation is performed immediately; the results are saved
266 // to be claimed by a later WAIT statement.
267 // TODO: True asynchronicity
ReadAsynchronously(FileOffset at,char * buffer,std::size_t bytes,IoErrorHandler & handler)268 int OpenFile::ReadAsynchronously(
269 FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
270 CheckOpen(handler);
271 int iostat{0};
272 for (std::size_t got{0}; got < bytes;) {
273 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
274 auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
275 #else
276 auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
277 #endif
278 if (chunk == 0) {
279 iostat = FORTRAN_RUNTIME_IOSTAT_END;
280 break;
281 }
282 if (chunk < 0) {
283 auto err{errno};
284 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
285 iostat = err;
286 break;
287 }
288 } else {
289 at += chunk;
290 got += chunk;
291 }
292 }
293 return PendingResult(handler, iostat);
294 }
295
296 // TODO: True asynchronicity
WriteAsynchronously(FileOffset at,const char * buffer,std::size_t bytes,IoErrorHandler & handler)297 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
298 std::size_t bytes, IoErrorHandler &handler) {
299 CheckOpen(handler);
300 int iostat{0};
301 for (std::size_t put{0}; put < bytes;) {
302 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
303 auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
304 #else
305 auto chunk{
306 Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
307 #endif
308 if (chunk >= 0) {
309 at += chunk;
310 put += chunk;
311 } else {
312 auto err{errno};
313 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
314 iostat = err;
315 break;
316 }
317 }
318 }
319 return PendingResult(handler, iostat);
320 }
321
Wait(int id,IoErrorHandler & handler)322 void OpenFile::Wait(int id, IoErrorHandler &handler) {
323 std::optional<int> ioStat;
324 Pending *prev{nullptr};
325 for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
326 if (p->id == id) {
327 ioStat = p->ioStat;
328 if (prev) {
329 prev->next.reset(p->next.release());
330 } else {
331 pending_.reset(p->next.release());
332 }
333 break;
334 }
335 }
336 if (ioStat) {
337 handler.SignalError(*ioStat);
338 }
339 }
340
WaitAll(IoErrorHandler & handler)341 void OpenFile::WaitAll(IoErrorHandler &handler) {
342 while (true) {
343 int ioStat;
344 if (pending_) {
345 ioStat = pending_->ioStat;
346 pending_.reset(pending_->next.release());
347 } else {
348 return;
349 }
350 handler.SignalError(ioStat);
351 }
352 }
353
CheckOpen(const Terminator & terminator)354 void OpenFile::CheckOpen(const Terminator &terminator) {
355 RUNTIME_CHECK(terminator, fd_ >= 0);
356 }
357
Seek(FileOffset at,IoErrorHandler & handler)358 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
359 if (at == position_) {
360 return true;
361 } else if (RawSeek(at)) {
362 position_ = at;
363 return true;
364 } else {
365 handler.SignalErrno();
366 return false;
367 }
368 }
369
RawSeek(FileOffset at)370 bool OpenFile::RawSeek(FileOffset at) {
371 #ifdef _LARGEFILE64_SOURCE
372 return ::lseek64(fd_, at, SEEK_SET) == at;
373 #else
374 return ::lseek(fd_, at, SEEK_SET) == at;
375 #endif
376 }
377
RawSeekToEnd()378 bool OpenFile::RawSeekToEnd() {
379 #ifdef _LARGEFILE64_SOURCE
380 std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
381 #else
382 std::int64_t at{::lseek(fd_, 0, SEEK_END)};
383 #endif
384 if (at >= 0) {
385 knownSize_ = at;
386 return true;
387 } else {
388 return false;
389 }
390 }
391
PendingResult(const Terminator & terminator,int iostat)392 int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
393 int id{nextId_++};
394 pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
395 return id;
396 }
397
IsATerminal(int fd)398 bool IsATerminal(int fd) { return ::isatty(fd); }
399
400 #ifdef WIN32
401 // Access flags are normally defined in unistd.h, which unavailable under
402 // Windows. Instead, define the flags as documented at
403 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
404 #define F_OK 00
405 #define W_OK 02
406 #define R_OK 04
407 #endif
408
IsExtant(const char * path)409 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
MayRead(const char * path)410 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
MayWrite(const char * path)411 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
MayReadAndWrite(const char * path)412 bool MayReadAndWrite(const char *path) {
413 return ::access(path, R_OK | W_OK) == 0;
414 }
415 } // namespace Fortran::runtime::io
416