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