173
|
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 <cerrno>
|
|
13 #include <cstring>
|
|
14 #include <fcntl.h>
|
|
15 #include <stdlib.h>
|
|
16 #ifdef _WIN32
|
|
17 #include <io.h>
|
|
18 #include <windows.h>
|
|
19 #else
|
|
20 #include <unistd.h>
|
|
21 #endif
|
|
22
|
|
23 namespace Fortran::runtime::io {
|
|
24
|
|
25 void OpenFile::set_path(OwningPtr<char> &&path, std::size_t bytes) {
|
|
26 path_ = std::move(path);
|
|
27 pathLength_ = bytes;
|
|
28 }
|
|
29
|
|
30 static int openfile_mkstemp(IoErrorHandler &handler) {
|
|
31 #ifdef _WIN32
|
|
32 const unsigned int uUnique{0};
|
|
33 // GetTempFileNameA needs a directory name < MAX_PATH-14 characters in length.
|
|
34 // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea
|
|
35 char tempDirName[MAX_PATH - 14];
|
|
36 char tempFileName[MAX_PATH];
|
|
37 unsigned long nBufferLength{sizeof(tempDirName)};
|
|
38 nBufferLength = ::GetTempPathA(nBufferLength, tempDirName);
|
|
39 if (nBufferLength > sizeof(tempDirName) || nBufferLength == 0) {
|
|
40 return -1;
|
|
41 }
|
|
42 if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) {
|
|
43 return -1;
|
|
44 }
|
|
45 int fd{::_open(tempFileName, _O_CREAT | _O_TEMPORARY, _S_IREAD | _S_IWRITE)};
|
|
46 #else
|
|
47 char path[]{"/tmp/Fortran-Scratch-XXXXXX"};
|
|
48 int fd{::mkstemp(path)};
|
|
49 #endif
|
|
50 if (fd < 0) {
|
|
51 handler.SignalErrno();
|
|
52 }
|
|
53 #ifndef _WIN32
|
|
54 ::unlink(path);
|
|
55 #endif
|
|
56 return fd;
|
|
57 }
|
|
58
|
|
59 void OpenFile::Open(
|
|
60 OpenStatus status, Position position, IoErrorHandler &handler) {
|
|
61 int flags{mayRead_ ? mayWrite_ ? O_RDWR : O_RDONLY : O_WRONLY};
|
|
62 switch (status) {
|
|
63 case OpenStatus::Old:
|
|
64 if (fd_ >= 0) {
|
|
65 return;
|
|
66 }
|
|
67 break;
|
|
68 case OpenStatus::New:
|
|
69 flags |= O_CREAT | O_EXCL;
|
|
70 break;
|
|
71 case OpenStatus::Scratch:
|
|
72 if (path_.get()) {
|
|
73 handler.SignalError("FILE= must not appear with STATUS='SCRATCH'");
|
|
74 path_.reset();
|
|
75 }
|
|
76 fd_ = openfile_mkstemp(handler);
|
|
77 return;
|
|
78 case OpenStatus::Replace:
|
|
79 flags |= O_CREAT | O_TRUNC;
|
|
80 break;
|
|
81 case OpenStatus::Unknown:
|
|
82 if (fd_ >= 0) {
|
|
83 return;
|
|
84 }
|
|
85 flags |= O_CREAT;
|
|
86 break;
|
|
87 }
|
|
88 // If we reach this point, we're opening a new file.
|
|
89 // TODO: Fortran shouldn't create a new file until the first WRITE.
|
|
90 if (fd_ >= 0) {
|
|
91 if (fd_ <= 2) {
|
|
92 // don't actually close a standard file descriptor, we might need it
|
|
93 } else if (::close(fd_) != 0) {
|
|
94 handler.SignalErrno();
|
|
95 }
|
|
96 }
|
|
97 if (!path_.get()) {
|
|
98 handler.SignalError(
|
|
99 "FILE= is required unless STATUS='OLD' and unit is connected");
|
|
100 return;
|
|
101 }
|
|
102 fd_ = ::open(path_.get(), flags, 0600);
|
|
103 if (fd_ < 0) {
|
|
104 handler.SignalErrno();
|
|
105 }
|
|
106 pending_.reset();
|
|
107 knownSize_.reset();
|
|
108 if (position == Position::Append && !RawSeekToEnd()) {
|
|
109 handler.SignalErrno();
|
|
110 }
|
|
111 isTerminal_ = ::isatty(fd_) == 1;
|
|
112 }
|
|
113
|
|
114 void OpenFile::Predefine(int fd) {
|
|
115 fd_ = fd;
|
|
116 path_.reset();
|
|
117 pathLength_ = 0;
|
|
118 position_ = 0;
|
|
119 knownSize_.reset();
|
|
120 nextId_ = 0;
|
|
121 pending_.reset();
|
|
122 }
|
|
123
|
|
124 void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
|
|
125 CheckOpen(handler);
|
|
126 pending_.reset();
|
|
127 knownSize_.reset();
|
|
128 switch (status) {
|
|
129 case CloseStatus::Keep:
|
|
130 break;
|
|
131 case CloseStatus::Delete:
|
|
132 if (path_.get()) {
|
|
133 ::unlink(path_.get());
|
|
134 }
|
|
135 break;
|
|
136 }
|
|
137 path_.reset();
|
|
138 if (fd_ >= 0) {
|
|
139 if (::close(fd_) != 0) {
|
|
140 handler.SignalErrno();
|
|
141 }
|
|
142 fd_ = -1;
|
|
143 }
|
|
144 }
|
|
145
|
|
146 std::size_t OpenFile::Read(FileOffset at, char *buffer, std::size_t minBytes,
|
|
147 std::size_t maxBytes, IoErrorHandler &handler) {
|
|
148 if (maxBytes == 0) {
|
|
149 return 0;
|
|
150 }
|
|
151 CheckOpen(handler);
|
|
152 if (!Seek(at, handler)) {
|
|
153 return 0;
|
|
154 }
|
|
155 if (maxBytes < minBytes) {
|
|
156 minBytes = maxBytes;
|
|
157 }
|
|
158 std::size_t got{0};
|
|
159 while (got < minBytes) {
|
|
160 auto chunk{::read(fd_, buffer + got, maxBytes - got)};
|
|
161 if (chunk == 0) {
|
|
162 handler.SignalEnd();
|
|
163 break;
|
|
164 }
|
|
165 if (chunk < 0) {
|
|
166 auto err{errno};
|
|
167 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
|
168 handler.SignalError(err);
|
|
169 break;
|
|
170 }
|
|
171 } else {
|
|
172 position_ += chunk;
|
|
173 got += chunk;
|
|
174 }
|
|
175 }
|
|
176 return got;
|
|
177 }
|
|
178
|
|
179 std::size_t OpenFile::Write(FileOffset at, const char *buffer,
|
|
180 std::size_t bytes, IoErrorHandler &handler) {
|
|
181 if (bytes == 0) {
|
|
182 return 0;
|
|
183 }
|
|
184 CheckOpen(handler);
|
|
185 if (!Seek(at, handler)) {
|
|
186 return 0;
|
|
187 }
|
|
188 std::size_t put{0};
|
|
189 while (put < bytes) {
|
|
190 auto chunk{::write(fd_, buffer + put, bytes - put)};
|
|
191 if (chunk >= 0) {
|
|
192 position_ += chunk;
|
|
193 put += chunk;
|
|
194 } else {
|
|
195 auto err{errno};
|
|
196 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
|
197 handler.SignalError(err);
|
|
198 break;
|
|
199 }
|
|
200 }
|
|
201 }
|
|
202 if (knownSize_ && position_ > *knownSize_) {
|
|
203 knownSize_ = position_;
|
|
204 }
|
|
205 return put;
|
|
206 }
|
|
207
|
|
208 inline static int openfile_ftruncate(int fd, OpenFile::FileOffset at) {
|
|
209 #ifdef _WIN32
|
|
210 return !::_chsize(fd, at);
|
|
211 #else
|
|
212 return ::ftruncate(fd, at);
|
|
213 #endif
|
|
214 }
|
|
215
|
|
216 void OpenFile::Truncate(FileOffset at, IoErrorHandler &handler) {
|
|
217 CheckOpen(handler);
|
|
218 if (!knownSize_ || *knownSize_ != at) {
|
|
219 if (openfile_ftruncate(fd_, at) != 0) {
|
|
220 handler.SignalErrno();
|
|
221 }
|
|
222 knownSize_ = at;
|
|
223 }
|
|
224 }
|
|
225
|
|
226 // The operation is performed immediately; the results are saved
|
|
227 // to be claimed by a later WAIT statement.
|
|
228 // TODO: True asynchronicity
|
|
229 int OpenFile::ReadAsynchronously(
|
|
230 FileOffset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) {
|
|
231 CheckOpen(handler);
|
|
232 int iostat{0};
|
|
233 for (std::size_t got{0}; got < bytes;) {
|
|
234 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
|
|
235 auto chunk{::pread(fd_, buffer + got, bytes - got, at)};
|
|
236 #else
|
|
237 auto chunk{Seek(at, handler) ? ::read(fd_, buffer + got, bytes - got) : -1};
|
|
238 #endif
|
|
239 if (chunk == 0) {
|
|
240 iostat = FORTRAN_RUNTIME_IOSTAT_END;
|
|
241 break;
|
|
242 }
|
|
243 if (chunk < 0) {
|
|
244 auto err{errno};
|
|
245 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
|
246 iostat = err;
|
|
247 break;
|
|
248 }
|
|
249 } else {
|
|
250 at += chunk;
|
|
251 got += chunk;
|
|
252 }
|
|
253 }
|
|
254 return PendingResult(handler, iostat);
|
|
255 }
|
|
256
|
|
257 // TODO: True asynchronicity
|
|
258 int OpenFile::WriteAsynchronously(FileOffset at, const char *buffer,
|
|
259 std::size_t bytes, IoErrorHandler &handler) {
|
|
260 CheckOpen(handler);
|
|
261 int iostat{0};
|
|
262 for (std::size_t put{0}; put < bytes;) {
|
|
263 #if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
|
|
264 auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)};
|
|
265 #else
|
|
266 auto chunk{
|
|
267 Seek(at, handler) ? ::write(fd_, buffer + put, bytes - put) : -1};
|
|
268 #endif
|
|
269 if (chunk >= 0) {
|
|
270 at += chunk;
|
|
271 put += chunk;
|
|
272 } else {
|
|
273 auto err{errno};
|
|
274 if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
|
275 iostat = err;
|
|
276 break;
|
|
277 }
|
|
278 }
|
|
279 }
|
|
280 return PendingResult(handler, iostat);
|
|
281 }
|
|
282
|
|
283 void OpenFile::Wait(int id, IoErrorHandler &handler) {
|
|
284 std::optional<int> ioStat;
|
|
285 Pending *prev{nullptr};
|
|
286 for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) {
|
|
287 if (p->id == id) {
|
|
288 ioStat = p->ioStat;
|
|
289 if (prev) {
|
|
290 prev->next.reset(p->next.release());
|
|
291 } else {
|
|
292 pending_.reset(p->next.release());
|
|
293 }
|
|
294 break;
|
|
295 }
|
|
296 }
|
|
297 if (ioStat) {
|
|
298 handler.SignalError(*ioStat);
|
|
299 }
|
|
300 }
|
|
301
|
|
302 void OpenFile::WaitAll(IoErrorHandler &handler) {
|
|
303 while (true) {
|
|
304 int ioStat;
|
|
305 if (pending_) {
|
|
306 ioStat = pending_->ioStat;
|
|
307 pending_.reset(pending_->next.release());
|
|
308 } else {
|
|
309 return;
|
|
310 }
|
|
311 handler.SignalError(ioStat);
|
|
312 }
|
|
313 }
|
|
314
|
|
315 void OpenFile::CheckOpen(const Terminator &terminator) {
|
|
316 RUNTIME_CHECK(terminator, fd_ >= 0);
|
|
317 }
|
|
318
|
|
319 bool OpenFile::Seek(FileOffset at, IoErrorHandler &handler) {
|
|
320 if (at == position_) {
|
|
321 return true;
|
|
322 } else if (RawSeek(at)) {
|
|
323 position_ = at;
|
|
324 return true;
|
|
325 } else {
|
|
326 handler.SignalErrno();
|
|
327 return false;
|
|
328 }
|
|
329 }
|
|
330
|
|
331 bool OpenFile::RawSeek(FileOffset at) {
|
|
332 #ifdef _LARGEFILE64_SOURCE
|
|
333 return ::lseek64(fd_, at, SEEK_SET) == at;
|
|
334 #else
|
|
335 return ::lseek(fd_, at, SEEK_SET) == at;
|
|
336 #endif
|
|
337 }
|
|
338
|
|
339 bool OpenFile::RawSeekToEnd() {
|
|
340 #ifdef _LARGEFILE64_SOURCE
|
|
341 std::int64_t at{::lseek64(fd_, 0, SEEK_END)};
|
|
342 #else
|
|
343 std::int64_t at{::lseek(fd_, 0, SEEK_END)};
|
|
344 #endif
|
|
345 if (at >= 0) {
|
|
346 knownSize_ = at;
|
|
347 return true;
|
|
348 } else {
|
|
349 return false;
|
|
350 }
|
|
351 }
|
|
352
|
|
353 int OpenFile::PendingResult(const Terminator &terminator, int iostat) {
|
|
354 int id{nextId_++};
|
|
355 pending_.reset(&New<Pending>{}(terminator, id, iostat, std::move(pending_)));
|
|
356 return id;
|
|
357 }
|
|
358 } // namespace Fortran::runtime::io
|