/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (C) 2013 Pietro Cerutti <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <fcntl.h>28#include <stdbool.h>29#include <stdio.h>30#include <stdlib.h>31#include <string.h>32#include <errno.h>33#include "local.h"3435struct fmemopen_cookie36{37char *buf; /* pointer to the memory region */38bool own; /* did we allocate the buffer ourselves? */39char bin; /* is this a binary buffer? */40size_t size; /* buffer length in bytes */41size_t len; /* data length in bytes */42size_t off; /* current offset into the buffer */43};4445static int fmemopen_read(void *cookie, char *buf, int nbytes);46static int fmemopen_write(void *cookie, const char *buf, int nbytes);47static fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence);48static int fmemopen_close(void *cookie);4950FILE *51fmemopen(void * __restrict buf, size_t size, const char * __restrict mode)52{53struct fmemopen_cookie *ck;54FILE *f;55int flags, rc;5657/*58* POSIX says we shall return EINVAL if size is 0.59*/60if (size == 0) {61errno = EINVAL;62return (NULL);63}6465/*66* Retrieve the flags as used by open(2) from the mode argument, and67* validate them.68*/69rc = __sflags(mode, &flags);70if (rc == 0) {71errno = EINVAL;72return (NULL);73}7475/*76* An automatically allocated buffer is only allowed in read-write mode.77*/78if ((flags & O_ACCMODE) != O_RDWR && buf == NULL) {79errno = EINVAL;80return (NULL);81}8283ck = malloc(sizeof(struct fmemopen_cookie));84if (ck == NULL) {85return (NULL);86}8788ck->off = 0;89ck->size = size;9091/* Check whether we have to allocate the buffer ourselves. */92ck->own = ((ck->buf = buf) == NULL);93if (ck->own) {94ck->buf = malloc(size);95if (ck->buf == NULL) {96free(ck);97return (NULL);98}99}100101/*102* POSIX distinguishes between w+ and r+, in that w+ is supposed to103* truncate the buffer.104*/105if (ck->own || mode[0] == 'w') {106ck->buf[0] = '\0';107}108109/* Check for binary mode. */110ck->bin = strchr(mode, 'b') != NULL;111112/*113* The size of the current buffer contents is set depending on the114* mode:115*116* for append (text-mode), the position of the first NULL byte, or the117* size of the buffer if none is found118*119* for append (binary-mode), the size of the buffer120*121* for read, the size of the buffer122*123* for write, 0124*/125switch (mode[0]) {126case 'a':127ck->off = ck->len = strnlen(ck->buf, ck->size);128break;129case 'r':130ck->len = size;131break;132case 'w':133ck->len = 0;134break;135}136137/* Disable read in O_WRONLY mode, and write in O_RDONLY mode. */138f = funopen(ck,139(flags & O_ACCMODE) == O_WRONLY ? NULL : fmemopen_read,140(flags & O_ACCMODE) == O_RDONLY ? NULL : fmemopen_write,141fmemopen_seek, fmemopen_close);142143if (f == NULL) {144if (ck->own)145free(ck->buf);146free(ck);147return (NULL);148}149150if (mode[0] == 'a')151f->_flags |= __SAPP;152153/*154* Turn off buffering, so a write past the end of the buffer155* correctly returns a short object count.156*/157setvbuf(f, NULL, _IONBF, 0);158159return (f);160}161162static int163fmemopen_read(void *cookie, char *buf, int nbytes)164{165struct fmemopen_cookie *ck = cookie;166167if (nbytes > ck->len - ck->off)168nbytes = ck->len - ck->off;169170if (nbytes == 0)171return (0);172173memcpy(buf, ck->buf + ck->off, nbytes);174175ck->off += nbytes;176177return (nbytes);178}179180static int181fmemopen_write(void *cookie, const char *buf, int nbytes)182{183struct fmemopen_cookie *ck = cookie;184185if (nbytes > ck->size - ck->off)186nbytes = ck->size - ck->off;187188if (nbytes == 0)189return (0);190191memcpy(ck->buf + ck->off, buf, nbytes);192193ck->off += nbytes;194195if (ck->off > ck->len)196ck->len = ck->off;197198/*199* We append a NULL byte if all these conditions are met:200* - the buffer is not binary201* - the buffer is not full202* - the data just written doesn't already end with a NULL byte203*/204if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')205ck->buf[ck->off] = '\0';206207return (nbytes);208}209210static fpos_t211fmemopen_seek(void *cookie, fpos_t offset, int whence)212{213struct fmemopen_cookie *ck = cookie;214215216switch (whence) {217case SEEK_SET:218if (offset > ck->size) {219errno = EINVAL;220return (-1);221}222ck->off = offset;223break;224225case SEEK_CUR:226if (ck->off + offset > ck->size) {227errno = EINVAL;228return (-1);229}230ck->off += offset;231break;232233case SEEK_END:234if (offset > 0 || -offset > ck->len) {235errno = EINVAL;236return (-1);237}238ck->off = ck->len + offset;239break;240241default:242errno = EINVAL;243return (-1);244}245246return (ck->off);247}248249static int250fmemopen_close(void *cookie)251{252struct fmemopen_cookie *ck = cookie;253254if (ck->own)255free(ck->buf);256257free(ck);258259return (0);260}261262263