1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2013 Hudson River Trading LLC
5 * Written by: John H. Baldwin <jhb@FreeBSD.org>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include "namespace.h"
34#include <assert.h>
35#include <errno.h>
36#include <limits.h>
37#ifdef DEBUG
38#include <stdint.h>
39#endif
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <wchar.h>
44#include "un-namespace.h"
45
46/* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
47#define	FPOS_MAX	OFF_MAX
48
49struct memstream {
50	char **bufp;
51	size_t *sizep;
52	ssize_t len;
53	fpos_t offset;
54};
55
56static int
57memstream_grow(struct memstream *ms, fpos_t newoff)
58{
59	char *buf;
60	ssize_t newsize;
61
62	if (newoff < 0 || newoff >= SSIZE_MAX)
63		newsize = SSIZE_MAX - 1;
64	else
65		newsize = newoff;
66	if (newsize > ms->len) {
67		buf = realloc(*ms->bufp, newsize + 1);
68		if (buf != NULL) {
69#ifdef DEBUG
70			fprintf(stderr, "MS: %p growing from %zd to %zd\n",
71			    ms, ms->len, newsize);
72#endif
73			memset(buf + ms->len + 1, 0, newsize - ms->len);
74			*ms->bufp = buf;
75			ms->len = newsize;
76			return (1);
77		}
78		return (0);
79	}
80	return (1);
81}
82
83static void
84memstream_update(struct memstream *ms)
85{
86
87	assert(ms->len >= 0 && ms->offset >= 0);
88	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
89}
90
91static int
92memstream_write(void *cookie, const char *buf, int len)
93{
94	struct memstream *ms;
95	ssize_t tocopy;
96
97	ms = cookie;
98	if (!memstream_grow(ms, ms->offset + len))
99		return (-1);
100	tocopy = ms->len - ms->offset;
101	if (len < tocopy)
102		tocopy = len;
103	memcpy(*ms->bufp + ms->offset, buf, tocopy);
104	ms->offset += tocopy;
105	memstream_update(ms);
106#ifdef DEBUG
107	fprintf(stderr, "MS: write(%p, %d) = %zd\n", ms, len, tocopy);
108#endif
109	return (tocopy);
110}
111
112static fpos_t
113memstream_seek(void *cookie, fpos_t pos, int whence)
114{
115	struct memstream *ms;
116#ifdef DEBUG
117	fpos_t old;
118#endif
119
120	ms = cookie;
121#ifdef DEBUG
122	old = ms->offset;
123#endif
124	switch (whence) {
125	case SEEK_SET:
126		/* _fseeko() checks for negative offsets. */
127		assert(pos >= 0);
128		ms->offset = pos;
129		break;
130	case SEEK_CUR:
131		/* This is only called by _ftello(). */
132		assert(pos == 0);
133		break;
134	case SEEK_END:
135		if (pos < 0) {
136			if (pos + ms->len < 0) {
137#ifdef DEBUG
138				fprintf(stderr,
139				    "MS: bad SEEK_END: pos %jd, len %zd\n",
140				    (intmax_t)pos, ms->len);
141#endif
142				errno = EINVAL;
143				return (-1);
144			}
145		} else {
146			if (FPOS_MAX - ms->len < pos) {
147#ifdef DEBUG
148				fprintf(stderr,
149				    "MS: bad SEEK_END: pos %jd, len %zd\n",
150				    (intmax_t)pos, ms->len);
151#endif
152				errno = EOVERFLOW;
153				return (-1);
154			}
155		}
156		ms->offset = ms->len + pos;
157		break;
158	}
159	memstream_update(ms);
160#ifdef DEBUG
161	fprintf(stderr, "MS: seek(%p, %jd, %d) %jd -> %jd\n", ms, (intmax_t)pos,
162	    whence, (intmax_t)old, (intmax_t)ms->offset);
163#endif
164	return (ms->offset);
165}
166
167static int
168memstream_close(void *cookie)
169{
170
171	free(cookie);
172	return (0);
173}
174
175FILE *
176open_memstream(char **bufp, size_t *sizep)
177{
178	struct memstream *ms;
179	int save_errno;
180	FILE *fp;
181
182	if (bufp == NULL || sizep == NULL) {
183		errno = EINVAL;
184		return (NULL);
185	}
186	*bufp = calloc(1, 1);
187	if (*bufp == NULL)
188		return (NULL);
189	ms = malloc(sizeof(*ms));
190	if (ms == NULL) {
191		save_errno = errno;
192		free(*bufp);
193		*bufp = NULL;
194		errno = save_errno;
195		return (NULL);
196	}
197	ms->bufp = bufp;
198	ms->sizep = sizep;
199	ms->len = 0;
200	ms->offset = 0;
201	memstream_update(ms);
202	fp = funopen(ms, NULL, memstream_write, memstream_seek,
203	    memstream_close);
204	if (fp == NULL) {
205		save_errno = errno;
206		free(ms);
207		free(*bufp);
208		*bufp = NULL;
209		errno = save_errno;
210		return (NULL);
211	}
212	fwide(fp, -1);
213	return (fp);
214}
215