/* * Copyright (c) 2021, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #pragma once struct FILE { public: FILE(int fd, int mode) : m_fd(fd) , m_mode(mode) { pthread_mutexattr_t attr = { __PTHREAD_MUTEX_RECURSIVE }; __pthread_mutex_init(&m_mutex, &attr); } ~FILE(); static FILE* create(int fd, int mode); void setbuf(u8* data, int mode, size_t size) { m_buffer.setbuf(data, mode, size); } bool flush(); void purge(); size_t pending(); bool close(); void lock(); void unlock(); int fileno() const { return m_fd; } bool eof() const { return m_eof; } int mode() const { return m_mode; } u8 flags() const { return m_flags; } int error() const { return m_error; } void clear_err() { m_error = 0; } void set_err() { m_error = 1; } size_t read(u8*, size_t); size_t write(u8 const*, size_t); template bool gets(CharType*, size_t); bool ungetc(u8 byte) { return m_buffer.enqueue_front(byte); } int seek(off_t offset, int whence); off_t tell(); pid_t popen_child() { return m_popen_child; } void set_popen_child(pid_t child_pid) { m_popen_child = child_pid; } void reopen(int fd, int mode); u8 const* readptr(size_t& available_size); void readptr_increase(size_t increment); enum Flags : u8 { None = 0, LastRead = 1, LastWrite = 2, }; private: struct Buffer { // A ringbuffer that also transparently implements ungetc(). public: ~Buffer(); int mode() const { return m_mode; } void setbuf(u8* data, int mode, size_t size); // Make sure to call realize() before enqueuing any data. // Dequeuing can be attempted without it. void realize(int fd); void drop(); bool may_use() const; bool is_not_empty() const { return m_ungotten || !m_empty; } size_t buffered_size() const; u8 const* begin_dequeue(size_t& available_size) const; void did_dequeue(size_t actual_size); u8* begin_enqueue(size_t& available_size) const; void did_enqueue(size_t actual_size); bool enqueue_front(u8 byte); private: constexpr static auto unget_buffer_size = MB_CUR_MAX; constexpr static u32 ungotten_mask = ((u32)0xffffffff) >> (sizeof(u32) * 8 - unget_buffer_size); // Note: the fields here are arranged this way // to make sizeof(Buffer) smaller. u8* m_data { nullptr }; size_t m_capacity { BUFSIZ }; size_t m_begin { 0 }; size_t m_end { 0 }; int m_mode { -1 }; Array m_unget_buffer { 0 }; u32 m_ungotten : unget_buffer_size { 0 }; bool m_data_is_malloced : 1 { false }; // When m_begin == m_end, we want to distinguish whether // the buffer is full or empty. bool m_empty : 1 { true }; }; // Read or write using the underlying fd, bypassing the buffer. ssize_t do_read(u8*, size_t); ssize_t do_write(u8 const*, size_t); // Read some data into the buffer. bool read_into_buffer(); // Flush *some* data from the buffer. bool write_from_buffer(); int m_fd { -1 }; int m_mode { 0 }; u8 m_flags { Flags::None }; int m_error { 0 }; bool m_eof { false }; pid_t m_popen_child { -1 }; Buffer m_buffer; __pthread_mutex_t m_mutex; IntrusiveListNode m_list_node; public: using List = IntrusiveList<&FILE::m_list_node>; }; class ScopedFileLock { public: ScopedFileLock(FILE* file) : m_file(file) { m_file->lock(); } ~ScopedFileLock() { m_file->unlock(); } private: FILE* m_file; };