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
47 struct wmemstream {
48 wchar_t **bufp;
49 size_t *sizep;
50 size_t len;
51 size_t offset;
52 mbstate_t mbstate;
53 };
54
55 static __inline size_t
off_t_to_size_t(off_t off)56 off_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
63 static int
wmemstream_grow(struct wmemstream * ms,size_t newoff)64 wmemstream_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
90 static void
wmemstream_update(struct wmemstream * ms)91 wmemstream_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 */
102 static ssize_t
wbuflen(const mbstate_t * state,const char * buf,size_t len)103 wbuflen(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
126 static ssize_t
wmemstream_write(void * cookie,const void * buf,size_t len)127 wmemstream_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
179 static off_t
wmemstream_seek(void * cookie,off_t pos,int whence)180 wmemstream_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
233 static int
wmemstream_close(void * cookie)234 wmemstream_close(void *cookie)
235 {
236
237 free(cookie);
238 return (0);
239 }
240
241 FILE *
open_wmemstream(wchar_t ** bufp,size_t * sizep)242 open_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