1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2020 Robert Mustacchi
14  */
15 
16 /*
17  * Implements open_wmemstream(3C).
18  */
19 
20 #include "mtlib.h"
21 #include "file64.h"
22 #include <stdio.h>
23 #include "stdiom.h"
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <fcntl.h>
27 #include <sys/sysmacros.h>
28 #include <limits.h>
29 #include "libc.h"
30 
31 typedef struct wmemstream {
32 	wchar_t *wmstr_buf;
33 	size_t wmstr_alloc;
34 	size_t wmstr_pos;
35 	size_t wmstr_lsize;
36 	mbstate_t wmstr_mbs;
37 	wchar_t **wmstr_ubufp;
38 	size_t *wmstr_usizep;
39 } wmemstream_t;
40 
41 #define	WMEMSTREAM_MAX	(SSIZE_MAX / sizeof (wchar_t))
42 
43 /*
44  * The SUSv4 spec says that this should not support reads.
45  */
46 static ssize_t
47 open_wmemstream_read(FILE *iop, char *buf, size_t nbytes)
48 {
49 	errno = EBADF;
50 	return (-1);
51 }
52 
53 static ssize_t
54 open_wmemstream_write(FILE *iop, const char *buf, size_t nbytes)
55 {
56 	wmemstream_t *wmemp = _xdata(iop);
57 	size_t newsize, mbscount;
58 	ssize_t nwritten = 0;
59 	int ret;
60 
61 	/*
62 	 * nbytes is in bytes not wide characters. However, the most
63 	 * pathological case from a writing perspective is using ASCII
64 	 * characters. Thus if we size things assuming that nbytes will all
65 	 * possibly be valid wchar_t values on their own, then we'll always have
66 	 * enough buffer space.
67 	 */
68 	nbytes = MIN(nbytes, WMEMSTREAM_MAX);
69 	ret = memstream_newsize(wmemp->wmstr_pos, wmemp->wmstr_alloc, nbytes,
70 	    &newsize);
71 	if (ret < 0) {
72 		return (-1);
73 	} else if (ret > 0) {
74 		void *temp;
75 		temp = recallocarray(wmemp->wmstr_buf, wmemp->wmstr_alloc,
76 		    newsize, sizeof (wchar_t));
77 		if (temp == NULL) {
78 			return (-1);
79 		}
80 		wmemp->wmstr_buf = temp;
81 		wmemp->wmstr_alloc = newsize;
82 		*wmemp->wmstr_ubufp = temp;
83 
84 	}
85 
86 	while (nbytes > 0) {
87 		size_t nchars;
88 
89 		nchars = mbrtowc_nz(&wmemp->wmstr_buf[wmemp->wmstr_pos],
90 		    &buf[nwritten], nbytes, &wmemp->wmstr_mbs);
91 		if (nchars == (size_t)-1) {
92 			if (nwritten > 0) {
93 				errno = 0;
94 				break;
95 			} else {
96 				/*
97 				 * Overwrite errno in this case to be EIO. Most
98 				 * callers of stdio routines don't expect
99 				 * EILSEQ and it's not documented in POSIX, so
100 				 * we use this instead.
101 				 */
102 				errno = EIO;
103 				return (-1);
104 			}
105 		} else if (nchars == (size_t)-2) {
106 			nwritten += nbytes;
107 			nbytes = 0;
108 		} else {
109 			nwritten += nchars;
110 			nbytes -= nchars;
111 			wmemp->wmstr_pos++;
112 		}
113 	}
114 
115 	if (wmemp->wmstr_pos > wmemp->wmstr_lsize) {
116 		wmemp->wmstr_lsize = wmemp->wmstr_pos;
117 		wmemp->wmstr_buf[wmemp->wmstr_pos] = L'\0';
118 	}
119 	*wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize);
120 	return (nwritten);
121 }
122 
123 static off_t
124 open_wmemstream_seek(FILE *iop, off_t off, int whence)
125 {
126 	wmemstream_t *wmemp = _xdata(iop);
127 	size_t base, npos;
128 
129 	switch (whence) {
130 	case SEEK_SET:
131 		base = 0;
132 		break;
133 	case SEEK_CUR:
134 		base = wmemp->wmstr_pos;
135 		break;
136 	case SEEK_END:
137 		base = wmemp->wmstr_lsize;
138 		break;
139 	default:
140 		errno = EINVAL;
141 		return (-1);
142 	}
143 
144 	if (!memstream_seek(base, off, WMEMSTREAM_MAX, &npos)) {
145 		errno = EINVAL;
146 		return (-1);
147 	}
148 
149 	wmemp->wmstr_pos = npos;
150 	*wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize);
151 
152 	return ((off_t)wmemp->wmstr_pos);
153 }
154 
155 static int
156 open_wmemstream_close(FILE *iop)
157 {
158 	wmemstream_t *wmemp = _xdata(iop);
159 	free(wmemp);
160 	_xunassoc(iop);
161 	return (0);
162 }
163 
164 
165 FILE *
166 open_wmemstream(wchar_t **bufp, size_t *sizep)
167 {
168 	int err;
169 	FILE *iop;
170 	wmemstream_t *wmemp;
171 
172 	if (bufp == NULL || sizep == NULL) {
173 		errno = EINVAL;
174 		return (NULL);
175 	}
176 
177 	wmemp = calloc(1, sizeof (wmemstream_t));
178 	if (wmemp == NULL) {
179 		return (NULL);
180 	}
181 
182 	wmemp->wmstr_alloc = BUFSIZ;
183 	wmemp->wmstr_buf = calloc(wmemp->wmstr_alloc, sizeof (wchar_t));
184 	if (wmemp->wmstr_buf == NULL) {
185 		goto cleanup;
186 	}
187 	wmemp->wmstr_buf[0] = L'\0';
188 	wmemp->wmstr_pos = 0;
189 	wmemp->wmstr_lsize = 0;
190 	wmemp->wmstr_ubufp = bufp;
191 	wmemp->wmstr_usizep = sizep;
192 
193 	iop = _findiop();
194 	if (iop == NULL) {
195 		goto cleanup;
196 	}
197 
198 #ifdef	_LP64
199 	iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | _IOWRT;
200 #else
201 	iop->_flag = _IOWRT;
202 #endif
203 
204 	/*
205 	 * Update the user pointers now, in case a call to fflush() happens
206 	 * immediately.
207 	 */
208 
209 	if (_xassoc(iop, open_wmemstream_read, open_wmemstream_write,
210 	    open_wmemstream_seek, open_wmemstream_close, wmemp) != 0) {
211 		goto cleanup;
212 	}
213 	_setorientation(iop, _WC_MODE);
214 	SET_SEEKABLE(iop);
215 
216 	*wmemp->wmstr_ubufp = wmemp->wmstr_buf;
217 	*wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize);
218 
219 	return (iop);
220 
221 cleanup:
222 	free(wmemp->wmstr_buf);
223 	free(wmemp);
224 	return (NULL);
225 }
226