diff --git a/Userland/Libraries/LibC/CMakeLists.txt b/Userland/Libraries/LibC/CMakeLists.txt index 742b1bf70d..6281610b51 100644 --- a/Userland/Libraries/LibC/CMakeLists.txt +++ b/Userland/Libraries/LibC/CMakeLists.txt @@ -66,6 +66,7 @@ set(LIBC_SOURCES utsname.cpp wchar.cpp wctype.cpp + wstdio.cpp ) file(GLOB AK_SOURCES CONFIGURE_DEPENDS "../../../AK/*.cpp") diff --git a/Userland/Libraries/LibC/bits/stdio_file_implementation.h b/Userland/Libraries/LibC/bits/stdio_file_implementation.h new file mode 100644 index 0000000000..81a762b5c1 --- /dev/null +++ b/Userland/Libraries/LibC/bits/stdio_file_implementation.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#pragma once + +struct FILE { +public: + FILE(int fd, int mode) + : m_fd(fd) + , m_mode(mode) + { + __pthread_mutex_init(&m_mutex, nullptr); + } + ~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(); + bool close(); + + 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; } + + size_t read(u8*, size_t); + size_t write(const u8*, size_t); + + bool gets(u8*, 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); + + 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 { return m_ungotten || m_mode != _IONBF; } + bool is_not_empty() const { return m_ungotten || !m_empty; } + size_t buffered_size() const; + + const u8* 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: + // 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 }; + u8 m_unget_buffer { 0 }; + bool m_ungotten : 1 { false }; + 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(const u8*, size_t); + + // Read some data into the buffer. + bool read_into_buffer(); + // Flush *some* data from the buffer. + bool write_from_buffer(); + + void lock(); + void unlock(); + + 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; + + friend class ScopedFileLock; +}; + +class ScopedFileLock { +public: + ScopedFileLock(FILE* file) + : m_file(file) + { + m_file->lock(); + } + + ~ScopedFileLock() + { + m_file->unlock(); + } + +private: + FILE* m_file; +}; diff --git a/Userland/Libraries/LibC/stdio.cpp b/Userland/Libraries/LibC/stdio.cpp index ac66b256e0..a800bd4ceb 100644 --- a/Userland/Libraries/LibC/stdio.cpp +++ b/Userland/Libraries/LibC/stdio.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -25,118 +25,6 @@ #include #include -struct FILE { -public: - FILE(int fd, int mode) - : m_fd(fd) - , m_mode(mode) - { - __pthread_mutex_init(&m_mutex, nullptr); - } - ~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(); - bool close(); - - 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; } - - size_t read(u8*, size_t); - size_t write(const u8*, size_t); - - bool gets(u8*, 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); - - 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 { return m_ungotten || m_mode != _IONBF; } - bool is_not_empty() const { return m_ungotten || !m_empty; } - size_t buffered_size() const; - - const u8* 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: - // 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 }; - u8 m_unget_buffer { 0 }; - bool m_ungotten : 1 { false }; - 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(const u8*, size_t); - - // Read some data into the buffer. - bool read_into_buffer(); - // Flush *some* data from the buffer. - bool write_from_buffer(); - - void lock(); - void unlock(); - - 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; - - friend class ScopedFileLock; -}; - FILE::~FILE() { bool already_closed = m_fd == -1; @@ -595,23 +483,6 @@ void FILE::unlock() __pthread_mutex_unlock(&m_mutex); } -class ScopedFileLock { -public: - ScopedFileLock(FILE* file) - : m_file(file) - { - m_file->lock(); - } - - ~ScopedFileLock() - { - m_file->unlock(); - } - -private: - FILE* m_file; -}; - extern "C" { alignas(FILE) static u8 default_streams[3][sizeof(FILE)]; diff --git a/Userland/Libraries/LibC/wchar.h b/Userland/Libraries/LibC/wchar.h index 3a0adce1e6..ae9bb5ed91 100644 --- a/Userland/Libraries/LibC/wchar.h +++ b/Userland/Libraries/LibC/wchar.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include @@ -69,4 +71,7 @@ size_t mbsnrtowcs(wchar_t*, const char**, size_t, size_t, mbstate_t*); size_t wcscspn(const wchar_t* wcs, const wchar_t* reject); size_t wcsspn(const wchar_t* wcs, const wchar_t* accept); +wint_t fgetwc(FILE* stream); +wint_t getwc(FILE* stream); + __END_DECLS diff --git a/Userland/Libraries/LibC/wstdio.cpp b/Userland/Libraries/LibC/wstdio.cpp new file mode 100644 index 0000000000..de41056abb --- /dev/null +++ b/Userland/Libraries/LibC/wstdio.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +static_assert(AssertSize()); + +extern "C" { + +wint_t fgetwc(FILE* stream) +{ + VERIFY(stream); + Array underlying; + auto underlying_bytes = underlying.span(); + size_t encoded_length = 0; + size_t bytes_read = 0; + do { + size_t nread = fread(underlying_bytes.offset_pointer(bytes_read), 1, 1, stream); + if (nread != 1) { + errno = EILSEQ; + return WEOF; + } + ++bytes_read; + if (bytes_read == 1) { + if (underlying[0] >> 7 == 0) { + encoded_length = 1; + } else if (underlying[0] >> 5 == 6) { + encoded_length = 2; + } else if (underlying[0] >> 4 == 0xe) { + encoded_length = 3; + } else if (underlying[0] >> 3 == 0x1e) { + encoded_length = 4; + } else { + errno = EILSEQ; + return WEOF; + } + } + } while (bytes_read < encoded_length); + + wchar_t code_point; + auto read_bytes = mbrtowc(&code_point, bit_cast(underlying.data()), encoded_length, nullptr); + VERIFY(read_bytes == encoded_length); + return code_point; +} + +wint_t getwc(FILE* stream) +{ + return fgetwc(stream); +} +}