open_wmemstream.c revision 283927
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 28#include <sys/cdefs.h> 29__FBSDID("$FreeBSD: stable/10/lib/libc/stdio/open_wmemstream.c 283927 2015-06-02 19:20:39Z jhb $"); 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 44struct wmemstream { 45 wchar_t **bufp; 46 size_t *sizep; 47 ssize_t len; 48 fpos_t offset; 49 mbstate_t mbstate; 50}; 51 52static int 53wmemstream_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 79static void 80wmemstream_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 */ 92static size_t 93wbuflen(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 116static int 117wmemstream_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 169static fpos_t 170wmemstream_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 223static int 224wmemstream_close(void *cookie) 225{ 226 227 free(cookie); 228 return (0); 229} 230 231FILE * 232open_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