From 65ae8a545cd30f754b2020456fd6f749dee5fd3f Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Mon, 23 Oct 2023 10:35:54 +0200 Subject: [PATCH] libc: add support for memory buffer stream with fmemopen() Add support for POSIX interface fmemopen(). This interface open a memory buffer as a stream and permits access to this buffer specified by mode. This allows I/O operations to be performed on the memory buffer. The implementation uses fopencookie() for custom stream operations and callbacks. Signed-off-by: Michal Lenc --- include/stdio.h | 4 + libs/libc/stdio/CMakeLists.txt | 3 +- libs/libc/stdio/Make.defs | 2 +- libs/libc/stdio/lib_fmemopen.c | 286 +++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 libs/libc/stdio/lib_fmemopen.c diff --git a/include/stdio.h b/include/stdio.h index b7a4c3dde4..cc7f10441c 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -249,6 +249,10 @@ int vdprintf(int fd, FAR const IPTR char *fmt, va_list ap) FAR FILE *fopencookie(FAR void *cookie, FAR const char *mode, cookie_io_functions_t io_funcs); +/* Memory buffer stream */ + +FAR FILE *fmemopen(FAR void *buf, size_t size, FAR const char *mode); + /* Operations on paths */ FAR FILE *tmpfile(void) fopen_like; diff --git a/libs/libc/stdio/CMakeLists.txt b/libs/libc/stdio/CMakeLists.txt index a3524352a9..4646e808c1 100644 --- a/libs/libc/stdio/CMakeLists.txt +++ b/libs/libc/stdio/CMakeLists.txt @@ -104,7 +104,8 @@ if(CONFIG_FILE_STREAM) lib_fputwc.c lib_putwc.c lib_fputws.c - lib_fopencookie.c) + lib_fopencookie.c + lib_fmemopen.c) endif() target_sources(c PRIVATE ${SRCS}) diff --git a/libs/libc/stdio/Make.defs b/libs/libc/stdio/Make.defs index c692bb1c25..ccf3418e95 100644 --- a/libs/libc/stdio/Make.defs +++ b/libs/libc/stdio/Make.defs @@ -48,7 +48,7 @@ CSRCS += lib_feof.c lib_ferror.c lib_rewind.c lib_clearerr.c CSRCS += lib_scanf.c lib_vscanf.c lib_fscanf.c lib_vfscanf.c lib_tmpfile.c CSRCS += lib_setbuf.c lib_setvbuf.c lib_libfilelock.c lib_libgetstreams.c CSRCS += lib_setbuffer.c lib_fputwc.c lib_putwc.c lib_fputws.c -CSRCS += lib_fopencookie.c +CSRCS += lib_fopencookie.c lib_fmemopen.c endif # Add the stdio directory to the build diff --git a/libs/libc/stdio/lib_fmemopen.c b/libs/libc/stdio/lib_fmemopen.c new file mode 100644 index 0000000000..9524899928 --- /dev/null +++ b/libs/libc/stdio/lib_fmemopen.c @@ -0,0 +1,286 @@ +/**************************************************************************** + * libs/libc/stdio/lib_fmemopen.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libc.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct fmemopen_cookie_s +{ + FAR char *buf; /* Memory buffer */ + off_t pos; /* Current position in the buffer */ + off_t end; /* End buffer position */ + size_t size; /* Buffer size */ + bool custom; /* True if custom buffer is used */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fmemopen_read + ****************************************************************************/ + +static ssize_t fmemopen_read(FAR void *c, FAR char *buf, size_t size) +{ + FAR struct fmemopen_cookie_s *fmemopen_cookie = + (FAR struct fmemopen_cookie_s *)c; + if (fmemopen_cookie->pos + size > fmemopen_cookie->end) + { + size = fmemopen_cookie->end - fmemopen_cookie->pos; + } + + memcpy(buf, fmemopen_cookie->buf + fmemopen_cookie->pos, size); + + fmemopen_cookie->pos += size; + return size; +} + +/**************************************************************************** + * Name: fmemopen_write + ****************************************************************************/ + +static ssize_t fmemopen_write(FAR void *c, FAR const char *buf, size_t size) +{ + FAR struct fmemopen_cookie_s *fmemopen_cookie = + (FAR struct fmemopen_cookie_s *)c; + if (size + fmemopen_cookie->pos > fmemopen_cookie->size) + { + size = fmemopen_cookie->size - fmemopen_cookie->pos; + } + + memcpy(fmemopen_cookie->buf + fmemopen_cookie->pos, buf, size); + + fmemopen_cookie->pos += size; + if (fmemopen_cookie->pos > fmemopen_cookie->end) + { + fmemopen_cookie->end = fmemopen_cookie->pos; + } + + /* POSIX states that NULL byte shall be written at the current position + * or end of the buffer. + */ + + if (fmemopen_cookie->pos < fmemopen_cookie->size && + fmemopen_cookie->buf[fmemopen_cookie->pos - 1] != '\0') + { + fmemopen_cookie->buf[fmemopen_cookie->pos] = '\0'; + } + + return size; +} + +/**************************************************************************** + * Name: fmemopen_seek + ****************************************************************************/ + +static off_t fmemopen_seek(FAR void *c, FAR off_t *offset, int whence) +{ + FAR struct fmemopen_cookie_s *fmemopen_cookie = + (FAR struct fmemopen_cookie_s *)c; + off_t new_offset; + + switch (whence) + { + case SEEK_SET: + new_offset = *offset; + break; + case SEEK_END: + new_offset = fmemopen_cookie->end + *offset; + break; + case SEEK_CUR: + new_offset = fmemopen_cookie->pos + *offset; + break; + default: + set_errno(ENOTSUP); + return -1; + } + + /* Seek to negative value or value larger than maximum size shall fail. */ + + if (new_offset < 0 || new_offset > fmemopen_cookie->end) + { + set_errno(EINVAL); + return -1; + } + + fmemopen_cookie->pos = new_offset; + *offset = new_offset; + return new_offset; +} + +/**************************************************************************** + * Name: fmemopen_close + ****************************************************************************/ + +static int fmemopen_close(FAR void *c) +{ + FAR struct fmemopen_cookie_s *fmemopen_cookie = + (FAR struct fmemopen_cookie_s *)c; + if (fmemopen_cookie->custom) + { + lib_free(fmemopen_cookie->buf); + } + + lib_free(fmemopen_cookie); + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fmemopen + ****************************************************************************/ + +FAR FILE *fmemopen(FAR void *buf, size_t size, FAR const char *mode) +{ + cookie_io_functions_t fmemopen_io; + FAR struct fmemopen_cookie_s *fmemopen_cookie; + FAR FILE *filep; + int oflags; + + fmemopen_cookie = lib_zalloc(sizeof(struct fmemopen_cookie_s)); + if (fmemopen_cookie == NULL) + { + set_errno(ENOMEM); + return NULL; + } + + oflags = lib_mode2oflags(mode); + + if (buf == NULL) + { + /* POSIX standard states: + * Because this feature is only useful when the stream is opened for + * updating (because there is no way to get a pointer to the buffer) + * the fmemopen() call may fail if the mode argument does not + * include a '+'. + */ + + if ((oflags & O_RDWR) != O_RDWR) + { + lib_free(fmemopen_cookie); + set_errno(EINVAL); + return NULL; + } + + /* Buf argument is NULL pointer and mode is correct. This buffer + * will be freed when stream is closed so we have to keep the + * information in fmemopen_cookie->custom. + */ + + fmemopen_cookie->custom = true; + fmemopen_cookie->buf = lib_zalloc(size); + if (fmemopen_cookie->buf == NULL) + { + lib_free(fmemopen_cookie); + set_errno(ENOMEM); + return NULL; + } + + /* If buf is a null pointer, the initial position shall always be set + * to the beginning of the buffer. + */ + + fmemopen_cookie->buf[0] = '\0'; + } + else + { + /* Buffer was already allocated by the user. */ + + fmemopen_cookie->custom = false; + fmemopen_cookie->buf = buf; + } + + fmemopen_cookie->size = size; + fmemopen_cookie->pos = 0; + + /* For modes w and w+ the initial size shall be zero. */ + + if ((oflags & O_TRUNC) != 0) + { + fmemopen_cookie->end = 0; + fmemopen_cookie->buf[0] = '\0'; + } + + /* For modes r and r+ the size shall be set to the value given + * by the size argument. + */ + + if ((oflags & O_RDWR) == O_RDOK) + { + fmemopen_cookie->end = size; + } + + /* For modes a and a+ the initial size shall be: + * - Zero, if buf is a null pointer + * - The position of the first null byte in the buffer, if one is foun + * - The value of the size argument, if buf is not a null pointer and + * no null byte is found + */ + + if ((oflags & O_APPEND) != 0) + { + fmemopen_cookie->pos = fmemopen_cookie->end = + strnlen(fmemopen_cookie->buf, fmemopen_cookie->size); + } + + /* Assign fmemopen callbacks. */ + + fmemopen_io.read = fmemopen_read; + fmemopen_io.write = fmemopen_write; + fmemopen_io.seek = fmemopen_seek; + fmemopen_io.close = fmemopen_close; + + /* Let fopencookie do the rest. */ + + filep = fopencookie(fmemopen_cookie, mode, fmemopen_io); + if (filep == NULL) + { + if (fmemopen_cookie->custom) + { + lib_free(fmemopen_cookie->buf); + } + + lib_free(fmemopen_cookie); + return NULL; + } + + return filep; +}