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 <michallenc@seznam.cz>
This commit is contained in:
Michal Lenc 2023-10-23 10:35:54 +02:00 committed by Xiang Xiao
parent 01500f8b20
commit 65ae8a545c
4 changed files with 293 additions and 2 deletions

View File

@ -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;

View File

@ -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})

View File

@ -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

View File

@ -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 <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#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;
}