325 lines · c
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-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H10#define LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H11 12#include "hdr/stdint_proxy.h"13#include "hdr/stdio_macros.h"14#include "hdr/types/off_t.h"15#include "src/__support/CPP/new.h"16#include "src/__support/error_or.h"17#include "src/__support/macros/config.h"18#include "src/__support/macros/properties/architectures.h"19#include "src/__support/threads/mutex.h"20 21#include <stddef.h>22 23namespace LIBC_NAMESPACE_DECL {24 25struct 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 data38// structure. Platform specific specializations should create a subclass as39// suitable for their platform.40class File {41public: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 external50 // 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 unsigned58 // integer value. A flag set can include both READ and WRITE if the file59 // is opened in update mode (ie. if the file was opened with a '+' the mode60 // 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 including69 // 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 79private:80 enum class FileOp : uint8_t { NONE, READ, WRITE, SEEK };81 82 // Platform specific functions which create new file objects should initialize83 // these fields suitably via the constructor. Typically, they should be simple84 // 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 ungetc93 // operation even if |buf| is nullptr. So, in the constructor of File, we94 // 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 streams98 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 be104 // 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 limit117 // 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 136protected: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 148public:149 // We want this constructor to be constexpr so that global file objects150 // like stdout do not require invocation of the constructor which can151 // potentially lead to static initialization order fiasco. Consequently,152 // we will assume that the |buffer| and |buffer_size| argument are153 // 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 of155 // 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 the191 // 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 close224 // implementation. The platform close should not need to access the buffer225 // 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 which230 // includes the file mutex. Hence, we call platform_close after releasing231 // the file lock. Another thread doing file operations while a thread is232 // 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 fail242 // 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 returned247 // 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 of275 // OpenMode, ContentType and CreateType.276 static ModeFlags mode_flags(const char *mode);277 278private: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 when291 // 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, let293 // us take up the three different kinds of buffering separately:294 // 1. If user wants _IOFBF but gives a zero buffer, buffering still295 // happens in the OS layer until the user flushes. So, from the user's296 // point of view, this single byte buffer does not affect their297 // experience.298 // 2. If user wants _IOLBF but gives a zero buffer, the reasoning is299 // 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 not302 // 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_file311// library.312ErrorOr<File *> openfile(const char *path, const char *mode);313 314// The platform_file library should implement it if it relevant for that315// platform.316int get_fileno(File *f);317 318extern File *stdin;319extern File *stdout;320extern File *stderr;321 322} // namespace LIBC_NAMESPACE_DECL323 324#endif // LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H325