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