Home | History | Annotate | Line # | Download | only in stdio
      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 (at) 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
     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
     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
     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
    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
    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
    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
    234 wmemstream_close(void *cookie)
    235 {
    236 
    237 	free(cookie);
    238 	return (0);
    239 }
    240 
    241 FILE *
    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