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