//===--- A platform independent file data structure -------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H #define LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H #include "hdr/stdio_macros.h" #include "hdr/types/off_t.h" #include "src/__support/CPP/new.h" #include "src/__support/error_or.h" #include "src/__support/macros/config.h" #include "src/__support/macros/properties/architectures.h" #include "src/__support/threads/mutex.h" #include #include namespace LIBC_NAMESPACE_DECL { struct FileIOResult { size_t value; int error; constexpr FileIOResult(size_t val) : value(val), error(0) {} constexpr FileIOResult(size_t val, int error) : value(val), error(error) {} constexpr bool has_error() { return error != 0; } constexpr operator size_t() { return value; } }; // This a generic base class to encapsulate a platform independent file data // structure. Platform specific specializations should create a subclass as // suitable for their platform. class File { public: static constexpr size_t DEFAULT_BUFFER_SIZE = 1024; using LockFunc = void(File *); using UnlockFunc = void(File *); using WriteFunc = FileIOResult(File *, const void *, size_t); using ReadFunc = FileIOResult(File *, void *, size_t); // The SeekFunc is expected to return the current offset of the external // file position indicator. using SeekFunc = ErrorOr(File *, off_t, int); using CloseFunc = int(File *); using ModeFlags = uint32_t; // The three different types of flags below are to be used with '|' operator. // Their values correspond to mutually exclusive bits in a 32-bit unsigned // integer value. A flag set can include both READ and WRITE if the file // is opened in update mode (ie. if the file was opened with a '+' the mode // string.) enum class OpenMode : ModeFlags { READ = 0x1, WRITE = 0x2, APPEND = 0x4, PLUS = 0x8, }; // Denotes a file opened in binary mode (which is specified by including // the 'b' character in teh mode string.) enum class ContentType : ModeFlags { BINARY = 0x10, }; // Denotes a file to be created for writing. enum class CreateType : ModeFlags { EXCLUSIVE = 0x100, }; private: enum class FileOp : uint8_t { NONE, READ, WRITE, SEEK }; // Platform specific functions which create new file objects should initialize // these fields suitably via the constructor. Typically, they should be simple // syscall wrappers for the corresponding functionality. WriteFunc *platform_write; ReadFunc *platform_read; SeekFunc *platform_seek; CloseFunc *platform_close; Mutex mutex; // For files which are readable, we should be able to support one ungetc // operation even if |buf| is nullptr. So, in the constructor of File, we // set |buf| to point to this buffer character. uint8_t ungetc_buf; uint8_t *buf; // Pointer to the stream buffer for buffered streams size_t bufsize; // Size of the buffer pointed to by |buf|. // Buffering mode to used to buffer. int bufmode; // If own_buf is true, the |buf| is owned by the stream and will be // free-ed when close method is called on the stream. bool own_buf; // The mode in which the file was opened. ModeFlags mode; // Current read or write pointer. size_t pos; // Represents the previous operation that was performed. FileOp prev_op; // When the buffer is used as a read buffer, read_limit is the upper limit // of the index to which the buffer can be read until. size_t read_limit; bool eof; bool err; // This is a convenience RAII class to lock and unlock file objects. class FileLock { File *file; public: explicit FileLock(File *f) : file(f) { file->lock(); } ~FileLock() { file->unlock(); } FileLock(const FileLock &) = delete; FileLock(FileLock &&) = delete; }; protected: constexpr bool write_allowed() const { return mode & (static_cast(OpenMode::WRITE) | static_cast(OpenMode::APPEND) | static_cast(OpenMode::PLUS)); } constexpr bool read_allowed() const { return mode & (static_cast(OpenMode::READ) | static_cast(OpenMode::PLUS)); } public: // We want this constructor to be constexpr so that global file objects // like stdout do not require invocation of the constructor which can // potentially lead to static initialization order fiasco. Consequently, // we will assume that the |buffer| and |buffer_size| argument are // meaningful - that is, |buffer| is nullptr if and only if |buffer_size| // is zero. This way, we will not have to employ the semantics of // the set_buffer method and allocate a buffer. constexpr File(WriteFunc *wf, ReadFunc *rf, SeekFunc *sf, CloseFunc *cf, uint8_t *buffer, size_t buffer_size, int buffer_mode, bool owned, ModeFlags modeflags) : platform_write(wf), platform_read(rf), platform_seek(sf), platform_close(cf), mutex(/*timed=*/false, /*recursive=*/false, /*robust=*/false, /*pshared=*/false), ungetc_buf(0), buf(buffer), bufsize(buffer_size), bufmode(buffer_mode), own_buf(owned), mode(modeflags), pos(0), prev_op(FileOp::NONE), read_limit(0), eof(false), err(false) { adjust_buf(); } // Buffered write of |len| bytes from |data| without the file lock. FileIOResult write_unlocked(const void *data, size_t len); // Buffered write of |len| bytes from |data| under the file lock. FileIOResult write(const void *data, size_t len) { FileLock l(this); return write_unlocked(data, len); } // Buffered read of |len| bytes into |data| without the file lock. FileIOResult read_unlocked(void *data, size_t len); // Buffered read of |len| bytes into |data| under the file lock. FileIOResult read(void *data, size_t len) { FileLock l(this); return read_unlocked(data, len); } ErrorOr seek(off_t offset, int whence); ErrorOr tell(); // If buffer has data written to it, flush it out. Does nothing if the // buffer is currently being used as a read buffer. int flush() { FileLock lock(this); return flush_unlocked(); } int flush_unlocked(); // Returns EOF on error and keeps the file unchanged. int ungetc_unlocked(int c); int ungetc(int c) { FileLock lock(this); return ungetc_unlocked(c); } // Does the following: // 1. If in write mode, Write out any data present in the buffer. // 2. Call platform_close. // platform_close is expected to cleanup the complete file object. int close() { { FileLock lock(this); if (prev_op == FileOp::WRITE && pos > 0) { auto buf_result = platform_write(this, buf, pos); if (buf_result.has_error() || buf_result.value < pos) { err = true; return buf_result.error; } } } // If we own the buffer, delete it before calling the platform close // implementation. The platform close should not need to access the buffer // and we need to clean it up before the entire structure is removed. if (own_buf) delete buf; // Platform close is expected to cleanup the file data structure which // includes the file mutex. Hence, we call platform_close after releasing // the file lock. Another thread doing file operations while a thread is // closing the file is undefined behavior as per POSIX. return platform_close(this); } // Sets the internal buffer to |buffer| with buffering mode |mode|. // |size| is the size of |buffer|. If |size| is non-zero, but |buffer| // is nullptr, then a buffer owned by this file will be allocated. // Else, |buffer| will not be owned by this file. // // Will return zero on success, or an error value on failure. Will fail // if: // 1. |buffer| is not a nullptr but |size| is zero. // 2. |buffer_mode| is not one of _IOLBF, IOFBF or _IONBF. // 3. If an allocation was required but the allocation failed. // For cases 1 and 2, the error returned in EINVAL. For case 3, error returned // is ENOMEM. int set_buffer(void *buffer, size_t size, int buffer_mode); void lock() { mutex.lock(); } void unlock() { mutex.unlock(); } bool error_unlocked() const { return err; } bool error() { FileLock l(this); return error_unlocked(); } void clearerr_unlocked() { err = false; } void clearerr() { FileLock l(this); clearerr_unlocked(); } bool iseof_unlocked() { return eof; } bool iseof() { FileLock l(this); return iseof_unlocked(); } // Returns an bit map of flags corresponding to enumerations of // OpenMode, ContentType and CreateType. static ModeFlags mode_flags(const char *mode); private: FileIOResult write_unlocked_lbf(const uint8_t *data, size_t len); FileIOResult write_unlocked_fbf(const uint8_t *data, size_t len); FileIOResult write_unlocked_nbf(const uint8_t *data, size_t len); FileIOResult read_unlocked_fbf(uint8_t *data, size_t len); FileIOResult read_unlocked_nbf(uint8_t *data, size_t len); size_t copy_data_from_buf(uint8_t *data, size_t len); constexpr void adjust_buf() { if (read_allowed() && (buf == nullptr || bufsize == 0)) { // We should allow atleast one ungetc operation. // This might give an impression that a buffer will be used even when // the user does not want a buffer. But, that will not be the case. // For reading, the buffering does not come into play. For writing, let // us take up the three different kinds of buffering separately: // 1. If user wants _IOFBF but gives a zero buffer, buffering still // happens in the OS layer until the user flushes. So, from the user's // point of view, this single byte buffer does not affect their // experience. // 2. If user wants _IOLBF but gives a zero buffer, the reasoning is // very similar to the _IOFBF case. // 3. If user wants _IONBF, then the buffer is ignored for writing. // So, all of the above cases, having a single ungetc buffer does not // affect the behavior experienced by the user. buf = &ungetc_buf; bufsize = 1; own_buf = false; // We shouldn't call free on |buf| when closing the file. } } }; // The implementaiton of this function is provided by the platform_file // library. ErrorOr openfile(const char *path, const char *mode); // The platform_file library should implement it if it relevant for that // platform. int get_fileno(File *f); extern File *stdin; extern File *stdout; extern File *stderr; } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H