summaryrefslogtreecommitdiff
path: root/lib/mlibc/options/ansi/generic/file-io.cpp
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2024-03-07 17:28:52 -0500
committerIan Moffett <ian@osmora.org>2024-03-07 18:24:51 -0500
commitf5e48e94a2f4d4bbd6e5628c7f2afafc6dbcc459 (patch)
tree93b156621dc0303816b37f60ba88051b702d92f6 /lib/mlibc/options/ansi/generic/file-io.cpp
parentbd5969fc876a10b18613302db7087ef3c40f18e1 (diff)
build: Build mlibc + add distclean target
Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'lib/mlibc/options/ansi/generic/file-io.cpp')
-rw-r--r--lib/mlibc/options/ansi/generic/file-io.cpp745
1 files changed, 0 insertions, 745 deletions
diff --git a/lib/mlibc/options/ansi/generic/file-io.cpp b/lib/mlibc/options/ansi/generic/file-io.cpp
deleted file mode 100644
index e59b109..0000000
--- a/lib/mlibc/options/ansi/generic/file-io.cpp
+++ /dev/null
@@ -1,745 +0,0 @@
-
-#include <errno.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <stdio.h>
-#if __MLIBC_GLIBC_OPTION
-#include <stdio_ext.h>
-#endif
-
-#include <bits/ensure.h>
-
-#include <mlibc/debug.hpp>
-
-#include <abi-bits/fcntl.h>
-#include <frg/allocation.hpp>
-#include <frg/mutex.hpp>
-#include <mlibc/allocator.hpp>
-#include <mlibc/file-io.hpp>
-#include <mlibc/ansi-sysdeps.hpp>
-#include <mlibc/lock.hpp>
-
-namespace mlibc {
-
-// --------------------------------------------------------------------------------------
-// abstract_file implementation.
-// --------------------------------------------------------------------------------------
-
-namespace {
- using file_list = frg::intrusive_list<
- abstract_file,
- frg::locate_member<
- abstract_file,
- frg::default_list_hook<abstract_file>,
- &abstract_file::_list_hook
- >
- >;
-
- // Useful when debugging the FILE implementation.
- constexpr bool globallyDisableBuffering = false;
-
- // The maximum number of characters we permit the user to ungetc.
- constexpr size_t ungetBufferSize = 8;
-
- // List of files that will be flushed before exit().
- file_list &global_file_list() {
- static frg::eternal<file_list> list;
- return list.get();
- };
-}
-
-// For pipe-like streams (seek returns ESPIPE), we need to make sure
-// that the buffer only ever contains all-dirty or all-clean data.
-// Regarding _type and _bufmode:
-// As we might construct FILE objects for FDs that are not actually
-// open (e.g. for std{in,out,err}), we defer the type determination and cache the result.
-
-abstract_file::abstract_file(void (*do_dispose)(abstract_file *))
-: _type{stream_type::unknown}, _bufmode{buffer_mode::unknown}, _do_dispose{do_dispose} {
- // TODO: For __fwriting to work correctly, set the __io_mode to 1 if the write is write-only.
- __buffer_ptr = nullptr;
- __unget_ptr = nullptr;
- __buffer_size = 4096;
- __offset = 0;
- __io_offset = 0;
- __valid_limit = 0;
- __dirty_begin = 0;
- __dirty_end = 0;
- __io_mode = 0;
- __status_bits = 0;
-
- global_file_list().push_back(this);
-}
-
-abstract_file::~abstract_file() {
- if(__dirty_begin != __dirty_end)
- mlibc::infoLogger() << "mlibc warning: File is not flushed before destruction"
- << frg::endlog;
-
- if(__buffer_ptr)
- getAllocator().free(__buffer_ptr - ungetBufferSize);
-
- auto it = global_file_list().iterator_to(this);
- global_file_list().erase(it);
-}
-
-void abstract_file::dispose() {
- if(!_do_dispose)
- return;
- _do_dispose(this);
-}
-
-// Note that read() and write() are asymmetric:
-// While read() can trigger a write-back, write() can never trigger a read-ahead().
-// This peculiarity is reflected in their code.
-
-int abstract_file::read(char *buffer, size_t max_size, size_t *actual_size) {
- __ensure(max_size);
-
- if(_init_bufmode())
- return -1;
-
- size_t unget_length = 0;
- if (__unget_ptr != __buffer_ptr) {
- unget_length = frg::min(max_size, (size_t)(__buffer_ptr - __unget_ptr));
- memcpy(buffer, __unget_ptr, unget_length);
-
- __unget_ptr += unget_length;
- buffer += unget_length;
- max_size -= unget_length;
-
- if (max_size == 0) {
- *actual_size = unget_length;
- return 0;
- }
- }
-
- if(globallyDisableBuffering || _bufmode == buffer_mode::no_buffer) {
- size_t io_size;
- if(int e = io_read(buffer, max_size, &io_size); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- if(!io_size)
- __status_bits |= __MLIBC_EOF_BIT;
- *actual_size = io_size + unget_length;
- return 0;
- }
-
- // Ensure correct buffer type for pipe-like streams.
- // TODO: In order to support pipe-like streams we need to write-back the buffer.
- if(__io_mode && __valid_limit)
- mlibc::panicLogger() << "mlibc: Cannot read-write to same pipe-like stream"
- << frg::endlog;
- __io_mode = 0;
-
- // Clear the buffer, then buffer new data.
- if(__offset == __valid_limit) {
- // TODO: We only have to write-back/reset if __valid_limit reaches the buffer end.
- if(int e = _write_back(); e)
- return e;
- if(int e = _reset(); e)
- return e;
-
- // Perform a read-ahead.
- _ensure_allocation();
- size_t io_size;
- if(int e = io_read(__buffer_ptr, __buffer_size, &io_size); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- if(!io_size) {
- __status_bits |= __MLIBC_EOF_BIT;
- *actual_size = 0;
- return 0;
- }
-
- __io_offset = io_size;
- __valid_limit = io_size;
- }
-
- // Return data from the buffer.
- __ensure(__offset < __valid_limit);
-
- auto chunk = frg::min(size_t(__valid_limit - __offset), max_size);
- memcpy(buffer, __buffer_ptr + __offset, chunk);
- __offset += chunk;
-
- *actual_size = chunk + unget_length;
- return 0;
-}
-
-int abstract_file::write(const char *buffer, size_t max_size, size_t *actual_size) {
- __ensure(max_size);
-
- if(_init_bufmode())
- return -1;
- if(globallyDisableBuffering || _bufmode == buffer_mode::no_buffer) {
- // As we do not buffer, nothing can be dirty.
- __ensure(__dirty_begin == __dirty_end);
- size_t io_size;
- if(int e = io_write(buffer, max_size, &io_size); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- *actual_size = io_size;
- return 0;
- }
-
- // Flush the buffer if necessary.
- if(__offset == __buffer_size) {
- if(int e = _write_back(); e)
- return e;
- if(int e = _reset(); e)
- return e;
- }
-
- // Ensure correct buffer type for pipe-like streams.
- // TODO: We could full support pipe-like files
- // by ungetc()ing all data before a write happens,
- // however, for now we just report an error.
- if(!__io_mode && __valid_limit) // TODO: Only check this for pipe-like streams.
- mlibc::panicLogger() << "mlibc: Cannot read-write to same pipe-like stream"
- << frg::endlog;
- __io_mode = 1;
-
- __ensure(__offset < __buffer_size);
- auto chunk = frg::min(__buffer_size - __offset, max_size);
-
- // Line-buffered streams perform I/O on full lines.
- bool flush_line = false;
- if(_bufmode == buffer_mode::line_buffer) {
- auto nl = reinterpret_cast<char *>(memchr(buffer, '\n', chunk));
- if(nl) {
- chunk = nl + 1 - buffer;
- flush_line = true;
- }
- }
- __ensure(chunk);
-
- // Buffer data (without necessarily performing I/O).
- _ensure_allocation();
- memcpy(__buffer_ptr + __offset, buffer, chunk);
-
- if(__dirty_begin != __dirty_end) {
- __dirty_begin = frg::min(__dirty_begin, __offset);
- __dirty_end = frg::max(__dirty_end, __offset + chunk);
- }else{
- __dirty_begin = __offset;
- __dirty_end = __offset + chunk;
- }
- __valid_limit = frg::max(__offset + chunk, __valid_limit);
- __offset += chunk;
-
- // Flush line-buffered streams.
- if(flush_line) {
- if(_write_back())
- return -1;
- }
-
- *actual_size = chunk;
- return 0;
-}
-
-int abstract_file::unget(char c) {
- if (!__unget_ptr) {
- // This can happen if the file is unbuffered, but we still need
- // a space to store ungetc'd data.
- __ensure(!__buffer_ptr);
- _ensure_allocation();
- __ensure(__unget_ptr);
- }
-
- if ((size_t)(__buffer_ptr - __unget_ptr) + 1 > ungetBufferSize)
- return EOF;
- else {
- *(--__unget_ptr) = c;
- return c;
- }
-}
-
-int abstract_file::update_bufmode(buffer_mode mode) {
- // setvbuf() has undefined behavior if I/O has been performed.
- __ensure(__dirty_begin == __dirty_end
- && "update_bufmode() must only be called before performing I/O");
- _bufmode = mode;
- return 0;
-}
-
-void abstract_file::purge() {
- __offset = 0;
- __io_offset = 0;
- __valid_limit = 0;
- __dirty_end = __dirty_begin;
- __unget_ptr = __buffer_ptr;
-}
-
-int abstract_file::flush() {
- if (__dirty_end != __dirty_begin) {
- if (int e = _write_back(); e)
- return e;
- }
-
- if (int e = _save_pos(); e)
- return e;
- purge();
- return 0;
-}
-
-int abstract_file::tell(off_t *current_offset) {
- off_t seek_offset;
- if(int e = io_seek(0, SEEK_CUR, &seek_offset); e)
- return e;
-
- *current_offset = seek_offset + (off_t(__offset) - off_t(__io_offset));
- return 0;
-}
-
-int abstract_file::seek(off_t offset, int whence) {
- if(int e = _write_back(); e)
- return e;
-
- off_t new_offset;
- if(whence == SEEK_CUR) {
- auto seek_offset = offset + (off_t(__offset) - off_t(__io_offset));
- if(int e = io_seek(seek_offset, whence, &new_offset); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- }else{
- __ensure(whence == SEEK_SET || whence == SEEK_END);
- if(int e = io_seek(offset, whence, &new_offset); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- }
-
- // We just forget the current buffer.
- // TODO: If the seek is "small", we can just modify our internal offset.
- purge();
-
- return 0;
-}
-
-int abstract_file::_init_type() {
- if(_type != stream_type::unknown)
- return 0;
-
- if(int e = determine_type(&_type); e)
- return e;
- __ensure(_type != stream_type::unknown);
- return 0;
-}
-
-int abstract_file::_init_bufmode() {
- if(_bufmode != buffer_mode::unknown)
- return 0;
-
- if(determine_bufmode(&_bufmode))
- return -1;
- __ensure(_bufmode != buffer_mode::unknown);
- return 0;
-}
-
-int abstract_file::_write_back() {
- if(int e = _init_type(); e)
- return e;
-
- if(__dirty_begin == __dirty_end)
- return 0;
-
- // For non-pipe streams, first do a seek to reset the
- // I/O position to zero, then do a write().
- if(_type == stream_type::file_like) {
- if(__io_offset != __dirty_begin) {
- __ensure(__dirty_begin - __io_offset > 0);
- off_t new_offset;
- if(int e = io_seek(off_t(__dirty_begin) - off_t(__io_offset), SEEK_CUR, &new_offset); e)
- return e;
- __io_offset = __dirty_begin;
- }
- }else{
- __ensure(_type == stream_type::pipe_like);
- __ensure(__io_offset == __dirty_begin);
- }
-
- // Now, we are in the correct position to write-back everything.
- while(__io_offset < __dirty_end) {
- size_t io_size;
- if(int e = io_write(__buffer_ptr + __io_offset, __dirty_end - __io_offset, &io_size); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- return e;
- }
- __ensure(io_size > 0 && "io_write() is expected to always write at least one byte");
- __io_offset += io_size;
- __dirty_begin += io_size;
- }
-
- return 0;
-}
-
-int abstract_file::_save_pos() {
- if (int e = _init_type(); e)
- return e;
- if (int e = _init_bufmode(); e)
- return e;
-
- if (_type == stream_type::file_like && _bufmode != buffer_mode::no_buffer) {
- off_t new_offset;
- auto seek_offset = (off_t(__offset) - off_t(__io_offset));
- if (int e = io_seek(seek_offset, SEEK_CUR, &new_offset); e) {
- __status_bits |= __MLIBC_ERROR_BIT;
- mlibc::infoLogger() << "hit io_seek() error " << e << frg::endlog;
- return e;
- }
- return 0;
- }
- return 0; // nothing to do for the rest
-}
-
-int abstract_file::_reset() {
- if(int e = _init_type(); e)
- return e;
-
- // For pipe-like files, we must not forget already read data.
- // TODO: Report this error to the user.
- if(_type == stream_type::pipe_like)
- __ensure(__offset == __valid_limit);
-
- __ensure(__dirty_begin == __dirty_end);
- __offset = 0;
- __io_offset = 0;
- __valid_limit = 0;
-
- return 0;
-}
-
-// This may still be called when buffering is disabled, for ungetc.
-void abstract_file::_ensure_allocation() {
- if(__buffer_ptr)
- return;
-
- auto ptr = getAllocator().allocate(__buffer_size + ungetBufferSize);
- __buffer_ptr = reinterpret_cast<char *>(ptr) + ungetBufferSize;
- __unget_ptr = __buffer_ptr;
-}
-
-// --------------------------------------------------------------------------------------
-// fd_file implementation.
-// --------------------------------------------------------------------------------------
-
-fd_file::fd_file(int fd, void (*do_dispose)(abstract_file *), bool force_unbuffered)
-: abstract_file{do_dispose}, _fd{fd}, _force_unbuffered{force_unbuffered} { }
-
-int fd_file::fd() {
- return _fd;
-}
-
-int fd_file::close() {
- if(__dirty_begin != __dirty_end)
- mlibc::infoLogger() << "mlibc warning: File is not flushed before closing"
- << frg::endlog;
- if(int e = mlibc::sys_close(_fd); e)
- return e;
- return 0;
-}
-
-int fd_file::reopen(const char *path, const char *mode) {
- int mode_flags = parse_modestring(mode);
-
- int fd;
- if(int e = sys_open(path, mode_flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, &fd); e) {
- return e;
- }
-
- flush();
- close();
- getAllocator().deallocate(__buffer_ptr, __buffer_size + ungetBufferSize);
-
- __buffer_ptr = nullptr;
- __unget_ptr = nullptr;
- __buffer_size = 4096;
- _reset();
- _fd = fd;
-
- if(mode_flags & O_APPEND) {
- seek(0, SEEK_END);
- }
-
- return 0;
-}
-
-int fd_file::determine_type(stream_type *type) {
- off_t offset;
- int e = mlibc::sys_seek(_fd, 0, SEEK_CUR, &offset);
- if(!e) {
- *type = stream_type::file_like;
- return 0;
- }else if(e == ESPIPE) {
- *type = stream_type::pipe_like;
- return 0;
- }else{
- return e;
- }
-}
-
-int fd_file::determine_bufmode(buffer_mode *mode) {
- // When isatty() is not implemented, we fall back to the safest default (no buffering).
- if(!mlibc::sys_isatty) {
- MLIBC_MISSING_SYSDEP();
- *mode = buffer_mode::no_buffer;
- return 0;
- }
- if(_force_unbuffered) {
- *mode = buffer_mode::no_buffer;
- return 0;
- }
-
- if(int e = mlibc::sys_isatty(_fd); !e) {
- *mode = buffer_mode::line_buffer;
- return 0;
- }else if(e == ENOTTY) {
- *mode = buffer_mode::full_buffer;
- return 0;
- }else{
- mlibc::infoLogger() << "mlibc: sys_isatty() failed while determining whether"
- " stream is interactive" << frg::endlog;
- return -1;
- }
-}
-
-int fd_file::io_read(char *buffer, size_t max_size, size_t *actual_size) {
- ssize_t s;
- if(int e = mlibc::sys_read(_fd, buffer, max_size, &s); e)
- return e;
- *actual_size = s;
- return 0;
-}
-
-int fd_file::io_write(const char *buffer, size_t max_size, size_t *actual_size) {
- ssize_t s;
- if(int e = mlibc::sys_write(_fd, buffer, max_size, &s); e)
- return e;
- *actual_size = s;
- return 0;
-}
-
-int fd_file::io_seek(off_t offset, int whence, off_t *new_offset) {
- if(int e = mlibc::sys_seek(_fd, offset, whence, new_offset); e)
- return e;
- return 0;
-}
-
-int fd_file::parse_modestring(const char *mode) {
- // Consume the first char; this must be 'r', 'w' or 'a'.
- int flags = 0;
- bool has_plus = strchr(mode, '+');
- if(*mode == 'r') {
- if(has_plus) {
- flags = O_RDWR;
- }else{
- flags = O_RDONLY;
- }
- }else if(*mode == 'w') {
- if(has_plus) {
- flags = O_RDWR;
- }else{
- flags = O_WRONLY;
- }
- flags |= O_CREAT | O_TRUNC;
- }else if(*mode == 'a') {
- if(has_plus) {
- flags = O_APPEND | O_RDWR;
- }else{
- flags = O_APPEND | O_WRONLY;
- }
- flags |= O_CREAT;
- }else{
- mlibc::infoLogger() << "Illegal fopen() mode '" << *mode << "'" << frg::endlog;
- }
- mode += 1;
-
- // Consume additional flags.
- while(*mode) {
- if(*mode == '+') {
- mode++; // This is already handled above.
- }else if(*mode == 'b') {
- mode++; // mlibc assumes that there is no distinction between text and binary.
- }else if(*mode == 'e') {
- flags |= O_CLOEXEC;
- mode++;
- }else{
- mlibc::infoLogger() << "Illegal fopen() flag '" << mode << "'" << frg::endlog;
- mode++;
- }
- }
-
- return flags;
-}
-
-} // namespace mlibc
-
-namespace {
- mlibc::fd_file stdin_file{0};
- mlibc::fd_file stdout_file{1};
- mlibc::fd_file stderr_file{2, nullptr, true};
-
- struct stdio_guard {
- stdio_guard() { }
-
- ~stdio_guard() {
- // Only flush the files but do not close them.
- for(auto it : mlibc::global_file_list()) {
- if(int e = it->flush(); e)
- mlibc::infoLogger() << "mlibc warning: Failed to flush file before exit()"
- << frg::endlog;
- }
- }
- } global_stdio_guard;
-}
-
-FILE *stderr = &stderr_file;
-FILE *stdin = &stdin_file;
-FILE *stdout = &stdout_file;
-
-int fileno_unlocked(FILE *file_base) {
- auto file = static_cast<mlibc::fd_file *>(file_base);
- return file->fd();
-}
-
-int fileno(FILE *file_base) {
- auto file = static_cast<mlibc::fd_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- return fileno_unlocked(file_base);
-}
-
-FILE *fopen(const char *path, const char *mode) {
- int flags = mlibc::fd_file::parse_modestring(mode);
-
- int fd;
- if(int e = mlibc::sys_open(path, flags, 0666, &fd); e) {
- errno = e;
- return nullptr;
- }
-
- return frg::construct<mlibc::fd_file>(getAllocator(), fd,
- mlibc::file_dispose_cb<mlibc::fd_file>);
-}
-
-int fclose(FILE *file_base) {
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- int e = 0;
- if(file->flush())
- e = EOF;
- if(file->close())
- e = EOF;
- file->dispose();
- return e;
-}
-
-int fseek(FILE *file_base, long offset, int whence) {
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- if(int e = file->seek(offset, whence); e) {
- errno = e;
- return -1;
- }
- return 0;
-}
-
-long ftell(FILE *file_base) {
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- off_t current_offset;
- if(int e = file->tell(&current_offset); e) {
- errno = e;
- return -1;
- }
- return current_offset;
-}
-
-int fflush_unlocked(FILE *file_base) {
- if(file_base == NULL) {
- // Only flush the files but do not close them.
- for(auto it : mlibc::global_file_list()) {
- if(int e = it->flush(); e)
- mlibc::infoLogger() << "mlibc warning: Failed to flush file"
- << frg::endlog;
- }
- return 0;
- }
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- if(file->flush())
- return EOF;
- return 0;
-}
-int fflush(FILE *file_base) {
- if(file_base == NULL) {
- // Only flush the files but do not close them.
- for(auto it : mlibc::global_file_list()) {
- frg::unique_lock lock(it->_lock);
- if(int e = it->flush(); e)
- mlibc::infoLogger() << "mlibc warning: Failed to flush file"
- << frg::endlog;
- }
- return 0;
- }
-
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- if (file->flush())
- return EOF;
- return 0;
-}
-
-int setvbuf(FILE *file_base, char *, int mode, size_t) {
- // TODO: We could also honor the buffer, but for now use just set the mode.
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- if(mode == _IONBF) {
- if(int e = file->update_bufmode(mlibc::buffer_mode::no_buffer); e) {
- errno = e;
- return -1;
- }
- }else if(mode == _IOLBF) {
- if(int e = file->update_bufmode(mlibc::buffer_mode::line_buffer); e) {
- errno = e;
- return -1;
- }
- }else if(mode == _IOFBF) {
- if(int e = file->update_bufmode(mlibc::buffer_mode::full_buffer); e) {
- errno = e;
- return -1;
- }
- }else{
- errno = EINVAL;
- return -1;
- }
-
- return 0;
-}
-
-void rewind(FILE *file_base) {
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- file->seek(0, SEEK_SET);
- file_base->__status_bits &= ~(__MLIBC_EOF_BIT | __MLIBC_ERROR_BIT);
-}
-
-int ungetc(int c, FILE *file_base) {
- if (c == EOF)
- return EOF;
-
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- return file->unget(c);
-}
-
-#if __MLIBC_GLIBC_OPTION
-void __fpurge(FILE *file_base) {
- auto file = static_cast<mlibc::abstract_file *>(file_base);
- frg::unique_lock lock(file->_lock);
- file->purge();
-}
-#endif
-