236
|
1 //===-- runtime/file.cpp --------------------------------------------------===//
|
173
|
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"
|
236
|
10 #include "flang/Runtime/magic-numbers.h"
|
|
11 #include "flang/Runtime/memory.h"
|
221
|
12 #include <algorithm>
|
173
|
13 #include <cerrno>
|
|
14 #include <cstring>
|
|
15 #include <fcntl.h>
|
|
16 #include <stdlib.h>
|
221
|
17 #include <sys/stat.h>
|
173
|
18 #ifdef _WIN32
|
221
|
19 #define NOMINMAX
|
173
|
20 #include <io.h>
|
|
21 #include <windows.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 }
|
236
|
48 int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,
|
|
49 _S_IREAD | _S_IWRITE)};
|
173
|
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
|
221
|
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 }
|
236
|
69 CloseFd(handler);
|
221
|
70 if (status == OpenStatus::Scratch) {
|
173
|
71 if (path_.get()) {
|
|
72 handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
|
|
73 path_.reset();
|
|
74 }
|
221
|
75 if (!action) {
|
|
76 action = Action::ReadWrite;
|
|
77 }
|
173
|
78 fd_ = openfile_mkstemp(handler);
|
221
|
79 } else {
|
|
80 if (!path_.get()) {
|
|
81 handler.SignalError("FILE= is required");
|
173
|
82 return;
|
|
83 }
|
221
|
84 int flags{0};
|
236
|
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
|
221
|
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) {
|
236
|
100 // Try to open read/write, back off to read-only or even write-only
|
|
101 // on failure
|
221
|
102 fd_ = ::open(path_.get(), flags | O_RDWR, 0600);
|
|
103 if (fd_ >= 0) {
|
|
104 action = Action::ReadWrite;
|
|
105 } else {
|
236
|
106 fd_ = ::open(path_.get(), flags | O_RDONLY, 0600);
|
|
107 if (fd_ >= 0) {
|
|
108 action = Action::Read;
|
|
109 } else {
|
|
110 action = Action::Write;
|
|
111 }
|
221
|
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 }
|
173
|
130 }
|
|
131 }
|
221
|
132 RUNTIME_CHECK(handler, action.has_value());
|
173
|
133 pending_.reset();
|
|
134 if (position == Position::Append && !RawSeekToEnd()) {
|
236
|
135 handler.SignalError(IostatOpenBadAppend);
|
173
|
136 }
|
236
|
137 isTerminal_ = IsATerminal(fd_) == 1;
|
221
|
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 }
|
236
|
155 openPosition_ = position; // for INQUIRE(POSITION=)
|
173
|
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();
|
236
|
166 isTerminal_ = IsATerminal(fd_) == 1;
|
221
|
167 mayRead_ = fd == 0;
|
|
168 mayWrite_ = fd != 0;
|
|
169 mayPosition_ = false;
|
236
|
170 #ifdef _WIN32
|
|
171 isWindowsTextFile_ = true;
|
|
172 #endif
|
173
|
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();
|
236
|
188 CloseFd(handler);
|
173
|
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 }
|
221
|
200 minBytes = std::min(minBytes, maxBytes);
|
173
|
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;
|
221
|
206 } else if (chunk < 0) {
|
173
|
207 auto err{errno};
|
|
208 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
|
209 handler.SignalError(err);
|
|
210 break;
|
|
211 }
|
|
212 } else {
|
236
|
213 SetPosition(position_ + chunk);
|
173
|
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) {
|
236
|
233 SetPosition(position_ + chunk);
|
173
|
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
|
236
|
251 return ::_chsize(fd, at);
|
173
|
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 std::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
|
236
|
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
|
173
|
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)) {
|
236
|
378 SetPosition(at);
|
173
|
379 return true;
|
|
380 } else {
|
236
|
381 handler.SignalError(IostatCannotReposition);
|
173
|
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_++};
|
221
|
410 pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
|
173
|
411 return id;
|
|
412 }
|
221
|
413
|
236
|
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
|
221
|
427 bool IsATerminal(int fd) { return ::isatty(fd); }
|
|
428
|
236
|
429 #if defined(_WIN32) && !defined(F_OK)
|
221
|
430 // Access flags are normally defined in unistd.h, which unavailable under
|
|
431 // Windows. Instead, define the flags as documented at
|
|
432 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
|
236
|
433 // On Mingw, io.h does define these same constants - so check whether they
|
|
434 // already are defined before defining these.
|
221
|
435 #define F_OK 00
|
|
436 #define W_OK 02
|
|
437 #define R_OK 04
|
|
438 #endif
|
|
439
|
|
440 bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
|
|
441 bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
|
|
442 bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
|
|
443 bool MayReadAndWrite(const char *path) {
|
|
444 return ::access(path, R_OK | W_OK) == 0;
|
|
445 }
|
236
|
446
|
|
447 std::int64_t SizeInBytes(const char *path) {
|
|
448 #ifndef _WIN32
|
|
449 struct stat buf;
|
|
450 if (::stat(path, &buf) == 0) {
|
|
451 return buf.st_size;
|
|
452 }
|
|
453 #else // TODO: _WIN32
|
|
454 #endif
|
|
455 // No Fortran compiler signals an error
|
|
456 return -1;
|
|
457 }
|
|
458
|
173
|
459 } // namespace Fortran::runtime::io
|