1 /*- 2 * Copyright (c) 2013 Advanced Computing Technologies 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 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include "namespace.h" 32 #include <assert.h> 33 #include <errno.h> 34 #include <limits.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <wchar.h> 39 #include "un-namespace.h" 40 41 /* XXX: There is no FPOS_MAX. This assumes fpos_t is an off_t. */ 42 #define FPOS_MAX OFF_MAX 43 44 struct wmemstream { 45 wchar_t **bufp; 46 size_t *sizep; 47 ssize_t len; 48 fpos_t offset; 49 mbstate_t mbstate; 50 }; 51 52 static int 53 wmemstream_grow(struct wmemstream *ms, fpos_t newoff) 54 { 55 wchar_t *buf; 56 ssize_t newsize; 57 58 if (newoff < 0 || newoff >= SSIZE_MAX / sizeof(wchar_t)) 59 newsize = SSIZE_MAX / sizeof(wchar_t) - 1; 60 else 61 newsize = newoff; 62 if (newsize > ms->len) { 63 buf = realloc(*ms->bufp, (newsize + 1) * sizeof(wchar_t)); 64 if (buf != NULL) { 65 #ifdef DEBUG 66 fprintf(stderr, "WMS: %p growing from %zd to %zd\n", 67 ms, ms->len, newsize); 68 #endif 69 wmemset(buf + ms->len + 1, 0, newsize - ms->len); 70 *ms->bufp = buf; 71 ms->len = newsize; 72 return (1); 73 } 74 return (0); 75 } 76 return (1); 77 } 78 79 static void 80 wmemstream_update(struct wmemstream *ms) 81 { 82 83 assert(ms->len >= 0 && ms->offset >= 0); 84 *ms->sizep = ms->len < ms->offset ? ms->len : ms->offset; 85 } 86 87 /* 88 * Based on a starting multibyte state and an input buffer, determine 89 * how many wchar_t's would be output. This doesn't use mbsnrtowcs() 90 * so that it can handle embedded null characters. 91 */ 92 static size_t 93 wbuflen(const mbstate_t *state, const char *buf, int len) 94 { 95 mbstate_t lenstate; 96 size_t charlen, count; 97 98 count = 0; 99 lenstate = *state; 100 while (len > 0) { 101 charlen = mbrlen(buf, len, &lenstate); 102 if (charlen == (size_t)-1) 103 return (-1); 104 if (charlen == (size_t)-2) 105 break; 106 if (charlen == 0) 107 /* XXX: Not sure how else to handle this. */ 108 charlen = 1; 109 len -= charlen; 110 buf += charlen; 111 count++; 112 } 113 return (count); 114 } 115 116 static int 117 wmemstream_write(void *cookie, const char *buf, int len) 118 { 119 struct wmemstream *ms; 120 ssize_t consumed, wlen; 121 size_t charlen; 122 123 ms = cookie; 124 wlen = wbuflen(&ms->mbstate, buf, len); 125 if (wlen < 0) { 126 errno = EILSEQ; 127 return (-1); 128 } 129 if (!wmemstream_grow(ms, ms->offset + wlen)) 130 return (-1); 131 132 /* 133 * This copies characters one at a time rather than using 134 * mbsnrtowcs() so it can properly handle embedded null 135 * characters. 136 */ 137 consumed = 0; 138 while (len > 0 && ms->offset < ms->len) { 139 charlen = mbrtowc(*ms->bufp + ms->offset, buf, len, 140 &ms->mbstate); 141 if (charlen == (size_t)-1) { 142 if (consumed == 0) { 143 errno = EILSEQ; 144 return (-1); 145 } 146 /* Treat it as a successful short write. */ 147 break; 148 } 149 if (charlen == 0) 150 /* XXX: Not sure how else to handle this. */ 151 charlen = 1; 152 if (charlen == (size_t)-2) { 153 consumed += len; 154 len = 0; 155 } else { 156 consumed += charlen; 157 buf += charlen; 158 len -= charlen; 159 ms->offset++; 160 } 161 } 162 wmemstream_update(ms); 163 #ifdef DEBUG 164 fprintf(stderr, "WMS: write(%p, %d) = %zd\n", ms, len, consumed); 165 #endif 166 return (consumed); 167 } 168 169 static fpos_t 170 wmemstream_seek(void *cookie, fpos_t pos, int whence) 171 { 172 struct wmemstream *ms; 173 fpos_t old; 174 175 ms = cookie; 176 old = ms->offset; 177 switch (whence) { 178 case SEEK_SET: 179 /* _fseeko() checks for negative offsets. */ 180 assert(pos >= 0); 181 ms->offset = pos; 182 break; 183 case SEEK_CUR: 184 /* This is only called by _ftello(). */ 185 assert(pos == 0); 186 break; 187 case SEEK_END: 188 if (pos < 0) { 189 if (pos + ms->len < 0) { 190 #ifdef DEBUG 191 fprintf(stderr, 192 "WMS: bad SEEK_END: pos %jd, len %zd\n", 193 (intmax_t)pos, ms->len); 194 #endif 195 errno = EINVAL; 196 return (-1); 197 } 198 } else { 199 if (FPOS_MAX - ms->len < pos) { 200 #ifdef DEBUG 201 fprintf(stderr, 202 "WMS: bad SEEK_END: pos %jd, len %zd\n", 203 (intmax_t)pos, ms->len); 204 #endif 205 errno = EOVERFLOW; 206 return (-1); 207 } 208 } 209 ms->offset = ms->len + pos; 210 break; 211 } 212 /* Reset the multibyte state if a seek changes the position. */ 213 if (ms->offset != old) 214 memset(&ms->mbstate, 0, sizeof(ms->mbstate)); 215 wmemstream_update(ms); 216 #ifdef DEBUG 217 fprintf(stderr, "WMS: seek(%p, %jd, %d) %jd -> %jd\n", ms, 218 (intmax_t)pos, whence, (intmax_t)old, (intmax_t)ms->offset); 219 #endif 220 return (ms->offset); 221 } 222 223 static int 224 wmemstream_close(void *cookie) 225 { 226 227 free(cookie); 228 return (0); 229 } 230 231 FILE * 232 open_wmemstream(wchar_t **bufp, size_t *sizep) 233 { 234 struct wmemstream *ms; 235 int save_errno; 236 FILE *fp; 237 238 if (bufp == NULL || sizep == NULL) { 239 errno = EINVAL; 240 return (NULL); 241 } 242 *bufp = calloc(1, sizeof(wchar_t)); 243 if (*bufp == NULL) 244 return (NULL); 245 ms = malloc(sizeof(*ms)); 246 if (ms == NULL) { 247 save_errno = errno; 248 free(*bufp); 249 *bufp = NULL; 250 errno = save_errno; 251 return (NULL); 252 } 253 ms->bufp = bufp; 254 ms->sizep = sizep; 255 ms->len = 0; 256 ms->offset = 0; 257 memset(&ms->mbstate, 0, sizeof(mbstate_t)); 258 wmemstream_update(ms); 259 fp = funopen(ms, NULL, wmemstream_write, wmemstream_seek, 260 wmemstream_close); 261 if (fp == NULL) { 262 save_errno = errno; 263 free(ms); 264 free(*bufp); 265 *bufp = NULL; 266 errno = save_errno; 267 return (NULL); 268 } 269 fwide(fp, 1); 270 return (fp); 271 } 272