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
open_wmemstream_read(FILE * iop __unused,char * buf __unused,size_t nbytes __unused)47 open_wmemstream_read(FILE *iop __unused, char *buf __unused,
48     size_t nbytes __unused)
49 {
50 	errno = EBADF;
51 	return (-1);
52 }
53 
54 static ssize_t
open_wmemstream_write(FILE * iop,const char * buf,size_t nbytes)55 open_wmemstream_write(FILE *iop, const char *buf, size_t nbytes)
56 {
57 	wmemstream_t *wmemp = _xdata(iop);
58 	size_t newsize;
59 	ssize_t nwritten = 0;
60 	int ret;
61 
62 	/*
63 	 * nbytes is in bytes not wide characters. However, the most
64 	 * pathological case from a writing perspective is using ASCII
65 	 * characters. Thus if we size things assuming that nbytes will all
66 	 * possibly be valid wchar_t values on their own, then we'll always have
67 	 * enough buffer space.
68 	 */
69 	nbytes = MIN(nbytes, WMEMSTREAM_MAX);
70 	ret = memstream_newsize(wmemp->wmstr_pos, wmemp->wmstr_alloc, nbytes,
71 	    &newsize);
72 	if (ret < 0) {
73 		return (-1);
74 	} else if (ret > 0) {
75 		void *temp;
76 		temp = recallocarray(wmemp->wmstr_buf, wmemp->wmstr_alloc,
77 		    newsize, sizeof (wchar_t));
78 		if (temp == NULL) {
79 			return (-1);
80 		}
81 		wmemp->wmstr_buf = temp;
82 		wmemp->wmstr_alloc = newsize;
83 		*wmemp->wmstr_ubufp = temp;
84 
85 	}
86 
87 	while (nbytes > 0) {
88 		size_t nchars;
89 
90 		nchars = mbrtowc_nz(&wmemp->wmstr_buf[wmemp->wmstr_pos],
91 		    &buf[nwritten], nbytes, &wmemp->wmstr_mbs);
92 		if (nchars == (size_t)-1) {
93 			if (nwritten > 0) {
94 				errno = 0;
95 				break;
96 			} else {
97 				/*
98 				 * Overwrite errno in this case to be EIO. Most
99 				 * callers of stdio routines don't expect
100 				 * EILSEQ and it's not documented in POSIX, so
101 				 * we use this instead.
102 				 */
103 				errno = EIO;
104 				return (-1);
105 			}
106 		} else if (nchars == (size_t)-2) {
107 			nwritten += nbytes;
108 			nbytes = 0;
109 		} else {
110 			nwritten += nchars;
111 			nbytes -= nchars;
112 			wmemp->wmstr_pos++;
113 		}
114 	}
115 
116 	if (wmemp->wmstr_pos > wmemp->wmstr_lsize) {
117 		wmemp->wmstr_lsize = wmemp->wmstr_pos;
118 		wmemp->wmstr_buf[wmemp->wmstr_pos] = L'\0';
119 	}
120 	*wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize);
121 	return (nwritten);
122 }
123 
124 static off_t
open_wmemstream_seek(FILE * iop,off_t off,int whence)125 open_wmemstream_seek(FILE *iop, off_t off, int whence)
126 {
127 	wmemstream_t *wmemp = _xdata(iop);
128 	size_t base, npos;
129 
130 	switch (whence) {
131 	case SEEK_SET:
132 		base = 0;
133 		break;
134 	case SEEK_CUR:
135 		base = wmemp->wmstr_pos;
136 		break;
137 	case SEEK_END:
138 		base = wmemp->wmstr_lsize;
139 		break;
140 	default:
141 		errno = EINVAL;
142 		return (-1);
143 	}
144 
145 	if (!memstream_seek(base, off, WMEMSTREAM_MAX, &npos)) {
146 		errno = EINVAL;
147 		return (-1);
148 	}
149 
150 	wmemp->wmstr_pos = npos;
151 	*wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize);
152 
153 	return ((off_t)wmemp->wmstr_pos);
154 }
155 
156 static int
open_wmemstream_close(FILE * iop)157 open_wmemstream_close(FILE *iop)
158 {
159 	wmemstream_t *wmemp = _xdata(iop);
160 	free(wmemp);
161 	_xunassoc(iop);
162 	return (0);
163 }
164 
165 
166 FILE *
open_wmemstream(wchar_t ** bufp,size_t * sizep)167 open_wmemstream(wchar_t **bufp, size_t *sizep)
168 {
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