Path: blob/main/contrib/llvm-project/libc/src/__support/File/file.h
213799 views
//===--- A platform independent file data structure -------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#ifndef LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H9#define LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H1011#include "hdr/stdio_macros.h"12#include "hdr/types/off_t.h"13#include "src/__support/CPP/new.h"14#include "src/__support/error_or.h"15#include "src/__support/macros/config.h"16#include "src/__support/macros/properties/architectures.h"17#include "src/__support/threads/mutex.h"1819#include <stddef.h>20#include <stdint.h>2122namespace LIBC_NAMESPACE_DECL {2324struct FileIOResult {25size_t value;26int error;2728constexpr FileIOResult(size_t val) : value(val), error(0) {}29constexpr FileIOResult(size_t val, int error) : value(val), error(error) {}3031constexpr bool has_error() { return error != 0; }3233constexpr operator size_t() { return value; }34};3536// This a generic base class to encapsulate a platform independent file data37// structure. Platform specific specializations should create a subclass as38// suitable for their platform.39class File {40public:41static constexpr size_t DEFAULT_BUFFER_SIZE = 1024;4243using LockFunc = void(File *);44using UnlockFunc = void(File *);4546using WriteFunc = FileIOResult(File *, const void *, size_t);47using ReadFunc = FileIOResult(File *, void *, size_t);48// The SeekFunc is expected to return the current offset of the external49// file position indicator.50using SeekFunc = ErrorOr<off_t>(File *, off_t, int);51using CloseFunc = int(File *);5253using ModeFlags = uint32_t;5455// The three different types of flags below are to be used with '|' operator.56// Their values correspond to mutually exclusive bits in a 32-bit unsigned57// integer value. A flag set can include both READ and WRITE if the file58// is opened in update mode (ie. if the file was opened with a '+' the mode59// string.)60enum class OpenMode : ModeFlags {61READ = 0x1,62WRITE = 0x2,63APPEND = 0x4,64PLUS = 0x8,65};6667// Denotes a file opened in binary mode (which is specified by including68// the 'b' character in teh mode string.)69enum class ContentType : ModeFlags {70BINARY = 0x10,71};7273// Denotes a file to be created for writing.74enum class CreateType : ModeFlags {75EXCLUSIVE = 0x100,76};7778private:79enum class FileOp : uint8_t { NONE, READ, WRITE, SEEK };8081// Platform specific functions which create new file objects should initialize82// these fields suitably via the constructor. Typically, they should be simple83// syscall wrappers for the corresponding functionality.84WriteFunc *platform_write;85ReadFunc *platform_read;86SeekFunc *platform_seek;87CloseFunc *platform_close;8889Mutex mutex;9091// For files which are readable, we should be able to support one ungetc92// operation even if |buf| is nullptr. So, in the constructor of File, we93// set |buf| to point to this buffer character.94uint8_t ungetc_buf;9596uint8_t *buf; // Pointer to the stream buffer for buffered streams97size_t bufsize; // Size of the buffer pointed to by |buf|.9899// Buffering mode to used to buffer.100int bufmode;101102// If own_buf is true, the |buf| is owned by the stream and will be103// free-ed when close method is called on the stream.104bool own_buf;105106// The mode in which the file was opened.107ModeFlags mode;108109// Current read or write pointer.110size_t pos;111112// Represents the previous operation that was performed.113FileOp prev_op;114115// When the buffer is used as a read buffer, read_limit is the upper limit116// of the index to which the buffer can be read until.117size_t read_limit;118119bool eof;120bool err;121122// This is a convenience RAII class to lock and unlock file objects.123class FileLock {124File *file;125126public:127explicit FileLock(File *f) : file(f) { file->lock(); }128129~FileLock() { file->unlock(); }130131FileLock(const FileLock &) = delete;132FileLock(FileLock &&) = delete;133};134135protected:136constexpr bool write_allowed() const {137return mode & (static_cast<ModeFlags>(OpenMode::WRITE) |138static_cast<ModeFlags>(OpenMode::APPEND) |139static_cast<ModeFlags>(OpenMode::PLUS));140}141142constexpr bool read_allowed() const {143return mode & (static_cast<ModeFlags>(OpenMode::READ) |144static_cast<ModeFlags>(OpenMode::PLUS));145}146147public:148// We want this constructor to be constexpr so that global file objects149// like stdout do not require invocation of the constructor which can150// potentially lead to static initialization order fiasco. Consequently,151// we will assume that the |buffer| and |buffer_size| argument are152// meaningful - that is, |buffer| is nullptr if and only if |buffer_size|153// is zero. This way, we will not have to employ the semantics of154// the set_buffer method and allocate a buffer.155constexpr File(WriteFunc *wf, ReadFunc *rf, SeekFunc *sf, CloseFunc *cf,156uint8_t *buffer, size_t buffer_size, int buffer_mode,157bool owned, ModeFlags modeflags)158: platform_write(wf), platform_read(rf), platform_seek(sf),159platform_close(cf), mutex(/*timed=*/false, /*recursive=*/false,160/*robust=*/false, /*pshared=*/false),161ungetc_buf(0), buf(buffer), bufsize(buffer_size), bufmode(buffer_mode),162own_buf(owned), mode(modeflags), pos(0), prev_op(FileOp::NONE),163read_limit(0), eof(false), err(false) {164adjust_buf();165}166167// Buffered write of |len| bytes from |data| without the file lock.168FileIOResult write_unlocked(const void *data, size_t len);169170// Buffered write of |len| bytes from |data| under the file lock.171FileIOResult write(const void *data, size_t len) {172FileLock l(this);173return write_unlocked(data, len);174}175176// Buffered read of |len| bytes into |data| without the file lock.177FileIOResult read_unlocked(void *data, size_t len);178179// Buffered read of |len| bytes into |data| under the file lock.180FileIOResult read(void *data, size_t len) {181FileLock l(this);182return read_unlocked(data, len);183}184185ErrorOr<int> seek(off_t offset, int whence);186187ErrorOr<off_t> tell();188189// If buffer has data written to it, flush it out. Does nothing if the190// buffer is currently being used as a read buffer.191int flush() {192FileLock lock(this);193return flush_unlocked();194}195196int flush_unlocked();197198// Returns EOF on error and keeps the file unchanged.199int ungetc_unlocked(int c);200201int ungetc(int c) {202FileLock lock(this);203return ungetc_unlocked(c);204}205206// Does the following:207// 1. If in write mode, Write out any data present in the buffer.208// 2. Call platform_close.209// platform_close is expected to cleanup the complete file object.210int close() {211{212FileLock lock(this);213if (prev_op == FileOp::WRITE && pos > 0) {214auto buf_result = platform_write(this, buf, pos);215if (buf_result.has_error() || buf_result.value < pos) {216err = true;217return buf_result.error;218}219}220}221222// If we own the buffer, delete it before calling the platform close223// implementation. The platform close should not need to access the buffer224// and we need to clean it up before the entire structure is removed.225if (own_buf)226delete buf;227228// Platform close is expected to cleanup the file data structure which229// includes the file mutex. Hence, we call platform_close after releasing230// the file lock. Another thread doing file operations while a thread is231// closing the file is undefined behavior as per POSIX.232return platform_close(this);233}234235// Sets the internal buffer to |buffer| with buffering mode |mode|.236// |size| is the size of |buffer|. If |size| is non-zero, but |buffer|237// is nullptr, then a buffer owned by this file will be allocated.238// Else, |buffer| will not be owned by this file.239//240// Will return zero on success, or an error value on failure. Will fail241// if:242// 1. |buffer| is not a nullptr but |size| is zero.243// 2. |buffer_mode| is not one of _IOLBF, IOFBF or _IONBF.244// 3. If an allocation was required but the allocation failed.245// For cases 1 and 2, the error returned in EINVAL. For case 3, error returned246// is ENOMEM.247int set_buffer(void *buffer, size_t size, int buffer_mode);248249void lock() { mutex.lock(); }250void unlock() { mutex.unlock(); }251252bool error_unlocked() const { return err; }253254bool error() {255FileLock l(this);256return error_unlocked();257}258259void clearerr_unlocked() { err = false; }260261void clearerr() {262FileLock l(this);263clearerr_unlocked();264}265266bool iseof_unlocked() { return eof; }267268bool iseof() {269FileLock l(this);270return iseof_unlocked();271}272273// Returns an bit map of flags corresponding to enumerations of274// OpenMode, ContentType and CreateType.275static ModeFlags mode_flags(const char *mode);276277private:278FileIOResult write_unlocked_lbf(const uint8_t *data, size_t len);279FileIOResult write_unlocked_fbf(const uint8_t *data, size_t len);280FileIOResult write_unlocked_nbf(const uint8_t *data, size_t len);281282FileIOResult read_unlocked_fbf(uint8_t *data, size_t len);283FileIOResult read_unlocked_nbf(uint8_t *data, size_t len);284size_t copy_data_from_buf(uint8_t *data, size_t len);285286constexpr void adjust_buf() {287if (read_allowed() && (buf == nullptr || bufsize == 0)) {288// We should allow atleast one ungetc operation.289// This might give an impression that a buffer will be used even when290// the user does not want a buffer. But, that will not be the case.291// For reading, the buffering does not come into play. For writing, let292// us take up the three different kinds of buffering separately:293// 1. If user wants _IOFBF but gives a zero buffer, buffering still294// happens in the OS layer until the user flushes. So, from the user's295// point of view, this single byte buffer does not affect their296// experience.297// 2. If user wants _IOLBF but gives a zero buffer, the reasoning is298// very similar to the _IOFBF case.299// 3. If user wants _IONBF, then the buffer is ignored for writing.300// So, all of the above cases, having a single ungetc buffer does not301// affect the behavior experienced by the user.302buf = &ungetc_buf;303bufsize = 1;304own_buf = false; // We shouldn't call free on |buf| when closing the file.305}306}307};308309// The implementaiton of this function is provided by the platform_file310// library.311ErrorOr<File *> openfile(const char *path, const char *mode);312313// The platform_file library should implement it if it relevant for that314// platform.315int get_fileno(File *f);316317extern File *stdin;318extern File *stdout;319extern File *stderr;320321} // namespace LIBC_NAMESPACE_DECL322323#endif // LLVM_LIBC_SRC___SUPPORT_FILE_FILE_H324325326