1/*	$OpenBSD: open_wmemstream.c,v 1.10 2023/07/11 12:14:16 claudio Exp $	*/
2
3/*
4 * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <errno.h>
20#include <fcntl.h>
21#include <stdio.h>
22#include <stdint.h>
23#include <stdlib.h>
24#include <string.h>
25#include <wchar.h>
26#include "local.h"
27
28#define	MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
29
30struct state {
31	wchar_t		 *string;	/* actual stream */
32	wchar_t		**pbuf;		/* point to the stream */
33	size_t		 *psize;	/* point to min(pos, len) */
34	size_t		  pos;		/* current position */
35	size_t		  size;		/* number of allocated wchar_t */
36	size_t		  len;		/* length of the data */
37	mbstate_t	  mbs;		/* conversion state of the stream */
38};
39
40static int
41wmemstream_write(void *v, const char *b, int l)
42{
43	struct state	*st = v;
44	wchar_t		*p;
45	size_t		 nmc, len, end;
46
47	end = (st->pos + l);
48
49	if (end >= st->size) {
50		/* 1.6 is (very) close to the golden ratio. */
51		size_t	sz = st->size * 8 / 5;
52
53		if (sz < end + 1)
54			sz = end + 1;
55		p = recallocarray(st->string, st->size, sz, sizeof(wchar_t));
56		if (!p)
57			return (-1);
58		*st->pbuf = st->string = p;
59		st->size = sz;
60	}
61
62	nmc = (st->size - st->pos) * sizeof(wchar_t);
63	len = mbsnrtowcs(st->string + st->pos, &b, nmc, l, &st->mbs);
64	if (len == (size_t)-1)
65		return (-1);
66	st->pos += len;
67
68	if (st->pos > st->len) {
69		st->len = st->pos;
70		st->string[st->len] = L'\0';
71	}
72
73	*st->psize = st->pos;
74
75	return (len);
76}
77
78static fpos_t
79wmemstream_seek(void *v, fpos_t off, int whence)
80{
81	struct state	*st = v;
82	ssize_t		 base = 0;
83
84	switch (whence) {
85	case SEEK_SET:
86		break;
87	case SEEK_CUR:
88		base = st->pos;
89		break;
90	case SEEK_END:
91		base = st->len;
92		break;
93	}
94
95	if (off > (SIZE_MAX / sizeof(wchar_t)) - base || off < -base) {
96		errno = EOVERFLOW;
97		return (-1);
98	}
99
100	/*
101	 * XXX Clearing mbs here invalidates shift state for state-
102	 * dependent encodings, but they are not (yet) supported.
103	 */
104	bzero(&st->mbs, sizeof(st->mbs));
105
106	st->pos = base + off;
107	*st->psize = MINIMUM(st->pos, st->len);
108
109	return (st->pos);
110}
111
112static int
113wmemstream_close(void *v)
114{
115	struct state	*st = v;
116
117	free(st);
118
119	return (0);
120}
121
122FILE *
123open_wmemstream(wchar_t **pbuf, size_t *psize)
124{
125	struct state	*st;
126	FILE		*fp;
127
128	if (pbuf == NULL || psize == NULL) {
129		errno = EINVAL;
130		return (NULL);
131	}
132
133	if ((st = malloc(sizeof(*st))) == NULL)
134		return (NULL);
135
136	if ((fp = __sfp()) == NULL) {
137		free(st);
138		return (NULL);
139	}
140
141	st->size = BUFSIZ * sizeof(wchar_t);
142	if ((st->string = calloc(1, st->size)) == NULL) {
143		free(st);
144		fp->_flags = 0;
145		return (NULL);
146	}
147
148	st->pos = 0;
149	st->len = 0;
150	st->pbuf = pbuf;
151	st->psize = psize;
152	bzero(&st->mbs, sizeof(st->mbs));
153
154	*pbuf = st->string;
155	*psize = st->len;
156
157	fp->_flags = __SWR;
158	fp->_file = -1;
159	fp->_cookie = st;
160	fp->_read = NULL;
161	fp->_write = wmemstream_write;
162	fp->_seek = wmemstream_seek;
163	fp->_close = wmemstream_close;
164	_SET_ORIENTATION(fp, 1);
165
166	return (fp);
167}
168