xref: /netbsd-src/lib/libc/stdio/open_wmemstream.c (revision 89f106fbc2bd696766675c676fb46391b59a2da3)
1*89f106fbSchristos /*	$NetBSD: open_wmemstream.c,v 1.2 2024/01/23 15:32:54 christos Exp $	*/
2dfe69df4Schristos 
3dfe69df4Schristos /*-
4dfe69df4Schristos  * Copyright (c) 2013 Advanced Computing Technologies LLC
5dfe69df4Schristos  * Written by: John H. Baldwin <jhb@FreeBSD.org>
6dfe69df4Schristos  * All rights reserved.
7dfe69df4Schristos  *
8dfe69df4Schristos  * Redistribution and use in source and binary forms, with or without
9dfe69df4Schristos  * modification, are permitted provided that the following conditions
10dfe69df4Schristos  * are met:
11dfe69df4Schristos  * 1. Redistributions of source code must retain the above copyright
12dfe69df4Schristos  *    notice, this list of conditions and the following disclaimer.
13dfe69df4Schristos  * 2. Redistributions in binary form must reproduce the above copyright
14dfe69df4Schristos  *    notice, this list of conditions and the following disclaimer in the
15dfe69df4Schristos  *    documentation and/or other materials provided with the distribution.
16dfe69df4Schristos  *
17dfe69df4Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18dfe69df4Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19dfe69df4Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20dfe69df4Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21dfe69df4Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22dfe69df4Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23dfe69df4Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24dfe69df4Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25dfe69df4Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26dfe69df4Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27dfe69df4Schristos  * SUCH DAMAGE.
28dfe69df4Schristos  */
29dfe69df4Schristos 
30dfe69df4Schristos #include <sys/cdefs.h>
31dfe69df4Schristos #if 0
32dfe69df4Schristos __FBSDID("$FreeBSD: head/lib/libc/stdio/open_wmemstream.c 247411 2013-02-27 19:50:46Z jhb $");
33dfe69df4Schristos #endif
34*89f106fbSchristos __RCSID("$NetBSD: open_wmemstream.c,v 1.2 2024/01/23 15:32:54 christos Exp $");
35dfe69df4Schristos 
36dfe69df4Schristos #include "namespace.h"
37dfe69df4Schristos #include <assert.h>
38dfe69df4Schristos #include <errno.h>
39dfe69df4Schristos #include <limits.h>
40dfe69df4Schristos #include <stdio.h>
41dfe69df4Schristos #include <stdlib.h>
42dfe69df4Schristos #include <string.h>
43dfe69df4Schristos #include <wchar.h>
44dfe69df4Schristos 
45dfe69df4Schristos #define	OFF_MAX LLONG_MAX
46dfe69df4Schristos 
47dfe69df4Schristos struct wmemstream {
48dfe69df4Schristos 	wchar_t **bufp;
49dfe69df4Schristos 	size_t *sizep;
50dfe69df4Schristos 	size_t len;
51dfe69df4Schristos 	size_t offset;
52dfe69df4Schristos 	mbstate_t mbstate;
53dfe69df4Schristos };
54dfe69df4Schristos 
55*89f106fbSchristos static __inline size_t
off_t_to_size_t(off_t off)56*89f106fbSchristos off_t_to_size_t(off_t off)
57*89f106fbSchristos {
58*89f106fbSchristos 	if (off < 0 || off >= SSIZE_MAX)
59*89f106fbSchristos 		return SSIZE_MAX - 1;
60*89f106fbSchristos 	return (size_t)off;
61*89f106fbSchristos }
62*89f106fbSchristos 
63dfe69df4Schristos static int
wmemstream_grow(struct wmemstream * ms,size_t newoff)64dfe69df4Schristos wmemstream_grow(struct wmemstream *ms, size_t newoff)
65dfe69df4Schristos {
66dfe69df4Schristos 	wchar_t *buf;
67dfe69df4Schristos 	size_t newsize;
68dfe69df4Schristos 
69dfe69df4Schristos 	if (newoff >= (off_t)(SSIZE_MAX / sizeof(wchar_t)))
70dfe69df4Schristos 		newsize = SSIZE_MAX / sizeof(wchar_t) - 1;
71dfe69df4Schristos 	else
72dfe69df4Schristos 		newsize = newoff;
73dfe69df4Schristos 	if (newsize > ms->len) {
74dfe69df4Schristos 		buf = realloc(*ms->bufp, (newsize + 1) * sizeof(wchar_t));
75dfe69df4Schristos 		if (buf != NULL) {
76dfe69df4Schristos #ifdef DEBUG
77dfe69df4Schristos 			fprintf(stderr, "WMS: %p growing from %zu to %zu\n",
78dfe69df4Schristos 			    ms, ms->len, newsize);
79dfe69df4Schristos #endif
80dfe69df4Schristos 			wmemset(buf + ms->len + 1, 0, newsize - ms->len);
81dfe69df4Schristos 			*ms->bufp = buf;
82dfe69df4Schristos 			ms->len = newsize;
83dfe69df4Schristos 			return (1);
84dfe69df4Schristos 		}
85dfe69df4Schristos 		return (0);
86dfe69df4Schristos 	}
87dfe69df4Schristos 	return (1);
88dfe69df4Schristos }
89dfe69df4Schristos 
90dfe69df4Schristos static void
wmemstream_update(struct wmemstream * ms)91dfe69df4Schristos wmemstream_update(struct wmemstream *ms)
92dfe69df4Schristos {
93dfe69df4Schristos 
94dfe69df4Schristos 	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
95dfe69df4Schristos }
96dfe69df4Schristos 
97dfe69df4Schristos /*
98dfe69df4Schristos  * Based on a starting multibyte state and an input buffer, determine
99dfe69df4Schristos  * how many wchar_t's would be output.  This doesn't use mbsnrtowcs()
100dfe69df4Schristos  * so that it can handle embedded null characters.
101dfe69df4Schristos  */
102dfe69df4Schristos static ssize_t
wbuflen(const mbstate_t * state,const char * buf,size_t len)103dfe69df4Schristos wbuflen(const mbstate_t *state, const char *buf, size_t len)
104dfe69df4Schristos {
105dfe69df4Schristos 	mbstate_t lenstate;
106dfe69df4Schristos 	size_t charlen, count;
107dfe69df4Schristos 
108dfe69df4Schristos 	count = 0;
109dfe69df4Schristos 	lenstate = *state;
110dfe69df4Schristos 	while (len > 0) {
111dfe69df4Schristos 		charlen = mbrlen(buf, len, &lenstate);
112dfe69df4Schristos 		if (charlen == (size_t)-1)
113dfe69df4Schristos 			return (-1);
114dfe69df4Schristos 		if (charlen == (size_t)-2)
115dfe69df4Schristos 			break;
116dfe69df4Schristos 		if (charlen == 0)
117dfe69df4Schristos 			/* XXX: Not sure how else to handle this. */
118dfe69df4Schristos 			charlen = 1;
119dfe69df4Schristos 		len -= charlen;
120dfe69df4Schristos 		buf += charlen;
121dfe69df4Schristos 		count++;
122dfe69df4Schristos 	}
123dfe69df4Schristos 	return (count);
124dfe69df4Schristos }
125dfe69df4Schristos 
126dfe69df4Schristos static ssize_t
wmemstream_write(void * cookie,const void * buf,size_t len)127dfe69df4Schristos wmemstream_write(void *cookie, const void *buf, size_t len)
128dfe69df4Schristos {
129dfe69df4Schristos 	struct wmemstream *ms;
130dfe69df4Schristos 	ssize_t consumed, wlen;
131dfe69df4Schristos 	size_t charlen;
132dfe69df4Schristos 
133dfe69df4Schristos 	ms = cookie;
134dfe69df4Schristos 	wlen = wbuflen(&ms->mbstate, buf, len);
135dfe69df4Schristos 	if (wlen < 0) {
136dfe69df4Schristos 		errno = EILSEQ;
137dfe69df4Schristos 		return (-1);
138dfe69df4Schristos 	}
139dfe69df4Schristos 	if (!wmemstream_grow(ms, ms->offset + wlen))
140dfe69df4Schristos 		return (-1);
141dfe69df4Schristos 
142dfe69df4Schristos 	/*
143dfe69df4Schristos 	 * This copies characters one at a time rather than using
144dfe69df4Schristos 	 * mbsnrtowcs() so it can properly handle embedded null
145dfe69df4Schristos 	 * characters.
146dfe69df4Schristos 	 */
147dfe69df4Schristos 	consumed = 0;
148dfe69df4Schristos 	while (len > 0 && ms->offset < ms->len) {
149dfe69df4Schristos 		charlen = mbrtowc(*ms->bufp + ms->offset, buf, len,
150dfe69df4Schristos 		    &ms->mbstate);
151dfe69df4Schristos 		if (charlen == (size_t)-1) {
152dfe69df4Schristos 			if (consumed == 0) {
153dfe69df4Schristos 				errno = EILSEQ;
154dfe69df4Schristos 				return (-1);
155dfe69df4Schristos 			}
156dfe69df4Schristos 			/* Treat it as a successful short write. */
157dfe69df4Schristos 			break;
158dfe69df4Schristos 		}
159dfe69df4Schristos 		if (charlen == 0)
160dfe69df4Schristos 			/* XXX: Not sure how else to handle this. */
161dfe69df4Schristos 			charlen = 1;
162dfe69df4Schristos 		if (charlen == (size_t)-2) {
163dfe69df4Schristos 			consumed += len;
164dfe69df4Schristos 			len = 0;
165dfe69df4Schristos 		} else {
166dfe69df4Schristos 			consumed += charlen;
167dfe69df4Schristos 			buf = (const char *)buf + charlen;
168dfe69df4Schristos 			len -= charlen;
169dfe69df4Schristos 			ms->offset++;
170dfe69df4Schristos 		}
171dfe69df4Schristos 	}
172dfe69df4Schristos 	wmemstream_update(ms);
173dfe69df4Schristos #ifdef DEBUG
174dfe69df4Schristos 	fprintf(stderr, "WMS: write(%p, %zu) = %zd\n", ms, len, consumed);
175dfe69df4Schristos #endif
176dfe69df4Schristos 	return (consumed);
177dfe69df4Schristos }
178dfe69df4Schristos 
179dfe69df4Schristos static off_t
wmemstream_seek(void * cookie,off_t pos,int whence)180dfe69df4Schristos wmemstream_seek(void *cookie, off_t pos, int whence)
181dfe69df4Schristos {
182dfe69df4Schristos 	struct wmemstream *ms;
183dfe69df4Schristos 	size_t old;
184dfe69df4Schristos 
185dfe69df4Schristos 	ms = cookie;
186dfe69df4Schristos 	old = ms->offset;
187dfe69df4Schristos 	switch (whence) {
188dfe69df4Schristos 	case SEEK_SET:
189dfe69df4Schristos 		/* _fseeko() checks for negative offsets. */
190dfe69df4Schristos 		assert(pos >= 0);
191*89f106fbSchristos 		ms->offset = off_t_to_size_t(pos);
192dfe69df4Schristos 		break;
193dfe69df4Schristos 	case SEEK_CUR:
194dfe69df4Schristos 		/* This is only called by _ftello(). */
195dfe69df4Schristos 		assert(pos == 0);
196dfe69df4Schristos 		break;
197dfe69df4Schristos 	case SEEK_END:
198dfe69df4Schristos 		if (pos < 0) {
199dfe69df4Schristos 			if (pos + (ssize_t)ms->len < 0) {
200dfe69df4Schristos #ifdef DEBUG
201dfe69df4Schristos 				fprintf(stderr,
202dfe69df4Schristos 				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
203dfe69df4Schristos 				    (intmax_t)pos, ms->len);
204dfe69df4Schristos #endif
205dfe69df4Schristos 				errno = EINVAL;
206dfe69df4Schristos 				return (-1);
207dfe69df4Schristos 			}
208dfe69df4Schristos 		} else {
209dfe69df4Schristos 			if (OFF_MAX - ms->len < (size_t)pos) {
210dfe69df4Schristos #ifdef DEBUG
211dfe69df4Schristos 				fprintf(stderr,
212dfe69df4Schristos 				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
213dfe69df4Schristos 				    (intmax_t)pos, ms->len);
214dfe69df4Schristos #endif
215dfe69df4Schristos 				errno = EOVERFLOW;
216dfe69df4Schristos 				return (-1);
217dfe69df4Schristos 			}
218dfe69df4Schristos 		}
219*89f106fbSchristos 		ms->offset = off_t_to_size_t(ms->len + pos);
220dfe69df4Schristos 		break;
221dfe69df4Schristos 	}
222dfe69df4Schristos 	/* Reset the multibyte state if a seek changes the position. */
223dfe69df4Schristos 	if (ms->offset != old)
224dfe69df4Schristos 		memset(&ms->mbstate, 0, sizeof(ms->mbstate));
225dfe69df4Schristos 	wmemstream_update(ms);
226dfe69df4Schristos #ifdef DEBUG
227dfe69df4Schristos 	fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms,
228dfe69df4Schristos 	    (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset);
229dfe69df4Schristos #endif
230dfe69df4Schristos 	return (ms->offset);
231dfe69df4Schristos }
232dfe69df4Schristos 
233dfe69df4Schristos static int
wmemstream_close(void * cookie)234dfe69df4Schristos wmemstream_close(void *cookie)
235dfe69df4Schristos {
236dfe69df4Schristos 
237dfe69df4Schristos 	free(cookie);
238dfe69df4Schristos 	return (0);
239dfe69df4Schristos }
240dfe69df4Schristos 
241dfe69df4Schristos FILE *
open_wmemstream(wchar_t ** bufp,size_t * sizep)242dfe69df4Schristos open_wmemstream(wchar_t **bufp, size_t *sizep)
243dfe69df4Schristos {
244dfe69df4Schristos 	struct wmemstream *ms;
245dfe69df4Schristos 	int save_errno;
246dfe69df4Schristos 	FILE *fp;
247dfe69df4Schristos 
248dfe69df4Schristos 	if (bufp == NULL || sizep == NULL) {
249dfe69df4Schristos 		errno = EINVAL;
250dfe69df4Schristos 		return (NULL);
251dfe69df4Schristos 	}
252dfe69df4Schristos 	*bufp = calloc(1, sizeof(wchar_t));
253dfe69df4Schristos 	if (*bufp == NULL)
254dfe69df4Schristos 		return (NULL);
255dfe69df4Schristos 	ms = malloc(sizeof(*ms));
256dfe69df4Schristos 	if (ms == NULL) {
257dfe69df4Schristos 		save_errno = errno;
258dfe69df4Schristos 		free(*bufp);
259dfe69df4Schristos 		*bufp = NULL;
260dfe69df4Schristos 		errno = save_errno;
261dfe69df4Schristos 		return (NULL);
262dfe69df4Schristos 	}
263dfe69df4Schristos 	ms->bufp = bufp;
264dfe69df4Schristos 	ms->sizep = sizep;
265dfe69df4Schristos 	ms->len = 0;
266dfe69df4Schristos 	ms->offset = 0;
267dfe69df4Schristos 	memset(&ms->mbstate, 0, sizeof(mbstate_t));
268dfe69df4Schristos 	wmemstream_update(ms);
269dfe69df4Schristos 	fp = funopen2(ms, NULL, wmemstream_write, wmemstream_seek,
270dfe69df4Schristos 	    NULL, wmemstream_close);
271dfe69df4Schristos 	if (fp == NULL) {
272dfe69df4Schristos 		save_errno = errno;
273dfe69df4Schristos 		free(ms);
274dfe69df4Schristos 		free(*bufp);
275dfe69df4Schristos 		*bufp = NULL;
276dfe69df4Schristos 		errno = save_errno;
277dfe69df4Schristos 		return (NULL);
278dfe69df4Schristos 	}
279dfe69df4Schristos 	fwide(fp, 1);
280dfe69df4Schristos 	return (fp);
281dfe69df4Schristos }
282