| 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 | |