llvm-project
480 строк · 12.4 Кб
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) {
29path_ = std::move(path);
30pathLength_ = bytes;
31}
32
33static int openfile_mkstemp(IoErrorHandler &handler) {
34#ifdef _WIN32
35const 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
38char tempDirName[MAX_PATH - 14];
39char tempFileName[MAX_PATH];
40unsigned long nBufferLength{sizeof(tempDirName)};
41nBufferLength = ::GetTempPathA(nBufferLength, tempDirName);
42if (nBufferLength > sizeof(tempDirName) || nBufferLength == 0) {
43return -1;
44}
45if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) {
46return -1;
47}
48int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR,
49_S_IREAD | _S_IWRITE)};
50#else
51char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
52int fd{::mkstemp(path)};
53#endif
54if (fd < 0) {
55handler.SignalErrno();
56}
57#ifndef _WIN32
58::unlink(path);
59#endif
60return fd;
61}
62
63void OpenFile::Open(OpenStatus status, Fortran::common::optional<Action> action,
64Position position, IoErrorHandler &handler) {
65if (fd_ >= 0 &&
66(status == OpenStatus::Old || status == OpenStatus::Unknown)) {
67return;
68}
69CloseFd(handler);
70if (status == OpenStatus::Scratch) {
71if (path_.get()) {
72handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
73path_.reset();
74}
75if (!action) {
76action = Action::ReadWrite;
77}
78fd_ = openfile_mkstemp(handler);
79} else {
80if (!path_.get()) {
81handler.SignalError("FILE= is required");
82return;
83}
84int 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.
89flags |= O_BINARY;
90#endif
91if (status != OpenStatus::Old) {
92flags |= O_CREAT;
93}
94if (status == OpenStatus::New) {
95flags |= O_EXCL;
96} else if (status == OpenStatus::Replace) {
97flags |= O_TRUNC;
98}
99if (!action) {
100// Try to open read/write, back off to read-only or even write-only
101// on failure
102fd_ = ::open(path_.get(), flags | O_RDWR, 0600);
103if (fd_ >= 0) {
104action = Action::ReadWrite;
105} else {
106fd_ = ::open(path_.get(), flags | O_RDONLY, 0600);
107if (fd_ >= 0) {
108action = Action::Read;
109} else {
110action = Action::Write;
111}
112}
113}
114if (fd_ < 0) {
115switch (*action) {
116case Action::Read:
117flags |= O_RDONLY;
118break;
119case Action::Write:
120flags |= O_WRONLY;
121break;
122case Action::ReadWrite:
123flags |= O_RDWR;
124break;
125}
126fd_ = ::open(path_.get(), flags, 0600);
127if (fd_ < 0) {
128handler.SignalErrno();
129}
130}
131}
132RUNTIME_CHECK(handler, action.has_value());
133pending_.reset();
134if (fd_ >= 0 && position == Position::Append && !RawSeekToEnd()) {
135handler.SignalError(IostatOpenBadAppend);
136}
137isTerminal_ = fd_ >= 0 && IsATerminal(fd_) == 1;
138mayRead_ = *action != Action::Write;
139mayWrite_ = *action != Action::Read;
140if (status == OpenStatus::Old || status == OpenStatus::Unknown) {
141knownSize_.reset();
142#ifndef _WIN32
143struct stat buf;
144if (fd_ >= 0 && ::fstat(fd_, &buf) == 0) {
145mayPosition_ = S_ISREG(buf.st_mode);
146knownSize_ = buf.st_size;
147}
148#else // TODO: _WIN32
149mayPosition_ = true;
150#endif
151} else {
152knownSize_ = 0;
153mayPosition_ = true;
154}
155openPosition_ = position; // for INQUIRE(POSITION=)
156}
157
158void OpenFile::Predefine(int fd) {
159fd_ = fd;
160path_.reset();
161pathLength_ = 0;
162position_ = 0;
163knownSize_.reset();
164nextId_ = 0;
165pending_.reset();
166isTerminal_ = IsATerminal(fd_) == 1;
167mayRead_ = fd == 0;
168mayWrite_ = fd != 0;
169mayPosition_ = false;
170#ifdef _WIN32
171isWindowsTextFile_ = true;
172#endif
173}
174
175void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
176pending_.reset();
177knownSize_.reset();
178switch (status) {
179case CloseStatus::Keep:
180break;
181case CloseStatus::Delete:
182if (path_.get()) {
183::unlink(path_.get());
184}
185break;
186}
187path_.reset();
188CloseFd(handler);
189}
190
191std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
192std::size_t maxBytes, IoErrorHandler &handler) {
193if (maxBytes == 0) {
194return 0;
195}
196CheckOpen(handler);
197if (!Seek(at, handler)) {
198return 0;
199}
200minBytes = std::min(minBytes, maxBytes);
201std::size_t got{0};
202while (got < minBytes) {
203auto chunk{::read(fd_, buffer + got, maxBytes - got)};
204if (chunk == 0) {
205break;
206} else if (chunk < 0) {
207auto err{errno};
208if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
209handler.SignalError(err);
210break;
211}
212} else {
213SetPosition(position_ + chunk);
214got += chunk;
215}
216}
217return got;
218}
219
220std::size_t OpenFile::Write(FileOffset at, const char *buffer,
221std::size_t bytes, IoErrorHandler &handler) {
222if (bytes == 0) {
223return 0;
224}
225CheckOpen(handler);
226if (!Seek(at, handler)) {
227return 0;
228}
229std::size_t put{0};
230while (put < bytes) {
231auto chunk{::write(fd_, buffer + put, bytes - put)};
232if (chunk >= 0) {
233SetPosition(position_ + chunk);
234put += chunk;
235} else {
236auto err{errno};
237if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
238handler.SignalError(err);
239break;
240}
241}
242}
243if (knownSize_ && position_ > *knownSize_) {
244knownSize_ = position_;
245}
246return put;
247}
248
249inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
250#ifdef _WIN32
251return ::_chsize(fd, at);
252#else
253return ::ftruncate(fd, at);
254#endif
255}
256
257void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
258CheckOpen(handler);
259if (!knownSize_ || *knownSize_ != at) {
260if (openfile_ftruncate(fd_, at) != 0) {
261handler.SignalErrno();
262}
263knownSize_ = 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(
271FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
272CheckOpen(handler);
273int iostat{0};
274for (std::size_t got{0}; got < bytes;) {
275#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
276auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
277#else
278auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
279#endif
280if (chunk == 0) {
281iostat = FORTRAN_RUNTIME_IOSTAT_END;
282break;
283}
284if (chunk < 0) {
285auto err{errno};
286if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
287iostat = err;
288break;
289}
290} else {
291at += chunk;
292got += chunk;
293}
294}
295return PendingResult(handler, iostat);
296}
297
298// TODO: True asynchronicity
299int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
300std::size_t bytes, IoErrorHandler &handler) {
301CheckOpen(handler);
302int iostat{0};
303for (std::size_t put{0}; put < bytes;) {
304#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
305auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
306#else
307auto chunk{
308Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
309#endif
310if (chunk >= 0) {
311at += chunk;
312put += chunk;
313} else {
314auto err{errno};
315if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
316iostat = err;
317break;
318}
319}
320}
321return PendingResult(handler, iostat);
322}
323
324void OpenFile::Wait(int id, IoErrorHandler &handler) {
325Fortran::common::optional<int> ioStat;
326Pending *prev{nullptr};
327for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
328if (p->id == id) {
329ioStat = p->ioStat;
330if (prev) {
331prev->next.reset(p->next.release());
332} else {
333pending_.reset(p->next.release());
334}
335break;
336}
337}
338if (ioStat) {
339handler.SignalError(*ioStat);
340}
341}
342
343void OpenFile::WaitAll(IoErrorHandler &handler) {
344while (true) {
345int ioStat;
346if (pending_) {
347ioStat = pending_->ioStat;
348pending_.reset(pending_->next.release());
349} else {
350return;
351}
352handler.SignalError(ioStat);
353}
354}
355
356Position OpenFile::InquirePosition() const {
357if (openPosition_) { // from OPEN statement
358return *openPosition_;
359} else { // unit has been repositioned since opening
360if (position_ == knownSize_.value_or(position_ + 1)) {
361return Position::Append;
362} else if (position_ == 0 && mayPosition_) {
363return Position::Rewind;
364} else {
365return Position::AsIs; // processor-dependent & no common behavior
366}
367}
368}
369
370void OpenFile::CheckOpen(const Terminator &terminator) {
371RUNTIME_CHECK(terminator, fd_ >= 0);
372}
373
374bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
375if (at == position_) {
376return true;
377} else if (RawSeek(at)) {
378SetPosition(at);
379return true;
380} else {
381handler.SignalError(IostatCannotReposition);
382return false;
383}
384}
385
386bool OpenFile::RawSeek(FileOffset at) {
387#ifdef _LARGEFILE64_SOURCE
388return ::lseek64(fd_, at, SEEK_SET) == at;
389#else
390return ::lseek(fd_, at, SEEK_SET) == at;
391#endif
392}
393
394bool OpenFile::RawSeekToEnd() {
395#ifdef _LARGEFILE64_SOURCE
396std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
397#else
398std::int64_t at{::lseek(fd_, 0, SEEK_END)};
399#endif
400if (at >= 0) {
401knownSize_ = at;
402return true;
403} else {
404return false;
405}
406}
407
408int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
409int id{nextId_++};
410pending_ = New<Pending>{terminator}(id, iostat, std::move(pending_));
411return id;
412}
413
414void OpenFile::CloseFd(IoErrorHandler &handler) {
415if (fd_ >= 0) {
416if (fd_ <= 2) {
417// don't actually close a standard file descriptor, we might need it
418} else {
419if (::close(fd_) != 0) {
420handler.SignalErrno();
421}
422}
423fd_ = -1;
424}
425}
426
427#if !defined(RT_DEVICE_COMPILATION)
428bool 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
441bool IsExtant(const char *path) { return ::access(path, F_OK) == 0; }
442bool MayRead(const char *path) { return ::access(path, R_OK) == 0; }
443bool MayWrite(const char *path) { return ::access(path, W_OK) == 0; }
444bool MayReadAndWrite(const char *path) {
445return ::access(path, R_OK | W_OK) == 0;
446}
447
448std::int64_t SizeInBytes(const char *path) {
449#ifndef _WIN32
450struct stat buf;
451if (::stat(path, &buf) == 0) {
452return buf.st_size;
453}
454#else // TODO: _WIN32
455#endif
456// No Fortran compiler signals an error
457return -1;
458}
459#else // defined(RT_DEVICE_COMPILATION)
460RT_API_ATTRS bool IsATerminal(int fd) {
461Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
462}
463RT_API_ATTRS bool IsExtant(const char *path) {
464Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
465}
466RT_API_ATTRS bool MayRead(const char *path) {
467Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
468}
469RT_API_ATTRS bool MayWrite(const char *path) {
470Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
471}
472RT_API_ATTRS bool MayReadAndWrite(const char *path) {
473Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
474}
475RT_API_ATTRS std::int64_t SizeInBytes(const char *path) {
476Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
477}
478#endif // defined(RT_DEVICE_COMPILATION)
479
480} // namespace Fortran::runtime::io
481