xref: /freebsd/lib/libc/stdio/open_wmemstream.c (revision 559a218c9b257775fb249b67945fe4a05b7a6b9f)
19240031aSJohn Baldwin /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3d915a14eSPedro F. Giffuni  *
4179fa75eSJohn Baldwin  * Copyright (c) 2013 Hudson River Trading LLC
59240031aSJohn Baldwin  * Written by: John H. Baldwin <jhb@FreeBSD.org>
69240031aSJohn Baldwin  * All rights reserved.
79240031aSJohn Baldwin  *
89240031aSJohn Baldwin  * Redistribution and use in source and binary forms, with or without
99240031aSJohn Baldwin  * modification, are permitted provided that the following conditions
109240031aSJohn Baldwin  * are met:
119240031aSJohn Baldwin  * 1. Redistributions of source code must retain the above copyright
129240031aSJohn Baldwin  *    notice, this list of conditions and the following disclaimer.
139240031aSJohn Baldwin  * 2. Redistributions in binary form must reproduce the above copyright
149240031aSJohn Baldwin  *    notice, this list of conditions and the following disclaimer in the
159240031aSJohn Baldwin  *    documentation and/or other materials provided with the distribution.
169240031aSJohn Baldwin  *
179240031aSJohn Baldwin  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
189240031aSJohn Baldwin  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
199240031aSJohn Baldwin  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
209240031aSJohn Baldwin  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
219240031aSJohn Baldwin  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
229240031aSJohn Baldwin  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
239240031aSJohn Baldwin  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
249240031aSJohn Baldwin  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
259240031aSJohn Baldwin  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
269240031aSJohn Baldwin  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
279240031aSJohn Baldwin  * SUCH DAMAGE.
289240031aSJohn Baldwin  */
299240031aSJohn Baldwin 
309240031aSJohn Baldwin #include "namespace.h"
319240031aSJohn Baldwin #include <assert.h>
329240031aSJohn Baldwin #include <errno.h>
339240031aSJohn Baldwin #include <limits.h>
34c6e61550SEnji Cooper #ifdef DEBUG
35c6e61550SEnji Cooper #include <stdint.h>
36c6e61550SEnji Cooper #endif
379240031aSJohn Baldwin #include <stdio.h>
389240031aSJohn Baldwin #include <stdlib.h>
399240031aSJohn Baldwin #include <string.h>
409240031aSJohn Baldwin #include <wchar.h>
419240031aSJohn Baldwin #include "un-namespace.h"
429240031aSJohn Baldwin 
439240031aSJohn Baldwin /* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
449240031aSJohn Baldwin #define	FPOS_MAX	OFF_MAX
459240031aSJohn Baldwin 
469240031aSJohn Baldwin struct wmemstream {
479240031aSJohn Baldwin 	wchar_t **bufp;
489240031aSJohn Baldwin 	size_t *sizep;
499240031aSJohn Baldwin 	ssize_t len;
509240031aSJohn Baldwin 	fpos_t offset;
519240031aSJohn Baldwin 	mbstate_t mbstate;
529240031aSJohn Baldwin };
539240031aSJohn Baldwin 
549240031aSJohn Baldwin static int
wmemstream_grow(struct wmemstream * ms,fpos_t newoff)559240031aSJohn Baldwin wmemstream_grow(struct wmemstream *ms, fpos_t newoff)
569240031aSJohn Baldwin {
579240031aSJohn Baldwin 	wchar_t *buf;
589240031aSJohn Baldwin 	ssize_t newsize;
599240031aSJohn Baldwin 
609240031aSJohn Baldwin 	if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t))
619240031aSJohn Baldwin 		newsize = SSIZE_MAX / sizeof(wchar_t) - 1;
629240031aSJohn Baldwin 	else
639240031aSJohn Baldwin 		newsize = newoff;
649240031aSJohn Baldwin 	if (newsize > ms->len) {
659f36610fSPedro F. Giffuni 		buf = reallocarray(*ms->bufp, newsize + 1, sizeof(wchar_t));
669240031aSJohn Baldwin 		if (buf != NULL) {
679240031aSJohn Baldwin #ifdef DEBUG
689240031aSJohn Baldwin 			fprintf(stderr, "WMS: %p growing from %zd to %zd\n",
699240031aSJohn Baldwin 			    ms, ms->len, newsize);
709240031aSJohn Baldwin #endif
719240031aSJohn Baldwin 			wmemset(buf + ms->len + 1, 0, newsize - ms->len);
729240031aSJohn Baldwin 			*ms->bufp = buf;
739240031aSJohn Baldwin 			ms->len = newsize;
749240031aSJohn Baldwin 			return (1);
759240031aSJohn Baldwin 		}
769240031aSJohn Baldwin 		return (0);
779240031aSJohn Baldwin 	}
789240031aSJohn Baldwin 	return (1);
799240031aSJohn Baldwin }
809240031aSJohn Baldwin 
819240031aSJohn Baldwin static void
wmemstream_update(struct wmemstream * ms)829240031aSJohn Baldwin wmemstream_update(struct wmemstream *ms)
839240031aSJohn Baldwin {
849240031aSJohn Baldwin 
859240031aSJohn Baldwin 	assert(ms->len >= 0 && ms->offset >= 0);
869240031aSJohn Baldwin 	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
879240031aSJohn Baldwin }
889240031aSJohn Baldwin 
899240031aSJohn Baldwin /*
909240031aSJohn Baldwin  * Based on a starting multibyte state and an input buffer, determine
919240031aSJohn Baldwin  * how many wchar_t's would be output.  This doesn't use mbsnrtowcs()
929240031aSJohn Baldwin  * so that it can handle embedded null characters.
939240031aSJohn Baldwin  */
949240031aSJohn Baldwin static size_t
wbuflen(const mbstate_t * state,const char * buf,int len)959240031aSJohn Baldwin wbuflen(const mbstate_t *state, const char *buf, int len)
969240031aSJohn Baldwin {
979240031aSJohn Baldwin 	mbstate_t lenstate;
989240031aSJohn Baldwin 	size_t charlen, count;
999240031aSJohn Baldwin 
1009240031aSJohn Baldwin 	count = 0;
1019240031aSJohn Baldwin 	lenstate = *state;
1029240031aSJohn Baldwin 	while (len > 0) {
1039240031aSJohn Baldwin 		charlen = mbrlen(buf, len, &lenstate);
1049240031aSJohn Baldwin 		if (charlen == (size_t)-1)
1059240031aSJohn Baldwin 			return (-1);
1069240031aSJohn Baldwin 		if (charlen == (size_t)-2)
1079240031aSJohn Baldwin 			break;
1089240031aSJohn Baldwin 		if (charlen == 0)
1099240031aSJohn Baldwin 			/* XXX: Not sure how else to handle this. */
1109240031aSJohn Baldwin 			charlen = 1;
1119240031aSJohn Baldwin 		len -= charlen;
1129240031aSJohn Baldwin 		buf += charlen;
1139240031aSJohn Baldwin 		count++;
1149240031aSJohn Baldwin 	}
1159240031aSJohn Baldwin 	return (count);
1169240031aSJohn Baldwin }
1179240031aSJohn Baldwin 
1189240031aSJohn Baldwin static int
wmemstream_write(void * cookie,const char * buf,int len)1199240031aSJohn Baldwin wmemstream_write(void *cookie, const char *buf, int len)
1209240031aSJohn Baldwin {
1219240031aSJohn Baldwin 	struct wmemstream *ms;
1229240031aSJohn Baldwin 	ssize_t consumed, wlen;
1239240031aSJohn Baldwin 	size_t charlen;
1249240031aSJohn Baldwin 
1259240031aSJohn Baldwin 	ms = cookie;
1269240031aSJohn Baldwin 	wlen = wbuflen(&ms->mbstate, buf, len);
1279240031aSJohn Baldwin 	if (wlen < 0) {
1289240031aSJohn Baldwin 		errno = EILSEQ;
1299240031aSJohn Baldwin 		return (-1);
1309240031aSJohn Baldwin 	}
1319240031aSJohn Baldwin 	if (!wmemstream_grow(ms, ms->offset + wlen))
1329240031aSJohn Baldwin 		return (-1);
1339240031aSJohn Baldwin 
1349240031aSJohn Baldwin 	/*
1359240031aSJohn Baldwin 	 * This copies characters one at a time rather than using
1369240031aSJohn Baldwin 	 * mbsnrtowcs() so it can properly handle embedded null
1379240031aSJohn Baldwin 	 * characters.
1389240031aSJohn Baldwin 	 */
1399240031aSJohn Baldwin 	consumed = 0;
1409240031aSJohn Baldwin 	while (len > 0 && ms->offset < ms->len) {
1419240031aSJohn Baldwin 		charlen = mbrtowc(*ms->bufp + ms->offset, buf, len,
1429240031aSJohn Baldwin 		    &ms->mbstate);
1439240031aSJohn Baldwin 		if (charlen == (size_t)-1) {
1449240031aSJohn Baldwin 			if (consumed == 0) {
1459240031aSJohn Baldwin 				errno = EILSEQ;
1469240031aSJohn Baldwin 				return (-1);
1479240031aSJohn Baldwin 			}
1489240031aSJohn Baldwin 			/* Treat it as a successful short write. */
1499240031aSJohn Baldwin 			break;
1509240031aSJohn Baldwin 		}
1519240031aSJohn Baldwin 		if (charlen == 0)
1529240031aSJohn Baldwin 			/* XXX: Not sure how else to handle this. */
1539240031aSJohn Baldwin 			charlen = 1;
1549240031aSJohn Baldwin 		if (charlen == (size_t)-2) {
1559240031aSJohn Baldwin 			consumed += len;
1569240031aSJohn Baldwin 			len = 0;
1579240031aSJohn Baldwin 		} else {
1589240031aSJohn Baldwin 			consumed += charlen;
1599240031aSJohn Baldwin 			buf += charlen;
1609240031aSJohn Baldwin 			len -= charlen;
1619240031aSJohn Baldwin 			ms->offset++;
1629240031aSJohn Baldwin 		}
1639240031aSJohn Baldwin 	}
1649240031aSJohn Baldwin 	wmemstream_update(ms);
1659240031aSJohn Baldwin #ifdef DEBUG
1669240031aSJohn Baldwin 	fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed);
1679240031aSJohn Baldwin #endif
1689240031aSJohn Baldwin 	return (consumed);
1699240031aSJohn Baldwin }
1709240031aSJohn Baldwin 
1719240031aSJohn Baldwin static fpos_t
wmemstream_seek(void * cookie,fpos_t pos,int whence)1729240031aSJohn Baldwin wmemstream_seek(void *cookie, fpos_t pos, int whence)
1739240031aSJohn Baldwin {
1749240031aSJohn Baldwin 	struct wmemstream *ms;
1759240031aSJohn Baldwin 	fpos_t old;
1769240031aSJohn Baldwin 
1779240031aSJohn Baldwin 	ms = cookie;
1789240031aSJohn Baldwin 	old = ms->offset;
1799240031aSJohn Baldwin 	switch (whence) {
1809240031aSJohn Baldwin 	case SEEK_SET:
1819240031aSJohn Baldwin 		/* _fseeko() checks for negative offsets. */
1829240031aSJohn Baldwin 		assert(pos >= 0);
1839240031aSJohn Baldwin 		ms->offset = pos;
1849240031aSJohn Baldwin 		break;
1859240031aSJohn Baldwin 	case SEEK_CUR:
1869240031aSJohn Baldwin 		/* This is only called by _ftello(). */
1879240031aSJohn Baldwin 		assert(pos == 0);
1889240031aSJohn Baldwin 		break;
1899240031aSJohn Baldwin 	case SEEK_END:
1909240031aSJohn Baldwin 		if (pos < 0) {
1919240031aSJohn Baldwin 			if (pos + ms->len < 0) {
1929240031aSJohn Baldwin #ifdef DEBUG
1939240031aSJohn Baldwin 				fprintf(stderr,
1949240031aSJohn Baldwin 				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
1959240031aSJohn Baldwin 				    (intmax_t)pos, ms->len);
1969240031aSJohn Baldwin #endif
1979240031aSJohn Baldwin 				errno = EINVAL;
1989240031aSJohn Baldwin 				return (-1);
1999240031aSJohn Baldwin 			}
2009240031aSJohn Baldwin 		} else {
2019240031aSJohn Baldwin 			if (FPOS_MAX - ms->len < pos) {
2029240031aSJohn Baldwin #ifdef DEBUG
2039240031aSJohn Baldwin 				fprintf(stderr,
2049240031aSJohn Baldwin 				    "WMS: bad SEEK_END: pos %jd, len %zd\n",
2059240031aSJohn Baldwin 				    (intmax_t)pos, ms->len);
2069240031aSJohn Baldwin #endif
2079240031aSJohn Baldwin 				errno = EOVERFLOW;
2089240031aSJohn Baldwin 				return (-1);
2099240031aSJohn Baldwin 			}
2109240031aSJohn Baldwin 		}
2119240031aSJohn Baldwin 		ms->offset = ms->len + pos;
2129240031aSJohn Baldwin 		break;
2139240031aSJohn Baldwin 	}
2149240031aSJohn Baldwin 	/* Reset the multibyte state if a seek changes the position. */
2159240031aSJohn Baldwin 	if (ms->offset != old)
2169240031aSJohn Baldwin 		memset(&ms->mbstate, 0, sizeof(ms->mbstate));
2179240031aSJohn Baldwin 	wmemstream_update(ms);
2189240031aSJohn Baldwin #ifdef DEBUG
2199240031aSJohn Baldwin 	fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms,
2209240031aSJohn Baldwin 	    (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset);
2219240031aSJohn Baldwin #endif
2229240031aSJohn Baldwin 	return (ms->offset);
2239240031aSJohn Baldwin }
2249240031aSJohn Baldwin 
2259240031aSJohn Baldwin static int
wmemstream_close(void * cookie)2269240031aSJohn Baldwin wmemstream_close(void *cookie)
2279240031aSJohn Baldwin {
2289240031aSJohn Baldwin 
2299240031aSJohn Baldwin 	free(cookie);
2309240031aSJohn Baldwin 	return (0);
2319240031aSJohn Baldwin }
2329240031aSJohn Baldwin 
2339240031aSJohn Baldwin FILE *
open_wmemstream(wchar_t ** bufp,size_t * sizep)2349240031aSJohn Baldwin open_wmemstream(wchar_t **bufp, size_t *sizep)
2359240031aSJohn Baldwin {
2369240031aSJohn Baldwin 	struct wmemstream *ms;
2379240031aSJohn Baldwin 	int save_errno;
2389240031aSJohn Baldwin 	FILE *fp;
2399240031aSJohn Baldwin 
2409240031aSJohn Baldwin 	if (bufp == NULL || sizep == NULL) {
2419240031aSJohn Baldwin 		errno = EINVAL;
2429240031aSJohn Baldwin 		return (NULL);
2439240031aSJohn Baldwin 	}
2449240031aSJohn Baldwin 	*bufp = calloc(1, sizeof(wchar_t));
2459240031aSJohn Baldwin 	if (*bufp == NULL)
2469240031aSJohn Baldwin 		return (NULL);
2479240031aSJohn Baldwin 	ms = malloc(sizeof(*ms));
2489240031aSJohn Baldwin 	if (ms == NULL) {
2499240031aSJohn Baldwin 		save_errno = errno;
2509240031aSJohn Baldwin 		free(*bufp);
2519240031aSJohn Baldwin 		*bufp = NULL;
2529240031aSJohn Baldwin 		errno = save_errno;
2539240031aSJohn Baldwin 		return (NULL);
2549240031aSJohn Baldwin 	}
2559240031aSJohn Baldwin 	ms->bufp = bufp;
2569240031aSJohn Baldwin 	ms->sizep = sizep;
2579240031aSJohn Baldwin 	ms->len = 0;
2589240031aSJohn Baldwin 	ms->offset = 0;
2599240031aSJohn Baldwin 	memset(&ms->mbstate, 0, sizeof(mbstate_t));
2609240031aSJohn Baldwin 	wmemstream_update(ms);
2619240031aSJohn Baldwin 	fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek,
2629240031aSJohn Baldwin 	    wmemstream_close);
2639240031aSJohn Baldwin 	if (fp == NULL) {
2649240031aSJohn Baldwin 		save_errno = errno;
2659240031aSJohn Baldwin 		free(ms);
2669240031aSJohn Baldwin 		free(*bufp);
2679240031aSJohn Baldwin 		*bufp = NULL;
2689240031aSJohn Baldwin 		errno = save_errno;
2699240031aSJohn Baldwin 		return (NULL);
2709240031aSJohn Baldwin 	}
2719240031aSJohn Baldwin 	fwide(fp, 1);
2729240031aSJohn Baldwin 	return (fp);
2739240031aSJohn Baldwin }
274