1 | //===--- A platform independent file data structure -------------*- 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 | #ifndef LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H |
10 | #define LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H |
11 | |
12 | #include "src/__support/CPP/new.h" |
13 | #include "src/__support/error_or.h" |
14 | #include "src/__support/macros/properties/architectures.h" |
15 | #include "src/__support/threads/mutex.h" |
16 | |
17 | #include <stddef.h> |
18 | #include <stdint.h> |
19 | |
20 | namespace LIBC_NAMESPACE { |
21 | |
22 | struct FileIOResult { |
23 | size_t value; |
24 | int error; |
25 | |
26 | constexpr FileIOResult(size_t val) : value(val), error(0) {} |
27 | constexpr FileIOResult(size_t val, int error) : value(val), error(error) {} |
28 | |
29 | constexpr bool has_error() { return error != 0; } |
30 | |
31 | constexpr operator size_t() { return value; } |
32 | }; |
33 | |
34 | // This a generic base class to encapsulate a platform independent file data |
35 | // structure. Platform specific specializations should create a subclass as |
36 | // suitable for their platform. |
37 | class File { |
38 | public: |
39 | static constexpr size_t DEFAULT_BUFFER_SIZE = 1024; |
40 | |
41 | using LockFunc = void(File *); |
42 | using UnlockFunc = void(File *); |
43 | |
44 | using WriteFunc = FileIOResult(File *, const void *, size_t); |
45 | using ReadFunc = FileIOResult(File *, void *, size_t); |
46 | // The SeekFunc is expected to return the current offset of the external |
47 | // file position indicator. |
48 | using SeekFunc = ErrorOr<long>(File *, long, int); |
49 | using CloseFunc = int(File *); |
50 | |
51 | using ModeFlags = uint32_t; |
52 | |
53 | // The three different types of flags below are to be used with '|' operator. |
54 | // Their values correspond to mutually exclusive bits in a 32-bit unsigned |
55 | // integer value. A flag set can include both READ and WRITE if the file |
56 | // is opened in update mode (ie. if the file was opened with a '+' the mode |
57 | // string.) |
58 | enum class OpenMode : ModeFlags { |
59 | READ = 0x1, |
60 | WRITE = 0x2, |
61 | APPEND = 0x4, |
62 | PLUS = 0x8, |
63 | }; |
64 | |
65 | // Denotes a file opened in binary mode (which is specified by including |
66 | // the 'b' character in teh mode string.) |
67 | enum class ContentType : ModeFlags { |
68 | BINARY = 0x10, |
69 | }; |
70 | |
71 | // Denotes a file to be created for writing. |
72 | enum class CreateType : ModeFlags { |
73 | EXCLUSIVE = 0x100, |
74 | }; |
75 | |
76 | private: |
77 | enum class FileOp : uint8_t { NONE, READ, WRITE, SEEK }; |
78 | |
79 | // Platform specific functions which create new file objects should initialize |
80 | // these fields suitably via the constructor. Typically, they should be simple |
81 | // syscall wrappers for the corresponding functionality. |
82 | WriteFunc *platform_write; |
83 | ReadFunc *platform_read; |
84 | SeekFunc *platform_seek; |
85 | CloseFunc *platform_close; |
86 | |
87 | Mutex mutex; |
88 | |
89 | // For files which are readable, we should be able to support one ungetc |
90 | // operation even if |buf| is nullptr. So, in the constructor of File, we |
91 | // set |buf| to point to this buffer character. |
92 | uint8_t ungetc_buf; |
93 | |
94 | uint8_t *buf; // Pointer to the stream buffer for buffered streams |
95 | size_t bufsize; // Size of the buffer pointed to by |buf|. |
96 | |
97 | // Buffering mode to used to buffer. |
98 | int bufmode; |
99 | |
100 | // If own_buf is true, the |buf| is owned by the stream and will be |
101 | // free-ed when close method is called on the stream. |
102 | bool own_buf; |
103 | |
104 | // The mode in which the file was opened. |
105 | ModeFlags mode; |
106 | |
107 | // Current read or write pointer. |
108 | size_t pos; |
109 | |
110 | // Represents the previous operation that was performed. |
111 | FileOp prev_op; |
112 | |
113 | // When the buffer is used as a read buffer, read_limit is the upper limit |
114 | // of the index to which the buffer can be read until. |
115 | size_t read_limit; |
116 | |
117 | bool eof; |
118 | bool err; |
119 | |
120 | // This is a convenience RAII class to lock and unlock file objects. |
121 | class FileLock { |
122 | File *file; |
123 | |
124 | public: |
125 | explicit FileLock(File *f) : file(f) { file->lock(); } |
126 | |
127 | ~FileLock() { file->unlock(); } |
128 | |
129 | FileLock(const FileLock &) = delete; |
130 | FileLock(FileLock &&) = delete; |
131 | }; |
132 | |
133 | protected: |
134 | constexpr bool write_allowed() const { |
135 | return mode & (static_cast<ModeFlags>(OpenMode::WRITE) | |
136 | static_cast<ModeFlags>(OpenMode::APPEND) | |
137 | static_cast<ModeFlags>(OpenMode::PLUS)); |
138 | } |
139 | |
140 | constexpr bool read_allowed() const { |
141 | return mode & (static_cast<ModeFlags>(OpenMode::READ) | |
142 | static_cast<ModeFlags>(OpenMode::PLUS)); |
143 | } |
144 | |
145 | public: |
146 | // We want this constructor to be constexpr so that global file objects |
147 | // like stdout do not require invocation of the constructor which can |
148 | // potentially lead to static initialization order fiasco. Consequently, |
149 | // we will assume that the |buffer| and |buffer_size| argument are |
150 | // meaningful - that is, |buffer| is nullptr if and only if |buffer_size| |
151 | // is zero. This way, we will not have to employ the semantics of |
152 | // the set_buffer method and allocate a buffer. |
153 | constexpr File(WriteFunc *wf, ReadFunc *rf, SeekFunc *sf, CloseFunc *cf, |
154 | uint8_t *buffer, size_t buffer_size, int buffer_mode, |
155 | bool owned, ModeFlags modeflags) |
156 | : platform_write(wf), platform_read(rf), platform_seek(sf), |
157 | platform_close(cf), mutex(false, false, false), ungetc_buf(0), |
158 | buf(buffer), bufsize(buffer_size), bufmode(buffer_mode), own_buf(owned), |
159 | mode(modeflags), pos(0), prev_op(FileOp::NONE), read_limit(0), |
160 | eof(false), err(false) { |
161 | adjust_buf(); |
162 | } |
163 | |
164 | // Buffered write of |len| bytes from |data| without the file lock. |
165 | FileIOResult write_unlocked(const void *data, size_t len); |
166 | |
167 | // Buffered write of |len| bytes from |data| under the file lock. |
168 | FileIOResult write(const void *data, size_t len) { |
169 | FileLock l(this); |
170 | return write_unlocked(data, len); |
171 | } |
172 | |
173 | // Buffered read of |len| bytes into |data| without the file lock. |
174 | FileIOResult read_unlocked(void *data, size_t len); |
175 | |
176 | // Buffered read of |len| bytes into |data| under the file lock. |
177 | FileIOResult read(void *data, size_t len) { |
178 | FileLock l(this); |
179 | return read_unlocked(data, len); |
180 | } |
181 | |
182 | ErrorOr<int> seek(long offset, int whence); |
183 | |
184 | ErrorOr<long> tell(); |
185 | |
186 | // If buffer has data written to it, flush it out. Does nothing if the |
187 | // buffer is currently being used as a read buffer. |
188 | int flush() { |
189 | FileLock lock(this); |
190 | return flush_unlocked(); |
191 | } |
192 | |
193 | int flush_unlocked(); |
194 | |
195 | // Returns EOF on error and keeps the file unchanged. |
196 | int ungetc_unlocked(int c); |
197 | |
198 | int ungetc(int c) { |
199 | FileLock lock(this); |
200 | return ungetc_unlocked(c); |
201 | } |
202 | |
203 | // Does the following: |
204 | // 1. If in write mode, Write out any data present in the buffer. |
205 | // 2. Call platform_close. |
206 | // platform_close is expected to cleanup the complete file object. |
207 | int close() { |
208 | { |
209 | FileLock lock(this); |
210 | if (prev_op == FileOp::WRITE && pos > 0) { |
211 | auto buf_result = platform_write(this, buf, pos); |
212 | if (buf_result.has_error() || buf_result.value < pos) { |
213 | err = true; |
214 | return buf_result.error; |
215 | } |
216 | } |
217 | } |
218 | |
219 | // If we own the buffer, delete it before calling the platform close |
220 | // implementation. The platform close should not need to access the buffer |
221 | // and we need to clean it up before the entire structure is removed. |
222 | if (own_buf) |
223 | delete buf; |
224 | |
225 | // Platform close is expected to cleanup the file data structure which |
226 | // includes the file mutex. Hence, we call platform_close after releasing |
227 | // the file lock. Another thread doing file operations while a thread is |
228 | // closing the file is undefined behavior as per POSIX. |
229 | return platform_close(this); |
230 | } |
231 | |
232 | // Sets the internal buffer to |buffer| with buffering mode |mode|. |
233 | // |size| is the size of |buffer|. If |size| is non-zero, but |buffer| |
234 | // is nullptr, then a buffer owned by this file will be allocated. |
235 | // Else, |buffer| will not be owned by this file. |
236 | // |
237 | // Will return zero on success, or an error value on failure. Will fail |
238 | // if: |
239 | // 1. |buffer| is not a nullptr but |size| is zero. |
240 | // 2. |buffer_mode| is not one of _IOLBF, IOFBF or _IONBF. |
241 | // 3. If an allocation was required but the allocation failed. |
242 | // For cases 1 and 2, the error returned in EINVAL. For case 3, error returned |
243 | // is ENOMEM. |
244 | int set_buffer(void *buffer, size_t size, int buffer_mode); |
245 | |
246 | void lock() { mutex.lock(); } |
247 | void unlock() { mutex.unlock(); } |
248 | |
249 | bool error_unlocked() const { return err; } |
250 | |
251 | bool error() { |
252 | FileLock l(this); |
253 | return error_unlocked(); |
254 | } |
255 | |
256 | void clearerr_unlocked() { err = false; } |
257 | |
258 | void clearerr() { |
259 | FileLock l(this); |
260 | clearerr_unlocked(); |
261 | } |
262 | |
263 | bool iseof_unlocked() { return eof; } |
264 | |
265 | bool iseof() { |
266 | FileLock l(this); |
267 | return iseof_unlocked(); |
268 | } |
269 | |
270 | // Returns an bit map of flags corresponding to enumerations of |
271 | // OpenMode, ContentType and CreateType. |
272 | static ModeFlags mode_flags(const char *mode); |
273 | |
274 | private: |
275 | FileIOResult write_unlocked_lbf(const uint8_t *data, size_t len); |
276 | FileIOResult write_unlocked_fbf(const uint8_t *data, size_t len); |
277 | FileIOResult write_unlocked_nbf(const uint8_t *data, size_t len); |
278 | |
279 | constexpr void adjust_buf() { |
280 | if (read_allowed() && (buf == nullptr || bufsize == 0)) { |
281 | // We should allow atleast one ungetc operation. |
282 | // This might give an impression that a buffer will be used even when |
283 | // the user does not want a buffer. But, that will not be the case. |
284 | // For reading, the buffering does not come into play. For writing, let |
285 | // us take up the three different kinds of buffering separately: |
286 | // 1. If user wants _IOFBF but gives a zero buffer, buffering still |
287 | // happens in the OS layer until the user flushes. So, from the user's |
288 | // point of view, this single byte buffer does not affect their |
289 | // experience. |
290 | // 2. If user wants _IOLBF but gives a zero buffer, the reasoning is |
291 | // very similar to the _IOFBF case. |
292 | // 3. If user wants _IONBF, then the buffer is ignored for writing. |
293 | // So, all of the above cases, having a single ungetc buffer does not |
294 | // affect the behavior experienced by the user. |
295 | buf = &ungetc_buf; |
296 | bufsize = 1; |
297 | own_buf = false; // We shouldn't call free on |buf| when closing the file. |
298 | } |
299 | } |
300 | }; |
301 | |
302 | // The implementaiton of this function is provided by the platform_file |
303 | // library. |
304 | ErrorOr<File *> openfile(const char *path, const char *mode); |
305 | |
306 | // The platform_file library should implement it if it relevant for that |
307 | // platform. |
308 | int get_fileno(File *f); |
309 | |
310 | extern File *stdin; |
311 | extern File *stdout; |
312 | extern File *stderr; |
313 | |
314 | } // namespace LIBC_NAMESPACE |
315 | |
316 | #endif // LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H |
317 | |