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 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 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 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 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 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 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 * 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