root/lib/libc/stdio/open_memstream.c
/*      $OpenBSD: open_memstream.c,v 1.10 2023/07/11 12:14:16 claudio Exp $     */

/*
 * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "local.h"

#define MINIMUM(a, b)   (((a) < (b)) ? (a) : (b))

struct state {
        char             *string;       /* actual stream */
        char            **pbuf;         /* point to the stream */
        size_t           *psize;        /* point to min(pos, len) */
        size_t            pos;          /* current position */
        size_t            size;         /* number of allocated char */
        size_t            len;          /* length of the data */
};

static int
memstream_write(void *v, const char *b, int l)
{
        struct state    *st = v;
        char            *p;
        size_t           i, end;

        end = (st->pos + l);

        if (end >= st->size) {
                /* 1.6 is (very) close to the golden ratio. */
                size_t  sz = st->size * 8 / 5;

                if (sz < end + 1)
                        sz = end + 1;
                p = recallocarray(st->string, st->size, sz, 1);
                if (!p)
                        return (-1);
                *st->pbuf = st->string = p;
                st->size = sz;
        }

        for (i = 0; i < l; i++)
                st->string[st->pos + i] = b[i];
        st->pos += l;

        if (st->pos > st->len) {
                st->len = st->pos;
                st->string[st->len] = '\0';
        }

        *st->psize = st->pos;

        return (i);
}

static fpos_t
memstream_seek(void *v, fpos_t off, int whence)
{
        struct state    *st = v;
        size_t           base = 0;

        switch (whence) {
        case SEEK_SET:
                break;
        case SEEK_CUR:
                base = st->pos;
                break;
        case SEEK_END:
                base = st->len;
                break;
        }

        if ((off > 0 && off > SIZE_MAX - base) || (off < 0 && base < -off)) {
                errno = EOVERFLOW;
                return (-1);
        }

        st->pos = base + off;
        *st->psize = MINIMUM(st->pos, st->len);

        return (st->pos);
}

static int
memstream_close(void *v)
{
        struct state    *st = v;

        free(st);

        return (0);
}

FILE *
open_memstream(char **pbuf, size_t *psize)
{
        struct state    *st;
        FILE            *fp;

        if (pbuf == NULL || psize == NULL) {
                errno = EINVAL;
                return (NULL);
        }

        if ((st = malloc(sizeof(*st))) == NULL)
                return (NULL);

        if ((fp = __sfp()) == NULL) {
                free(st);
                return (NULL);
        }

        st->size = BUFSIZ;
        if ((st->string = calloc(1, st->size)) == NULL) {
                free(st);
                fp->_flags = 0;
                return (NULL);
        }

        st->pos = 0;
        st->len = 0;
        st->pbuf = pbuf;
        st->psize = psize;

        *pbuf = st->string;
        *psize = st->len;

        fp->_flags = __SWR;
        fp->_file = -1;
        fp->_cookie = st;
        fp->_read = NULL;
        fp->_write = memstream_write;
        fp->_seek = memstream_seek;
        fp->_close = memstream_close;
        _SET_ORIENTATION(fp, -1);

        return (fp);
}
DEF_WEAK(open_memstream);