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