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