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,char * buf,size_t nbytes)47 open_wmemstream_read(FILE *iop, char *buf, size_t nbytes)
48 {
49 errno = EBADF;
50 return (-1);
51 }
52
53 static ssize_t
open_wmemstream_write(FILE * iop,const char * buf,size_t nbytes)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
open_wmemstream_seek(FILE * iop,off_t off,int whence)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
open_wmemstream_close(FILE * iop)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 *
open_wmemstream(wchar_t ** bufp,size_t * sizep)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