diff --git a/libc/Makefile b/libc/Makefile index cf8a9620..f20d6828 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -91,6 +91,7 @@ stdio/flbf.o \ stdio/flbf_unlocked.o \ stdio/flockfile.o \ stdio/flushlbf.o \ +stdio/fmemopen.o \ stdio/fnewfile.o \ stdio/fparsemode.o \ stdio/fpending.o \ diff --git a/libc/include/stdio.h b/libc/include/stdio.h index 4c83b532..86fdbd26 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -119,6 +119,7 @@ int fgetpos(FILE* __restrict stream, fpos_t* __restrict pos); char* fgets(char* __restrict s, int n, FILE* __restrict stream); char* fgets_unlocked(char* __restrict, int, FILE* __restrict); void flockfile(FILE* file); +FILE* fmemopen(void* __restrict, size_t, const char* __restrict); FILE* fopen(const char* __restrict filename, const char* __restrict mode); int fprintf(FILE* __restrict stream, const char* __restrict format, ...) __attribute__((__format__ (printf, 2, 3))); @@ -210,7 +211,6 @@ int vsscanf(const char* __restrict s, const char* __restrict format, __gnuc_va_l /* TODO: These are not implemented in sortix libc yet. */ #if 0 char* ctermid(char* s); -FILE *fmemopen(void* __restrict buf, size_t size, const char* __restrict mode); FILE* open_memstream(char** bufp, size_t* sizep); #endif diff --git a/libc/stdio/fmemopen.cpp b/libc/stdio/fmemopen.cpp new file mode 100644 index 00000000..066c7015 --- /dev/null +++ b/libc/stdio/fmemopen.cpp @@ -0,0 +1,224 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + libc/stdio/fmemopen.cpp + Open a memory buffer stream. + +*******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +static const int FMEMOPEN_ALLOCATED = 1 << 0; + +struct fmemopen_state +{ + unsigned char* buffer; + size_t buffer_true_size; + size_t buffer_size; + size_t buffer_offset; + size_t buffer_used; + int flags; + int mode; +}; + +static ssize_t fmemopen_read(void* state_ptr, void* dst, size_t count) +{ + struct fmemopen_state* state = (struct fmemopen_state*) state_ptr; + + if ( !(state->mode & FILE_MODE_READ) ) + return errno = EBADF, -1; + + if ( (size_t) SSIZE_MAX < count ) + count = SSIZE_MAX; + + if ( state->buffer_used < state->buffer_offset ) + return 0; + + size_t available = state->buffer_used - state->buffer_offset; + if ( available < count ) + count = available; + + memcpy(dst, state->buffer + state->buffer_offset, count); + state->buffer_offset += count; + + return count; +} + +static ssize_t fmemopen_write(void* state_ptr, const void* src, size_t count) +{ + struct fmemopen_state* state = (struct fmemopen_state*) state_ptr; + + if ( !(state->mode & FILE_MODE_WRITE) ) + return errno = EBADF, -1; + + if ( (size_t) SSIZE_MAX < count ) + count = SSIZE_MAX; + + if ( state->mode & FILE_MODE_APPEND ) + state->buffer_offset = state->buffer_used; + + if ( state->buffer_used < state->buffer_offset ) + { + size_t distance = state->buffer_offset - state->buffer_used; + memset(state->buffer + state->buffer_used, 0, distance); + state->buffer_used = state->buffer_offset; + } + + size_t available = state->buffer_size - state->buffer_offset; + if ( available < count ) + count = available; + + memcpy(state->buffer + state->buffer_offset, src, count); + state->buffer_offset += count; + + if ( state->buffer_used < state->buffer_offset ) + { + state->buffer_used = state->buffer_offset; + if ( !(state->mode & FILE_MODE_BINARY) ) + { + if ( state->buffer_used < state->buffer_true_size ) + state->buffer[state->buffer_used] = '\0'; + } + } + + return count; +} + +static off_t fmemopen_seek(void* state_ptr, off_t offset, int whence) +{ + struct fmemopen_state* state = (struct fmemopen_state*) state_ptr; + off_t base; + switch ( whence ) + { + case SEEK_SET: base = 0; break; + case SEEK_CUR: base = (off_t) state->buffer_offset; break; + case SEEK_END: base = (off_t) state->buffer_used; break; + default: return errno = EINVAL, -1; + } + if ( offset < -base || base - (off_t) state->buffer_size < offset ) + return errno = EOVERFLOW, -1; + return (off_t) (state->buffer_offset = (size_t) (base + offset)); +} + +static int fmemopen_close(void* state_ptr) +{ + struct fmemopen_state* state = (struct fmemopen_state*) state_ptr; + + if ( state->flags & FMEMOPEN_ALLOCATED ) + free(state->buffer); + + return 0; +} + +extern "C" +FILE* fmemopen(void* restrict buffer_ptr, + size_t buffer_size, + const char* restrict mode_str) +{ + int flags = 0; + int mode = fparsemode(mode_str); + if ( mode < 0 ) + return (FILE*) NULL; + + if ( mode & ~(FILE_MODE_READ | FILE_MODE_WRITE | FILE_MODE_CREATE | + FILE_MODE_BINARY | FILE_MODE_TRUNCATE | FILE_MODE_APPEND) ) + return errno = EINVAL, (FILE*) NULL; + +#if OFF_MAX < SIZE_MAX + if ( (uintmax_t) OFF_MAX < (uintmax_t) buffer_size ) + return errno = EOVERFLOW, (FILE*) NULL; +#endif + + if ( !(mode & FILE_MODE_BINARY) && + (mode & FILE_MODE_WRITE) && + !(mode & FILE_MODE_READ) && + buffer_size == 0 ) + return errno = EINVAL, (FILE*) NULL; + + void* allocated_buffer = NULL; + if ( !buffer_ptr ) + { + if ( !(buffer_ptr = allocated_buffer = calloc(buffer_size, 1)) ) + return (FILE*) NULL; + flags |= FMEMOPEN_ALLOCATED; + } + + struct fmemopen_state* state = + (struct fmemopen_state*) malloc(sizeof(struct fmemopen_state)); + if ( !state ) + return free(allocated_buffer), (FILE*) NULL; + + FILE* fp = fnewfile(); + if ( !fp ) + return free(state), free(allocated_buffer), (FILE*) NULL; + + memset(state, 0, sizeof(*state)); + state->flags = flags; + state->mode = mode; + state->buffer = (unsigned char*) buffer_ptr; + state->buffer_size = buffer_size; + state->buffer_true_size = buffer_size; + + if ( !(mode & FILE_MODE_BINARY) && + (mode & FILE_MODE_WRITE) && + !(mode & FILE_MODE_READ) ) + state->buffer_size = state->buffer_true_size - 1; + + if ( (mode & FILE_MODE_APPEND) && !(mode & FILE_MODE_BINARY) ) + { + state->buffer_offset = strnlen((const char*) state->buffer, state->buffer_size); + state->buffer_used = state->buffer_offset; + } + else if ( mode & FILE_MODE_TRUNCATE ) + { + state->buffer_offset = 0; + state->buffer_used = 0; + } + else + { + state->buffer_offset = 0; + state->buffer_used = state->buffer_size; + } + + if ( !(state->mode & FILE_MODE_BINARY) && (mode & FILE_MODE_WRITE) ) + { + if ( state->buffer_used < state->buffer_true_size ) + state->buffer[state->buffer_used] = '\0'; + } + + if ( mode & FILE_MODE_READ ) + fp->flags |= _FILE_READABLE; + if ( mode & FILE_MODE_WRITE ) + fp->flags |= _FILE_WRITABLE; + + fp->user = state; + fp->read_func = fmemopen_read; + fp->write_func = fmemopen_write; + fp->seek_func = fmemopen_seek; + fp->close_func = fmemopen_close; + + return fp; +}