1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2013 Hudson River Trading LLC 5 * Written by: John H. Baldwin <jhb@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include "namespace.h" 31 #include <sys/param.h> 32 #include <assert.h> 33 #include <errno.h> 34 #include <limits.h> 35 #include <stdckdint.h> 36 #ifdef DEBUG 37 #include <stdint.h> 38 #endif 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <wchar.h> 43 #include "un-namespace.h" 44 45 /* XXX: There is no FPOS_MAX. This assumes fpos_t is an off_t. */ 46 #define FPOS_MAX OFF_MAX 47 48 struct wmemstream { 49 wchar_t **bufp; 50 size_t *sizep; 51 size_t size; 52 ssize_t len; 53 fpos_t offset; 54 mbstate_t mbstate; 55 }; 56 57 static int 58 wmemstream_grow(struct wmemstream *ms, fpos_t newoff) 59 { 60 wchar_t *buf; 61 ssize_t newlen; 62 63 if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t)) 64 newlen = SSIZE_MAX / sizeof(wchar_t) - 1; 65 else 66 newlen = newoff; 67 if (newlen > ms->size) { 68 size_t newsize; 69 70 if (ckd_add(&newsize, ms->size, ms->size / 2)) 71 newsize = SSIZE_MAX - 1; 72 73 newsize = MAX(newsize, newlen); 74 75 buf = reallocarray(*ms->bufp, newsize + 1, sizeof(wchar_t)); 76 if (buf == NULL) 77 return (0); 78 #ifdef DEBUG 79 fprintf(stderr, "WMS: %p growing from %zd to %zd\n", 80 ms, ms->size, newsize); 81 #endif 82 wmemset(buf + ms->size + 1, 0, newsize - ms->size); 83 *ms->bufp = buf; 84 ms->size = newsize; 85 } 86 87 if (newlen > ms->len) 88 ms->len = newlen; 89 return (1); 90 } 91 92 static void 93 wmemstream_update(struct wmemstream *ms) 94 { 95 96 assert(ms->len >= 0 && ms->offset >= 0); 97 *ms->sizep = ms->len < ms->offset ? ms->len : ms->offset; 98 } 99 100 /* 101 * Based on a starting multibyte state and an input buffer, determine 102 * how many wchar_t's would be output. This doesn't use mbsnrtowcs() 103 * so that it can handle embedded null characters. 104 */ 105 static size_t 106 wbuflen(const mbstate_t *state, const char *buf, int len) 107 { 108 mbstate_t lenstate; 109 size_t charlen, count; 110 111 count = 0; 112 lenstate = *state; 113 while (len > 0) { 114 charlen = mbrlen(buf, len, &lenstate); 115 if (charlen == (size_t)-1) 116 return (-1); 117 if (charlen == (size_t)-2) 118 break; 119 if (charlen == 0) 120 /* XXX: Not sure how else to handle this. */ 121 charlen = 1; 122 len -= charlen; 123 buf += charlen; 124 count++; 125 } 126 return (count); 127 } 128 129 static int 130 wmemstream_write(void *cookie, const char *buf, int len) 131 { 132 struct wmemstream *ms; 133 ssize_t consumed, wlen; 134 size_t charlen; 135 136 ms = cookie; 137 wlen = wbuflen(&ms->mbstate, buf, len); 138 if (wlen < 0) { 139 errno = EILSEQ; 140 return (-1); 141 } 142 if (!wmemstream_grow(ms, ms->offset + wlen)) 143 return (-1); 144 145 /* 146 * This copies characters one at a time rather than using 147 * mbsnrtowcs() so it can properly handle embedded null 148 * characters. 149 */ 150 consumed = 0; 151 while (len > 0 && ms->offset < ms->len) { 152 charlen = mbrtowc(*ms->bufp + ms->offset, buf, len, 153 &ms->mbstate); 154 if (charlen == (size_t)-1) { 155 if (consumed == 0) { 156 errno = EILSEQ; 157 return (-1); 158 } 159 /* Treat it as a successful short write. */ 160 break; 161 } 162 if (charlen == 0) 163 /* XXX: Not sure how else to handle this. */ 164 charlen = 1; 165 if (charlen == (size_t)-2) { 166 consumed += len; 167 len = 0; 168 } else { 169 consumed += charlen; 170 buf += charlen; 171 len -= charlen; 172 ms->offset++; 173 } 174 } 175 wmemstream_update(ms); 176 #ifdef DEBUG 177 fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed); 178 #endif 179 return (consumed); 180 } 181 182 static fpos_t 183 wmemstream_seek(void *cookie, fpos_t pos, int whence) 184 { 185 struct wmemstream *ms; 186 fpos_t old; 187 188 ms = cookie; 189 old = ms->offset; 190 switch (whence) { 191 case SEEK_SET: 192 /* _fseeko() checks for negative offsets. */ 193 assert(pos >= 0); 194 ms->offset = pos; 195 break; 196 case SEEK_CUR: 197 /* This is only called by _ftello(). */ 198 assert(pos == 0); 199 break; 200 case SEEK_END: 201 if (pos < 0) { 202 if (pos + ms->len < 0) { 203 #ifdef DEBUG 204 fprintf(stderr, 205 "WMS: bad SEEK_END: pos %jd, len %zd\n", 206 (intmax_t)pos, ms->len); 207 #endif 208 errno = EINVAL; 209 return (-1); 210 } 211 } else { 212 if (FPOS_MAX - ms->len < pos) { 213 #ifdef DEBUG 214 fprintf(stderr, 215 "WMS: bad SEEK_END: pos %jd, len %zd\n", 216 (intmax_t)pos, ms->len); 217 #endif 218 errno = EOVERFLOW; 219 return (-1); 220 } 221 } 222 ms->offset = ms->len + pos; 223 break; 224 } 225 /* Reset the multibyte state if a seek changes the position. */ 226 if (ms->offset != old) 227 memset(&ms->mbstate, 0, sizeof(ms->mbstate)); 228 wmemstream_update(ms); 229 #ifdef DEBUG 230 fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms, 231 (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset); 232 #endif 233 return (ms->offset); 234 } 235 236 static int 237 wmemstream_close(void *cookie) 238 { 239 240 free(cookie); 241 return (0); 242 } 243 244 FILE * 245 open_wmemstream(wchar_t **bufp, size_t *sizep) 246 { 247 struct wmemstream *ms; 248 int save_errno; 249 FILE *fp; 250 251 if (bufp == NULL || sizep == NULL) { 252 errno = EINVAL; 253 return (NULL); 254 } 255 *bufp = calloc(1, sizeof(wchar_t)); 256 if (*bufp == NULL) 257 return (NULL); 258 ms = malloc(sizeof(*ms)); 259 if (ms == NULL) { 260 save_errno = errno; 261 free(*bufp); 262 *bufp = NULL; 263 errno = save_errno; 264 return (NULL); 265 } 266 ms->bufp = bufp; 267 ms->sizep = sizep; 268 ms->size = 0; 269 ms->len = 0; 270 ms->offset = 0; 271 memset(&ms->mbstate, 0, sizeof(mbstate_t)); 272 wmemstream_update(ms); 273 fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek, 274 wmemstream_close); 275 if (fp == NULL) { 276 save_errno = errno; 277 free(ms); 278 free(*bufp); 279 *bufp = NULL; 280 errno = save_errno; 281 return (NULL); 282 } 283 fwide(fp, 1); 284 return (fp); 285 } 286