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(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 | |
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 (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 | |
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_ = IsATerminal(fd_) == 1; |
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(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 | |
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: 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 | |
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: fd, length: 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: 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: 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 |
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: 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 | |
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: fd_, offset: 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: 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 | |
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: 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: 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(name: path, F_OK) == 0; } |
442 | bool MayRead(const char *path) { return ::access(name: path, R_OK) == 0; } |
443 | bool MayWrite(const char *path) { return ::access(name: path, W_OK) == 0; } |
444 | bool MayReadAndWrite(const char *path) { |
445 | return ::access(name: 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(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) |
460 | bool IsATerminal(int fd) { |
461 | Terminator{__FILE__, __LINE__}.Crash("%s: unsupported" , RT_PRETTY_FUNCTION); |
462 | } |
463 | bool IsExtant(const char *path) { |
464 | Terminator{__FILE__, __LINE__}.Crash("%s: unsupported" , RT_PRETTY_FUNCTION); |
465 | } |
466 | bool MayRead(const char *path) { |
467 | Terminator{__FILE__, __LINE__}.Crash("%s: unsupported" , RT_PRETTY_FUNCTION); |
468 | } |
469 | bool MayWrite(const char *path) { |
470 | Terminator{__FILE__, __LINE__}.Crash("%s: unsupported" , RT_PRETTY_FUNCTION); |
471 | } |
472 | bool MayReadAndWrite(const char *path) { |
473 | Terminator{__FILE__, __LINE__}.Crash("%s: unsupported" , RT_PRETTY_FUNCTION); |
474 | } |
475 | 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 | |