1/*	$OpenBSD: open_memstream.c,v 1.6 2015/08/31 02:53:57 guenther Exp $	*/
2
3/*
4 * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <errno.h>
20#include <fcntl.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include "local.h"
26
27#define	MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
28
29struct state {
30	char		 *string;	/* actual stream */
31	char		**pbuf;		/* point to the stream */
32	size_t		 *psize;	/* point to min(pos, len) */
33	size_t		  pos;		/* current position */
34	size_t		  size;		/* number of allocated char */
35	size_t		  len;		/* length of the data */
36};
37
38static int
39memstream_write(void *v, const char *b, int l)
40{
41	struct state	*st = v;
42	char		*p;
43	size_t		 i, end;
44
45	end = (st->pos + l);
46
47	if (end >= st->size) {
48		/* 1.6 is (very) close to the golden ratio. */
49		size_t	sz = st->size * 8 / 5;
50
51		if (sz < end + 1)
52			sz = end + 1;
53		p = realloc(st->string, sz);
54		if (!p)
55			return (-1);
56		bzero(p + st->size, sz - st->size);
57		*st->pbuf = st->string = p;
58		st->size = sz;
59	}
60
61	for (i = 0; i < l; i++)
62		st->string[st->pos + i] = b[i];
63	st->pos += l;
64
65	if (st->pos > st->len) {
66		st->len = st->pos;
67		st->string[st->len] = '\0';
68	}
69
70	*st->psize = st->pos;
71
72	return (i);
73}
74
75static fpos_t
76memstream_seek(void *v, fpos_t off, int whence)
77{
78	struct state	*st = v;
79	ssize_t		 base = 0;
80
81	switch (whence) {
82	case SEEK_SET:
83		break;
84	case SEEK_CUR:
85		base = st->pos;
86		break;
87	case SEEK_END:
88		base = st->len;
89		break;
90	}
91
92	if (off > SIZE_MAX - base || off < -base) {
93		errno = EOVERFLOW;
94		return (-1);
95	}
96
97	st->pos = base + off;
98	*st->psize = MINIMUM(st->pos, st->len);
99
100	return (st->pos);
101}
102
103static int
104memstream_close(void *v)
105{
106	struct state	*st = v;
107
108	free(st);
109
110	return (0);
111}
112
113FILE *
114open_memstream(char **pbuf, size_t *psize)
115{
116	struct state	*st;
117	FILE		*fp;
118
119	if (pbuf == NULL || psize == NULL) {
120		errno = EINVAL;
121		return (NULL);
122	}
123
124	if ((st = malloc(sizeof(*st))) == NULL)
125		return (NULL);
126
127	if ((fp = __sfp()) == NULL) {
128		free(st);
129		return (NULL);
130	}
131
132	st->size = BUFSIZ;
133	if ((st->string = calloc(1, st->size)) == NULL) {
134		free(st);
135		fp->_flags = 0;
136		return (NULL);
137	}
138
139	*st->string = '\0';
140	st->pos = 0;
141	st->len = 0;
142	st->pbuf = pbuf;
143	st->psize = psize;
144
145	*pbuf = st->string;
146	*psize = st->len;
147
148	fp->_flags = __SWR;
149	fp->_file = -1;
150	fp->_cookie = st;
151	fp->_read = NULL;
152	fp->_write = memstream_write;
153	fp->_seek = memstream_seek;
154	fp->_close = memstream_close;
155	_SET_ORIENTATION(fp, -1);
156
157	return (fp);
158}
159DEF_WEAK(open_memstream);
160