open_wmemstream.c revision 330897
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: stable/11/lib/libc/stdio/open_wmemstream.c 330897 2018-03-14 03:19:51Z eadler $");
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 wmemstream {
50	wchar_t **bufp;
51	size_t *sizep;
52	ssize_t len;
53	fpos_t offset;
54	mbstate_t mbstate;
55};
56
57static int
58wmemstream_grow(struct wmemstream *ms, fpos_t newoff)
59{
60	wchar_t *buf;
61	ssize_t newsize;
62
63	if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t))
64		newsize = SSIZE_MAX / sizeof(wchar_t) - 1;
65	else
66		newsize = newoff;
67	if (newsize > ms->len) {
68		buf = reallocarray(*ms->bufp, newsize + 1, sizeof(wchar_t));
69		if (buf != NULL) {
70#ifdef DEBUG
71			fprintf(stderr, "WMS: %p growing from %zd to %zd\n",
72			    ms, ms->len, newsize);
73#endif
74			wmemset(buf + ms->len + 1, 0, newsize - ms->len);
75			*ms->bufp = buf;
76			ms->len = newsize;
77			return (1);
78		}
79		return (0);
80	}
81	return (1);
82}
83
84static void
85wmemstream_update(struct wmemstream *ms)
86{
87
88	assert(ms->len >= 0 && ms->offset >= 0);
89	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
90}
91
92/*
93 * Based on a starting multibyte state and an input buffer, determine
94 * how many wchar_t's would be output.  This doesn't use mbsnrtowcs()
95 * so that it can handle embedded null characters.
96 */
97static size_t
98wbuflen(const mbstate_t *state, const char *buf, int len)
99{
100	mbstate_t lenstate;
101	size_t charlen, count;
102
103	count = 0;
104	lenstate = *state;
105	while (len > 0) {
106		charlen = mbrlen(buf, len, &lenstate);
107		if (charlen == (size_t)-1)
108			return (-1);
109		if (charlen == (size_t)-2)
110			break;
111		if (charlen == 0)
112			/* XXX: Not sure how else to handle this. */
113			charlen = 1;
114		len -= charlen;
115		buf += charlen;
116		count++;
117	}
118	return (count);
119}
120
121static int
122wmemstream_write(void *cookie, const char *buf, int len)
123{
124	struct wmemstream *ms;
125	ssize_t consumed, wlen;
126	size_t charlen;
127
128	ms = cookie;
129	wlen = wbuflen(&ms->mbstate, buf, len);
130	if (wlen < 0) {
131		errno = EILSEQ;
132		return (-1);
133	}
134	if (!wmemstream_grow(ms, ms->offset + wlen))
135		return (-1);
136
137	/*
138	 * This copies characters one at a time rather than using
139	 * mbsnrtowcs() so it can properly handle embedded null
140	 * characters.
141	 */
142	consumed = 0;
143	while (len > 0 && ms->offset < ms->len) {
144		charlen = mbrtowc(*ms->bufp + ms->offset, buf, len,
145		    &ms->mbstate);
146		if (charlen == (size_t)-1) {
147			if (consumed == 0) {
148				errno = EILSEQ;
149				return (-1);
150			}
151			/* Treat it as a successful short write. */
152			break;
153		}
154		if (charlen == 0)
155			/* XXX: Not sure how else to handle this. */
156			charlen = 1;
157		if (charlen == (size_t)-2) {
158			consumed += len;
159			len = 0;
160		} else {
161			consumed += charlen;
162			buf += charlen;
163			len -= charlen;
164			ms->offset++;
165		}
166	}
167	wmemstream_update(ms);
168#ifdef DEBUG
169	fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed);
170#endif
171	return (consumed);
172}
173
174static fpos_t
175wmemstream_seek(void *cookie, fpos_t pos, int whence)
176{
177	struct wmemstream *ms;
178	fpos_t old;
179
180	ms = cookie;
181	old = ms->offset;
182	switch (whence) {
183	case SEEK_SET:
184		/* _fseeko() checks for negative offsets. */
185		assert(pos >= 0);
186		ms->offset = pos;
187		break;
188	case SEEK_CUR:
189		/* This is only called by _ftello(). */
190		assert(pos == 0);
191		break;
192	case SEEK_END:
193		if (pos < 0) {
194			if (pos + ms->len < 0) {
195#ifdef DEBUG
196				fprintf(stderr,
197				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
198				    (intmax_t)pos, ms->len);
199#endif
200				errno = EINVAL;
201				return (-1);
202			}
203		} else {
204			if (FPOS_MAX - ms->len < pos) {
205#ifdef DEBUG
206				fprintf(stderr,
207				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
208				    (intmax_t)pos, ms->len);
209#endif
210				errno = EOVERFLOW;
211				return (-1);
212			}
213		}
214		ms->offset = ms->len + pos;
215		break;
216	}
217	/* Reset the multibyte state if a seek changes the position. */
218	if (ms->offset != old)
219		memset(&ms->mbstate, 0, sizeof(ms->mbstate));
220	wmemstream_update(ms);
221#ifdef DEBUG
222	fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms,
223	    (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset);
224#endif
225	return (ms->offset);
226}
227
228static int
229wmemstream_close(void *cookie)
230{
231
232	free(cookie);
233	return (0);
234}
235
236FILE *
237open_wmemstream(wchar_t **bufp, size_t *sizep)
238{
239	struct wmemstream *ms;
240	int save_errno;
241	FILE *fp;
242
243	if (bufp == NULL || sizep == NULL) {
244		errno = EINVAL;
245		return (NULL);
246	}
247	*bufp = calloc(1, sizeof(wchar_t));
248	if (*bufp == NULL)
249		return (NULL);
250	ms = malloc(sizeof(*ms));
251	if (ms == NULL) {
252		save_errno = errno;
253		free(*bufp);
254		*bufp = NULL;
255		errno = save_errno;
256		return (NULL);
257	}
258	ms->bufp = bufp;
259	ms->sizep = sizep;
260	ms->len = 0;
261	ms->offset = 0;
262	memset(&ms->mbstate, 0, sizeof(mbstate_t));
263	wmemstream_update(ms);
264	fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek,
265	    wmemstream_close);
266	if (fp == NULL) {
267		save_errno = errno;
268		free(ms);
269		free(*bufp);
270		*bufp = NULL;
271		errno = save_errno;
272		return (NULL);
273	}
274	fwide(fp, 1);
275	return (fp);
276}
277