1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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 "namespace.h"
31#include <assert.h>
32#include <errno.h>
33#include <limits.h>
34#ifdef DEBUG
35#include <stdint.h>
36#endif
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <wchar.h>
41#include "un-namespace.h"
42
43/* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
44#define	FPOS_MAX	OFF_MAX
45
46struct memstream {
47	char **bufp;
48	size_t *sizep;
49	ssize_t len;
50	fpos_t offset;
51};
52
53static int
54memstream_grow(struct memstream *ms, fpos_t newoff)
55{
56	char *buf;
57	ssize_t newsize;
58
59	if (newoff < 0 || newoff >= SSIZE_MAX)
60		newsize = SSIZE_MAX - 1;
61	else
62		newsize = newoff;
63	if (newsize > ms->len) {
64		buf = realloc(*ms->bufp, newsize + 1);
65		if (buf != NULL) {
66#ifdef DEBUG
67			fprintf(stderr, "MS: %p growing from %zd to %zd\n",
68			    ms, ms->len, newsize);
69#endif
70			memset(buf + ms->len + 1, 0, newsize - ms->len);
71			*ms->bufp = buf;
72			ms->len = newsize;
73			return (1);
74		}
75		return (0);
76	}
77	return (1);
78}
79
80static void
81memstream_update(struct memstream *ms)
82{
83
84	assert(ms->len >= 0 && ms->offset >= 0);
85	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
86}
87
88static int
89memstream_write(void *cookie, const char *buf, int len)
90{
91	struct memstream *ms;
92	ssize_t tocopy;
93
94	ms = cookie;
95	if (!memstream_grow(ms, ms->offset + len))
96		return (-1);
97	tocopy = ms->len - ms->offset;
98	if (len < tocopy)
99		tocopy = len;
100	memcpy(*ms->bufp + ms->offset, buf, tocopy);
101	ms->offset += tocopy;
102	memstream_update(ms);
103#ifdef DEBUG
104	fprintf(stderr, "MS: write(%p, %d) = %zd\n", ms, len, tocopy);
105#endif
106	return (tocopy);
107}
108
109static fpos_t
110memstream_seek(void *cookie, fpos_t pos, int whence)
111{
112	struct memstream *ms;
113#ifdef DEBUG
114	fpos_t old;
115#endif
116
117	ms = cookie;
118#ifdef DEBUG
119	old = ms->offset;
120#endif
121	switch (whence) {
122	case SEEK_SET:
123		/* _fseeko() checks for negative offsets. */
124		assert(pos >= 0);
125		ms->offset = pos;
126		break;
127	case SEEK_CUR:
128		/* This is only called by _ftello(). */
129		assert(pos == 0);
130		break;
131	case SEEK_END:
132		if (pos < 0) {
133			if (pos + ms->len < 0) {
134#ifdef DEBUG
135				fprintf(stderr,
136				    "MS: bad SEEK_END: pos %jd, len %zd\n",
137				    (intmax_t)pos, ms->len);
138#endif
139				errno = EINVAL;
140				return (-1);
141			}
142		} else {
143			if (FPOS_MAX - ms->len < pos) {
144#ifdef DEBUG
145				fprintf(stderr,
146				    "MS: bad SEEK_END: pos %jd, len %zd\n",
147				    (intmax_t)pos, ms->len);
148#endif
149				errno = EOVERFLOW;
150				return (-1);
151			}
152		}
153		ms->offset = ms->len + pos;
154		break;
155	}
156	memstream_update(ms);
157#ifdef DEBUG
158	fprintf(stderr, "MS: seek(%p, %jd, %d) %jd -> %jd\n", ms, (intmax_t)pos,
159	    whence, (intmax_t)old, (intmax_t)ms->offset);
160#endif
161	return (ms->offset);
162}
163
164static int
165memstream_close(void *cookie)
166{
167
168	free(cookie);
169	return (0);
170}
171
172FILE *
173open_memstream(char **bufp, size_t *sizep)
174{
175	struct memstream *ms;
176	int save_errno;
177	FILE *fp;
178
179	if (bufp == NULL || sizep == NULL) {
180		errno = EINVAL;
181		return (NULL);
182	}
183	*bufp = calloc(1, 1);
184	if (*bufp == NULL)
185		return (NULL);
186	ms = malloc(sizeof(*ms));
187	if (ms == NULL) {
188		save_errno = errno;
189		free(*bufp);
190		*bufp = NULL;
191		errno = save_errno;
192		return (NULL);
193	}
194	ms->bufp = bufp;
195	ms->sizep = sizep;
196	ms->len = 0;
197	ms->offset = 0;
198	memstream_update(ms);
199	fp = funopen(ms, NULL, memstream_write, memstream_seek,
200	    memstream_close);
201	if (fp == NULL) {
202		save_errno = errno;
203		free(ms);
204		free(*bufp);
205		*bufp = NULL;
206		errno = save_errno;
207		return (NULL);
208	}
209	fwide(fp, -1);
210	return (fp);
211}
212