1247411Sjhb/*-
2283927Sjhb * Copyright (c) 2013 Hudson River Trading LLC
3247411Sjhb * Written by: John H. Baldwin <jhb@FreeBSD.org>
4247411Sjhb * All rights reserved.
5247411Sjhb *
6247411Sjhb * Redistribution and use in source and binary forms, with or without
7247411Sjhb * modification, are permitted provided that the following conditions
8247411Sjhb * are met:
9247411Sjhb * 1. Redistributions of source code must retain the above copyright
10247411Sjhb *    notice, this list of conditions and the following disclaimer.
11247411Sjhb * 2. Redistributions in binary form must reproduce the above copyright
12247411Sjhb *    notice, this list of conditions and the following disclaimer in the
13247411Sjhb *    documentation and/or other materials provided with the distribution.
14247411Sjhb *
15247411Sjhb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16247411Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17247411Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18247411Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19247411Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20247411Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21247411Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22247411Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23247411Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24247411Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25247411Sjhb * SUCH DAMAGE.
26247411Sjhb */
27247411Sjhb
28247411Sjhb#include <sys/cdefs.h>
29247411Sjhb__FBSDID("$FreeBSD$");
30247411Sjhb
31247411Sjhb#include "namespace.h"
32247411Sjhb#include <assert.h>
33247411Sjhb#include <errno.h>
34292150Sngie#include <limits.h>
35292140Sngie#ifdef DEBUG
36292150Sngie#include <stdint.h>
37292140Sngie#endif
38247411Sjhb#include <stdio.h>
39247411Sjhb#include <stdlib.h>
40247411Sjhb#include <string.h>
41247411Sjhb#include <wchar.h>
42247411Sjhb#include "un-namespace.h"
43247411Sjhb
44247411Sjhb/* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
45247411Sjhb#define	FPOS_MAX	OFF_MAX
46247411Sjhb
47247411Sjhbstruct wmemstream {
48247411Sjhb	wchar_t **bufp;
49247411Sjhb	size_t *sizep;
50247411Sjhb	ssize_t len;
51247411Sjhb	fpos_t offset;
52247411Sjhb	mbstate_t mbstate;
53247411Sjhb};
54247411Sjhb
55247411Sjhbstatic int
56247411Sjhbwmemstream_grow(struct wmemstream *ms, fpos_t newoff)
57247411Sjhb{
58247411Sjhb	wchar_t *buf;
59247411Sjhb	ssize_t newsize;
60247411Sjhb
61247411Sjhb	if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t))
62247411Sjhb		newsize = SSIZE_MAX / sizeof(wchar_t) - 1;
63247411Sjhb	else
64247411Sjhb		newsize = newoff;
65247411Sjhb	if (newsize > ms->len) {
66247411Sjhb		buf = realloc(*ms->bufp, (newsize + 1) * sizeof(wchar_t));
67247411Sjhb		if (buf != NULL) {
68247411Sjhb#ifdef DEBUG
69247411Sjhb			fprintf(stderr, "WMS: %p growing from %zd to %zd\n",
70247411Sjhb			    ms, ms->len, newsize);
71247411Sjhb#endif
72247411Sjhb			wmemset(buf + ms->len + 1, 0, newsize - ms->len);
73247411Sjhb			*ms->bufp = buf;
74247411Sjhb			ms->len = newsize;
75247411Sjhb			return (1);
76247411Sjhb		}
77247411Sjhb		return (0);
78247411Sjhb	}
79247411Sjhb	return (1);
80247411Sjhb}
81247411Sjhb
82247411Sjhbstatic void
83247411Sjhbwmemstream_update(struct wmemstream *ms)
84247411Sjhb{
85247411Sjhb
86247411Sjhb	assert(ms->len >= 0 && ms->offset >= 0);
87247411Sjhb	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
88247411Sjhb}
89247411Sjhb
90247411Sjhb/*
91247411Sjhb * Based on a starting multibyte state and an input buffer, determine
92247411Sjhb * how many wchar_t's would be output.  This doesn't use mbsnrtowcs()
93247411Sjhb * so that it can handle embedded null characters.
94247411Sjhb */
95247411Sjhbstatic size_t
96247411Sjhbwbuflen(const mbstate_t *state, const char *buf, int len)
97247411Sjhb{
98247411Sjhb	mbstate_t lenstate;
99247411Sjhb	size_t charlen, count;
100247411Sjhb
101247411Sjhb	count = 0;
102247411Sjhb	lenstate = *state;
103247411Sjhb	while (len > 0) {
104247411Sjhb		charlen = mbrlen(buf, len, &lenstate);
105247411Sjhb		if (charlen == (size_t)-1)
106247411Sjhb			return (-1);
107247411Sjhb		if (charlen == (size_t)-2)
108247411Sjhb			break;
109247411Sjhb		if (charlen == 0)
110247411Sjhb			/* XXX: Not sure how else to handle this. */
111247411Sjhb			charlen = 1;
112247411Sjhb		len -= charlen;
113247411Sjhb		buf += charlen;
114247411Sjhb		count++;
115247411Sjhb	}
116247411Sjhb	return (count);
117247411Sjhb}
118247411Sjhb
119247411Sjhbstatic int
120247411Sjhbwmemstream_write(void *cookie, const char *buf, int len)
121247411Sjhb{
122247411Sjhb	struct wmemstream *ms;
123247411Sjhb	ssize_t consumed, wlen;
124247411Sjhb	size_t charlen;
125247411Sjhb
126247411Sjhb	ms = cookie;
127247411Sjhb	wlen = wbuflen(&ms->mbstate, buf, len);
128247411Sjhb	if (wlen < 0) {
129247411Sjhb		errno = EILSEQ;
130247411Sjhb		return (-1);
131247411Sjhb	}
132247411Sjhb	if (!wmemstream_grow(ms, ms->offset + wlen))
133247411Sjhb		return (-1);
134247411Sjhb
135247411Sjhb	/*
136247411Sjhb	 * This copies characters one at a time rather than using
137247411Sjhb	 * mbsnrtowcs() so it can properly handle embedded null
138247411Sjhb	 * characters.
139247411Sjhb	 */
140247411Sjhb	consumed = 0;
141247411Sjhb	while (len > 0 && ms->offset < ms->len) {
142247411Sjhb		charlen = mbrtowc(*ms->bufp + ms->offset, buf, len,
143247411Sjhb		    &ms->mbstate);
144247411Sjhb		if (charlen == (size_t)-1) {
145247411Sjhb			if (consumed == 0) {
146247411Sjhb				errno = EILSEQ;
147247411Sjhb				return (-1);
148247411Sjhb			}
149247411Sjhb			/* Treat it as a successful short write. */
150247411Sjhb			break;
151247411Sjhb		}
152247411Sjhb		if (charlen == 0)
153247411Sjhb			/* XXX: Not sure how else to handle this. */
154247411Sjhb			charlen = 1;
155247411Sjhb		if (charlen == (size_t)-2) {
156247411Sjhb			consumed += len;
157247411Sjhb			len = 0;
158247411Sjhb		} else {
159247411Sjhb			consumed += charlen;
160247411Sjhb			buf += charlen;
161247411Sjhb			len -= charlen;
162247411Sjhb			ms->offset++;
163247411Sjhb		}
164247411Sjhb	}
165247411Sjhb	wmemstream_update(ms);
166247411Sjhb#ifdef DEBUG
167247411Sjhb	fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed);
168247411Sjhb#endif
169247411Sjhb	return (consumed);
170247411Sjhb}
171247411Sjhb
172247411Sjhbstatic fpos_t
173247411Sjhbwmemstream_seek(void *cookie, fpos_t pos, int whence)
174247411Sjhb{
175247411Sjhb	struct wmemstream *ms;
176247411Sjhb	fpos_t old;
177247411Sjhb
178247411Sjhb	ms = cookie;
179247411Sjhb	old = ms->offset;
180247411Sjhb	switch (whence) {
181247411Sjhb	case SEEK_SET:
182247411Sjhb		/* _fseeko() checks for negative offsets. */
183247411Sjhb		assert(pos >= 0);
184247411Sjhb		ms->offset = pos;
185247411Sjhb		break;
186247411Sjhb	case SEEK_CUR:
187247411Sjhb		/* This is only called by _ftello(). */
188247411Sjhb		assert(pos == 0);
189247411Sjhb		break;
190247411Sjhb	case SEEK_END:
191247411Sjhb		if (pos < 0) {
192247411Sjhb			if (pos + ms->len < 0) {
193247411Sjhb#ifdef DEBUG
194247411Sjhb				fprintf(stderr,
195247411Sjhb				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
196247411Sjhb				    (intmax_t)pos, ms->len);
197247411Sjhb#endif
198247411Sjhb				errno = EINVAL;
199247411Sjhb				return (-1);
200247411Sjhb			}
201247411Sjhb		} else {
202247411Sjhb			if (FPOS_MAX - ms->len < pos) {
203247411Sjhb#ifdef DEBUG
204247411Sjhb				fprintf(stderr,
205247411Sjhb				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
206247411Sjhb				    (intmax_t)pos, ms->len);
207247411Sjhb#endif
208247411Sjhb				errno = EOVERFLOW;
209247411Sjhb				return (-1);
210247411Sjhb			}
211247411Sjhb		}
212247411Sjhb		ms->offset = ms->len + pos;
213247411Sjhb		break;
214247411Sjhb	}
215247411Sjhb	/* Reset the multibyte state if a seek changes the position. */
216247411Sjhb	if (ms->offset != old)
217247411Sjhb		memset(&ms->mbstate, 0, sizeof(ms->mbstate));
218247411Sjhb	wmemstream_update(ms);
219247411Sjhb#ifdef DEBUG
220247411Sjhb	fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms,
221247411Sjhb	    (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset);
222247411Sjhb#endif
223247411Sjhb	return (ms->offset);
224247411Sjhb}
225247411Sjhb
226247411Sjhbstatic int
227247411Sjhbwmemstream_close(void *cookie)
228247411Sjhb{
229247411Sjhb
230247411Sjhb	free(cookie);
231247411Sjhb	return (0);
232247411Sjhb}
233247411Sjhb
234247411SjhbFILE *
235247411Sjhbopen_wmemstream(wchar_t **bufp, size_t *sizep)
236247411Sjhb{
237247411Sjhb	struct wmemstream *ms;
238247411Sjhb	int save_errno;
239247411Sjhb	FILE *fp;
240247411Sjhb
241247411Sjhb	if (bufp == NULL || sizep == NULL) {
242247411Sjhb		errno = EINVAL;
243247411Sjhb		return (NULL);
244247411Sjhb	}
245247411Sjhb	*bufp = calloc(1, sizeof(wchar_t));
246247411Sjhb	if (*bufp == NULL)
247247411Sjhb		return (NULL);
248247411Sjhb	ms = malloc(sizeof(*ms));
249247411Sjhb	if (ms == NULL) {
250247411Sjhb		save_errno = errno;
251247411Sjhb		free(*bufp);
252247411Sjhb		*bufp = NULL;
253247411Sjhb		errno = save_errno;
254247411Sjhb		return (NULL);
255247411Sjhb	}
256247411Sjhb	ms->bufp = bufp;
257247411Sjhb	ms->sizep = sizep;
258247411Sjhb	ms->len = 0;
259247411Sjhb	ms->offset = 0;
260247411Sjhb	memset(&ms->mbstate, 0, sizeof(mbstate_t));
261247411Sjhb	wmemstream_update(ms);
262247411Sjhb	fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek,
263247411Sjhb	    wmemstream_close);
264247411Sjhb	if (fp == NULL) {
265247411Sjhb		save_errno = errno;
266247411Sjhb		free(ms);
267247411Sjhb		free(*bufp);
268247411Sjhb		*bufp = NULL;
269247411Sjhb		errno = save_errno;
270247411Sjhb		return (NULL);
271247411Sjhb	}
272247411Sjhb	fwide(fp, 1);
273247411Sjhb	return (fp);
274247411Sjhb}
275