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,char * buf,size_t nbytes)117 open_memstream_read(FILE *iop, char *buf, size_t nbytes)
118 {
119 errno = EBADF;
120 return (-1);
121 }
122
123 static ssize_t
open_memstream_write(FILE * iop,const char * buf,size_t nbytes)124 open_memstream_write(FILE *iop, const char *buf, size_t nbytes)
125 {
126 memstream_t *memp = _xdata(iop);
127 size_t newsize;
128 int ret;
129
130 /*
131 * We need to fit inside of an ssize_t, so we need to first constrain
132 * nbytes to a reasonable value.
133 */
134 nbytes = MIN(nbytes, SSIZE_MAX);
135 ret = memstream_newsize(memp->mstr_pos, memp->mstr_alloc, nbytes,
136 &newsize);
137 if (ret < 0) {
138 return (-1);
139 } else if (ret > 0) {
140 void *temp;
141 temp = recallocarray(memp->mstr_buf, memp->mstr_alloc,
142 newsize, sizeof (char));
143 if (temp == NULL) {
144 return (-1);
145 }
146 memp->mstr_buf = temp;
147 memp->mstr_alloc = newsize;
148 *memp->mstr_ubufp = temp;
149 }
150
151 (void) memcpy(&memp->mstr_buf[memp->mstr_pos], buf, nbytes);
152 memp->mstr_pos += nbytes;
153
154 if (memp->mstr_pos > memp->mstr_lsize) {
155 memp->mstr_lsize = memp->mstr_pos;
156 memp->mstr_buf[memp->mstr_pos] = '\0';
157 }
158 *memp->mstr_usizep = MIN(memp->mstr_pos, memp->mstr_lsize);
159
160 return (nbytes);
161 }
162
163 static off_t
open_memstream_seek(FILE * iop,off_t off,int whence)164 open_memstream_seek(FILE *iop, off_t off, int whence)
165 {
166 memstream_t *memp = _xdata(iop);
167 size_t base, npos;
168
169 switch (whence) {
170 case SEEK_SET:
171 base = 0;
172 break;
173 case SEEK_CUR:
174 base = memp->mstr_pos;
175 break;
176 case SEEK_END:
177 base = memp->mstr_lsize;
178 break;
179 default:
180 errno = EINVAL;
181 return (-1);
182 }
183
184 if (!memstream_seek(base, off, SSIZE_MAX, &npos)) {
185 errno = EINVAL;
186 return (-1);
187 }
188 memp->mstr_pos = npos;
189 *memp->mstr_usizep = MIN(memp->mstr_pos, memp->mstr_lsize);
190
191 return ((off_t)memp->mstr_pos);
192 }
193
194 static int
open_memstream_close(FILE * iop)195 open_memstream_close(FILE *iop)
196 {
197 memstream_t *memp = _xdata(iop);
198 free(memp);
199 _xunassoc(iop);
200 return (0);
201 }
202
203 FILE *
open_memstream(char ** bufp,size_t * sizep)204 open_memstream(char **bufp, size_t *sizep)
205 {
206 int err;
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