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_memstream(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
30 typedef struct memstream {
31 char *mstr_buf;
32 size_t mstr_alloc;
33 size_t mstr_pos;
34 size_t mstr_lsize;
35 char **mstr_ubufp;
36 size_t *mstr_usizep;
37 } memstream_t;
38
39 /*
40 * Common seek and overflow detection logic for the memory stream family of
41 * functions (open_memstream, open_wmemstream, etc.). We need to validate
42 * several things:
43 *
44 * - That the offset when applied to base doesn't cause an over or underflow.
45 * - That the resulting offset is positive (done implicitly with the above)
46 * - That the resulting offset does not exceed an off_t's maximum size.
47 * Unfortunately the kernel doesn't export an OFF_MAX value to userland, so
48 * we have to know that it will always be equivalent to the environment's
49 * long. This is designed with the assumption that in an ILP32 environment we
50 * care about an off_t and not an off64_t. In cases where an off64_t is
51 * valid, we still have to fit inside of the size_t constraints.
52 *
53 * We check for each of the cases and only perform unsigned arithmetic to verify
54 * that we have defined behavior.
55 */
56 boolean_t
memstream_seek(size_t base,off_t off,size_t max,size_t * nposp)57 memstream_seek(size_t base, off_t off, size_t max, size_t *nposp)
58 {
59 size_t npos;
60
61 npos = base + (size_t)off;
62 if (off >= 0 && npos < base) {
63 return (B_FALSE);
64 }
65
66 if (off >= 0 && npos > LONG_MAX) {
67 return (B_FALSE);
68 }
69
70 if (off < 0 && npos >= base) {
71 return (B_FALSE);
72 }
73
74 if (npos > max) {
75 return (B_FALSE);
76 }
77
78 *nposp = npos;
79 return (B_TRUE);
80 }
81
82 int
memstream_newsize(size_t pos,size_t alloc,size_t nbytes,size_t * nallocp)83 memstream_newsize(size_t pos, size_t alloc, size_t nbytes, size_t *nallocp)
84 {
85 size_t npos = pos + nbytes + 1;
86 if (npos < pos) {
87 /*
88 * We've been asked to write a number of bytes that would result
89 * in an overflow in the position. This means the stream would
90 * need to allocate all of memory, that's impractical.
91 */
92 errno = EOVERFLOW;
93 return (-1);
94 }
95
96 /*
97 * If the new position is beyond the allocated amount, grow the array to
98 * a practical amount.
99 */
100 if (npos > alloc) {
101 size_t newalloc = P2ROUNDUP(npos, BUFSIZ);
102 if (newalloc < npos) {
103 errno = EOVERFLOW;
104 return (-1);
105 }
106 *nallocp = newalloc;
107 return (1);
108 }
109
110 return (0);
111 }
112
113 /*
114 * The SUSv4 spec says that this should not support reads.
115 */
116 static ssize_t
open_memstream_read(FILE * iop __unused,char * buf __unused,size_t nbytes __unused)117 open_memstream_read(FILE *iop __unused, char *buf __unused,
118 size_t nbytes __unused)
119 {
120 errno = EBADF;
121 return (-1);
122 }
123
124 static ssize_t
open_memstream_write(FILE * iop,const char * buf,size_t nbytes)125 open_memstream_write(FILE *iop, const char *buf, size_t nbytes)
126 {
127 memstream_t *memp = _xdata(iop);
128 size_t newsize;
129 int ret;
130
131 /*
132 * We need to fit inside of an ssize_t, so we need to first constrain
133 * nbytes to a reasonable value.
134 */
135 nbytes = MIN(nbytes, SSIZE_MAX);
136 ret = memstream_newsize(memp->mstr_pos, memp->mstr_alloc, nbytes,
137 &newsize);
138 if (ret < 0) {
139 return (-1);
140 } else if (ret > 0) {
141 void *temp;
142 temp = recallocarray(memp->mstr_buf, memp->mstr_alloc,
143 newsize, sizeof (char));
144 if (temp == NULL) {
145 return (-1);
146 }
147 memp->mstr_buf = temp;
148 memp->mstr_alloc = newsize;
149 *memp->mstr_ubufp = temp;
150 }
151
152 (void) memcpy(&memp->mstr_buf[memp->mstr_pos], buf, nbytes);
153 memp->mstr_pos += nbytes;
154
155 if (memp->mstr_pos > memp->mstr_lsize) {
156 memp->mstr_lsize = memp->mstr_pos;
157 memp->mstr_buf[memp->mstr_pos] = '\0';
158 }
159 *memp->mstr_usizep = MIN(memp->mstr_pos, memp->mstr_lsize);
160
161 return (nbytes);
162 }
163
164 static off_t
open_memstream_seek(FILE * iop,off_t off,int whence)165 open_memstream_seek(FILE *iop, off_t off, int whence)
166 {
167 memstream_t *memp = _xdata(iop);
168 size_t base, npos;
169
170 switch (whence) {
171 case SEEK_SET:
172 base = 0;
173 break;
174 case SEEK_CUR:
175 base = memp->mstr_pos;
176 break;
177 case SEEK_END:
178 base = memp->mstr_lsize;
179 break;
180 default:
181 errno = EINVAL;
182 return (-1);
183 }
184
185 if (!memstream_seek(base, off, SSIZE_MAX, &npos)) {
186 errno = EINVAL;
187 return (-1);
188 }
189 memp->mstr_pos = npos;
190 *memp->mstr_usizep = MIN(memp->mstr_pos, memp->mstr_lsize);
191
192 return ((off_t)memp->mstr_pos);
193 }
194
195 static int
open_memstream_close(FILE * iop)196 open_memstream_close(FILE *iop)
197 {
198 memstream_t *memp = _xdata(iop);
199 free(memp);
200 _xunassoc(iop);
201 return (0);
202 }
203
204 FILE *
open_memstream(char ** bufp,size_t * sizep)205 open_memstream(char **bufp, size_t *sizep)
206 {
207 FILE *iop;
208 memstream_t *memp;
209
210 if (bufp == NULL || sizep == NULL) {
211 errno = EINVAL;
212 return (NULL);
213 }
214
215 memp = calloc(1, sizeof (memstream_t));
216 if (memp == NULL) {
217 return (NULL);
218 }
219
220 memp->mstr_alloc = BUFSIZ;
221 memp->mstr_buf = calloc(memp->mstr_alloc, sizeof (char));
222 if (memp->mstr_buf == NULL) {
223 goto cleanup;
224 }
225 memp->mstr_buf[0] = '\0';
226 memp->mstr_pos = 0;
227 memp->mstr_lsize = 0;
228 memp->mstr_ubufp = bufp;
229 memp->mstr_usizep = sizep;
230
231 iop = _findiop();
232 if (iop == NULL) {
233 goto cleanup;
234 }
235
236 #ifdef _LP64
237 iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | _IOWRT;
238 #else
239 iop->_flag = _IOWRT;
240 #endif
241
242 /*
243 * Update the user pointers now, in case a call to fflush() happens
244 * immediately.
245 */
246
247 if (_xassoc(iop, open_memstream_read, open_memstream_write,
248 open_memstream_seek, open_memstream_close, memp) != 0) {
249 goto cleanup;
250 }
251 _setorientation(iop, _BYTE_MODE);
252 SET_SEEKABLE(iop);
253
254 *memp->mstr_ubufp = memp->mstr_buf;
255 *memp->mstr_usizep = MIN(memp->mstr_pos, memp->mstr_lsize);
256
257 return (iop);
258
259 cleanup:
260 free(memp->mstr_buf);
261 free(memp);
262 return (NULL);
263 }
264