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