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