From 8879344780c31a23f285f0e9d2a4c72359edac51 Mon Sep 17 00:00:00 2001 From: Geert Custers Date: Sat, 8 May 2021 13:58:20 +0200 Subject: [PATCH 1/2] options/posix: implement and test open_memstream() --- options/posix/generic/posix-file-io.cpp | 79 +++++++++++++++++++ options/posix/generic/posix_stdio.cpp | 9 +-- options/posix/include/mlibc/posix-file-io.hpp | 34 ++++++++ options/posix/meson.build | 1 + tests/meson.build | 3 +- tests/posix/open_memstream.c | 63 +++++++++++++++ 6 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 options/posix/generic/posix-file-io.cpp create mode 100644 options/posix/include/mlibc/posix-file-io.hpp create mode 100644 tests/posix/open_memstream.c diff --git a/options/posix/generic/posix-file-io.cpp b/options/posix/generic/posix-file-io.cpp new file mode 100644 index 00000000..2d035ef9 --- /dev/null +++ b/options/posix/generic/posix-file-io.cpp @@ -0,0 +1,79 @@ +#include +#include + +#include + +namespace mlibc { + +mem_file::mem_file(char **ptr, size_t *sizeloc, void (*do_dispose)(abstract_file *)) +: abstract_file{do_dispose}, _bufloc{ptr}, _sizeloc{sizeloc}, _buf{getAllocator()} { } + +int mem_file::close() { + _update_ptrs(); + _buf.detach(); + return 0; +} + +int mem_file::determine_type(stream_type *type) { + *type = stream_type::file_like; + return 0; +} + +int mem_file::determine_bufmode(buffer_mode *mode) { + *mode = buffer_mode::no_buffer; + return 0; +} + +int mem_file::io_read(char *buffer, size_t max_size, size_t *actual_size) { + return EINVAL; +} + +int mem_file::io_write(const char *buffer, size_t max_size, size_t *actual_size) { + if (_pos + max_size >= _buf.size()) { + _buf.resize(_pos + max_size + 1, '\0'); + _update_ptrs(); + } + + memcpy(_buf.data() + _pos, buffer, max_size); + _pos += max_size; + *actual_size = max_size; + return 0; +} + +int mem_file::io_seek(off_t offset, int whence, off_t *new_offset) { + switch (whence) { + case SEEK_SET: + _pos = offset; + if (_pos >= _buf.size()) { + _buf.resize(_pos + 1, '\0'); + _update_ptrs(); + } + *new_offset = _pos; + break; + case SEEK_CUR: + _pos += offset; + if (_pos >= _buf.size()) { + _buf.resize(_pos + 1, '\0'); + _update_ptrs(); + } + *new_offset = _pos; + break; + case SEEK_END: + _pos = _buf.size() ? _buf.size() - 1 + offset : _buf.size() + offset; + _buf.resize(_pos + 1, '\0'); + _update_ptrs(); + *new_offset = _pos; + break; + default: + __ensure(!"Unknown whence value passed!"); + __builtin_unreachable(); + } + return 0; +} + +void mem_file::_update_ptrs() { + *_bufloc = _buf.data(); + *_sizeloc = _buf.size() - 1; +} + +} // namespace mlibc diff --git a/options/posix/generic/posix_stdio.cpp b/options/posix/generic/posix_stdio.cpp index 0bb6d04b..72d60433 100644 --- a/options/posix/generic/posix_stdio.cpp +++ b/options/posix/generic/posix_stdio.cpp @@ -6,6 +6,7 @@ #include #include #include +#include FILE *fmemopen(void *__restrict, size_t, const char *__restrict) { __ensure(!"Not implemented"); @@ -22,11 +23,9 @@ FILE *popen(const char*, const char *) { __builtin_unreachable(); } -FILE *open_memstream(char **, size_t *) { - mlibc::infoLogger() << "\e[31mmlibc: open_memstream() always fails" - << "\e[39m" << frg::endlog; - errno = ENOMEM; - return nullptr; +FILE *open_memstream(char **buf, size_t *sizeloc) { + return frg::construct(getAllocator(), buf, sizeloc, + [] (mlibc::abstract_file *abstract) { frg::destruct(getAllocator(), abstract); }); } int fseeko(FILE *file_base, off_t offset, int whence) { diff --git a/options/posix/include/mlibc/posix-file-io.hpp b/options/posix/include/mlibc/posix-file-io.hpp new file mode 100644 index 00000000..dd38fa82 --- /dev/null +++ b/options/posix/include/mlibc/posix-file-io.hpp @@ -0,0 +1,34 @@ +#ifndef MLIBC_POSIX_FILE_IO_HPP +#define MLIBC_POSIX_FILE_IO_HPP + +#include +#include +#include + +namespace mlibc { + +struct mem_file : abstract_file { + mem_file(char **ptr, size_t *sizeloc, void (*do_dispose)(abstract_file *) = nullptr); + + int close() override; +protected: + int determine_type(stream_type *type) override; + int determine_bufmode(buffer_mode *mode) override; + + int io_read(char *buffer, size_t max_size, size_t *actual_size) override; + int io_write(const char *buffer, size_t max_size, size_t *actual_size) override; + int io_seek(off_t offset, int whence, off_t *new_offset) override; +private: + void _update_ptrs(); + // Where to write back buffer and size on flush and close. + char **_bufloc; + size_t *_sizeloc; + + // Actual buffer. + frg::vector _buf; + size_t _pos; +}; + +} // namespace mlibc + +#endif // MLIBC_POSIX_FILE_IO_HPP diff --git a/options/posix/meson.build b/options/posix/meson.build index cfa7de53..fea9685d 100644 --- a/options/posix/meson.build +++ b/options/posix/meson.build @@ -15,6 +15,7 @@ libc_sources += files( 'generic/netdb-stubs.cpp', 'generic/net-if-stubs.cpp', 'generic/posix_ctype.cpp', + 'generic/posix-file-io.cpp', 'generic/posix_locale.cpp', 'generic/posix_signal.cpp', 'generic/posix_stdio.cpp', diff --git a/tests/meson.build b/tests/meson.build index b260ffb0..3bd636ce 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -24,7 +24,8 @@ posix_test_cases = [ 'posix_spawn', 'index', 'rindex', - 'search' + 'search', + 'open_memstream' ] glibc_test_cases = [ diff --git a/tests/posix/open_memstream.c b/tests/posix/open_memstream.c new file mode 100644 index 00000000..5868f5e5 --- /dev/null +++ b/tests/posix/open_memstream.c @@ -0,0 +1,63 @@ +#include +#include + +#define WRITE_NO 1024 + +int main() { + size_t size; + char *buf; + + FILE *fp = open_memstream(&buf, &size); + assert(fp); + + char c = 'A'; + for (int i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Flush the file to update the pointers. + assert(!fflush(fp)); + + assert(size == WRITE_NO); + for (int i = 0; i < size; i++) + assert(buf[i] == c); + + // Check if the stream maintains a null-bye at the end. + assert(buf[size] == '\0'); + + // Stream should be expanded, size should be == 2*WRITE_NO. + assert(!fseek(fp, WRITE_NO, SEEK_END)); + assert(!fflush(fp)); + assert(size == 2*WRITE_NO); + assert(buf[size] == '\0'); + + // Check if it's filled with zero's. + for (int i = WRITE_NO; i < size; i++) + assert(buf[i] == '\0'); + + // Go back and overwrite the 0's with 'B'. + assert(!fseek(fp, -WRITE_NO, SEEK_CUR)); + c = 'B'; + for (int i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Check if that happened. + assert(size == 2*WRITE_NO); + for (int i = WRITE_NO; i < size; i++) + assert(buf[i] == c); + assert(buf[size] == '\0'); + + // Go to the front and write 'B'. + assert(!fseek(fp, 0, SEEK_SET)); + for (int i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Check if that happened. + assert(size == 2*WRITE_NO); + for (int i = 0; i < size; i++) + assert(buf[i] == c); + assert(buf[size] == '\0'); + + // Close the file, we have tested everything. + assert(!fclose(fp)); + return 0; +} From 76f8739f20778019b478e208832f92ca8e1b63bd Mon Sep 17 00:00:00 2001 From: Geert Custers Date: Sat, 8 May 2021 14:00:20 +0200 Subject: [PATCH 2/2] options/ansi: make file list eternal --- options/ansi/generic/file-io.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/options/ansi/generic/file-io.cpp b/options/ansi/generic/file-io.cpp index 9e2d663a..8e1f3254 100644 --- a/options/ansi/generic/file-io.cpp +++ b/options/ansi/generic/file-io.cpp @@ -38,7 +38,10 @@ namespace { constexpr bool globallyDisableBuffering = false; // List of files that will be flushed before exit(). - file_list global_file_list; + file_list &global_file_list() { + static frg::eternal list; + return list.get(); + }; } // For pipe-like streams (seek returns ESPIPE), we need to make sure @@ -60,7 +63,7 @@ abstract_file::abstract_file(void (*do_dispose)(abstract_file *)) __io_mode = 0; __status_bits = 0; - global_file_list.push_back(this); + global_file_list().push_back(this); } abstract_file::~abstract_file() { @@ -71,8 +74,8 @@ abstract_file::~abstract_file() { if(__buffer_ptr) getAllocator().free(__buffer_ptr); - auto it = global_file_list.iterator_to(this); - global_file_list.erase(it); + auto it = global_file_list().iterator_to(this); + global_file_list().erase(it); } void abstract_file::dispose() { @@ -475,7 +478,7 @@ namespace { ~stdio_guard() { // Only flush the files but do not close them. - for(auto it : mlibc::global_file_list) { + 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; @@ -589,7 +592,7 @@ long ftell(FILE *file_base) { 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) { + for(auto it : mlibc::global_file_list()) { if(int e = it->flush(); e) mlibc::infoLogger() << "mlibc warning: Failed to flush file" << frg::endlog; @@ -604,7 +607,7 @@ int fflush_unlocked(FILE *file_base) { 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) { + 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"