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