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