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_wmemstream(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 #include "libc.h" 30 31 typedef struct wmemstream { 32 wchar_t *wmstr_buf; 33 size_t wmstr_alloc; 34 size_t wmstr_pos; 35 size_t wmstr_lsize; 36 mbstate_t wmstr_mbs; 37 wchar_t **wmstr_ubufp; 38 size_t *wmstr_usizep; 39 } wmemstream_t; 40 41 #define WMEMSTREAM_MAX (SSIZE_MAX / sizeof (wchar_t)) 42 43 /* 44 * The SUSv4 spec says that this should not support reads. 45 */ 46 static ssize_t 47 open_wmemstream_read(FILE *iop __unused, char *buf __unused, 48 size_t nbytes __unused) 49 { 50 errno = EBADF; 51 return (-1); 52 } 53 54 static ssize_t 55 open_wmemstream_write(FILE *iop, const char *buf, size_t nbytes) 56 { 57 wmemstream_t *wmemp = _xdata(iop); 58 size_t newsize; 59 ssize_t nwritten = 0; 60 int ret; 61 62 /* 63 * nbytes is in bytes not wide characters. However, the most 64 * pathological case from a writing perspective is using ASCII 65 * characters. Thus if we size things assuming that nbytes will all 66 * possibly be valid wchar_t values on their own, then we'll always have 67 * enough buffer space. 68 */ 69 nbytes = MIN(nbytes, WMEMSTREAM_MAX); 70 ret = memstream_newsize(wmemp->wmstr_pos, wmemp->wmstr_alloc, nbytes, 71 &newsize); 72 if (ret < 0) { 73 return (-1); 74 } else if (ret > 0) { 75 void *temp; 76 temp = recallocarray(wmemp->wmstr_buf, wmemp->wmstr_alloc, 77 newsize, sizeof (wchar_t)); 78 if (temp == NULL) { 79 return (-1); 80 } 81 wmemp->wmstr_buf = temp; 82 wmemp->wmstr_alloc = newsize; 83 *wmemp->wmstr_ubufp = temp; 84 85 } 86 87 while (nbytes > 0) { 88 size_t nchars; 89 90 nchars = mbrtowc_nz(&wmemp->wmstr_buf[wmemp->wmstr_pos], 91 &buf[nwritten], nbytes, &wmemp->wmstr_mbs); 92 if (nchars == (size_t)-1) { 93 if (nwritten > 0) { 94 errno = 0; 95 break; 96 } else { 97 /* 98 * Overwrite errno in this case to be EIO. Most 99 * callers of stdio routines don't expect 100 * EILSEQ and it's not documented in POSIX, so 101 * we use this instead. 102 */ 103 errno = EIO; 104 return (-1); 105 } 106 } else if (nchars == (size_t)-2) { 107 nwritten += nbytes; 108 nbytes = 0; 109 } else { 110 nwritten += nchars; 111 nbytes -= nchars; 112 wmemp->wmstr_pos++; 113 } 114 } 115 116 if (wmemp->wmstr_pos > wmemp->wmstr_lsize) { 117 wmemp->wmstr_lsize = wmemp->wmstr_pos; 118 wmemp->wmstr_buf[wmemp->wmstr_pos] = L'\0'; 119 } 120 *wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize); 121 return (nwritten); 122 } 123 124 static off_t 125 open_wmemstream_seek(FILE *iop, off_t off, int whence) 126 { 127 wmemstream_t *wmemp = _xdata(iop); 128 size_t base, npos; 129 130 switch (whence) { 131 case SEEK_SET: 132 base = 0; 133 break; 134 case SEEK_CUR: 135 base = wmemp->wmstr_pos; 136 break; 137 case SEEK_END: 138 base = wmemp->wmstr_lsize; 139 break; 140 default: 141 errno = EINVAL; 142 return (-1); 143 } 144 145 if (!memstream_seek(base, off, WMEMSTREAM_MAX, &npos)) { 146 errno = EINVAL; 147 return (-1); 148 } 149 150 wmemp->wmstr_pos = npos; 151 *wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize); 152 153 return ((off_t)wmemp->wmstr_pos); 154 } 155 156 static int 157 open_wmemstream_close(FILE *iop) 158 { 159 wmemstream_t *wmemp = _xdata(iop); 160 free(wmemp); 161 _xunassoc(iop); 162 return (0); 163 } 164 165 166 FILE * 167 open_wmemstream(wchar_t **bufp, size_t *sizep) 168 { 169 FILE *iop; 170 wmemstream_t *wmemp; 171 172 if (bufp == NULL || sizep == NULL) { 173 errno = EINVAL; 174 return (NULL); 175 } 176 177 wmemp = calloc(1, sizeof (wmemstream_t)); 178 if (wmemp == NULL) { 179 return (NULL); 180 } 181 182 wmemp->wmstr_alloc = BUFSIZ; 183 wmemp->wmstr_buf = calloc(wmemp->wmstr_alloc, sizeof (wchar_t)); 184 if (wmemp->wmstr_buf == NULL) { 185 goto cleanup; 186 } 187 wmemp->wmstr_buf[0] = L'\0'; 188 wmemp->wmstr_pos = 0; 189 wmemp->wmstr_lsize = 0; 190 wmemp->wmstr_ubufp = bufp; 191 wmemp->wmstr_usizep = sizep; 192 193 iop = _findiop(); 194 if (iop == NULL) { 195 goto cleanup; 196 } 197 198 #ifdef _LP64 199 iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | _IOWRT; 200 #else 201 iop->_flag = _IOWRT; 202 #endif 203 204 /* 205 * Update the user pointers now, in case a call to fflush() happens 206 * immediately. 207 */ 208 209 if (_xassoc(iop, open_wmemstream_read, open_wmemstream_write, 210 open_wmemstream_seek, open_wmemstream_close, wmemp) != 0) { 211 goto cleanup; 212 } 213 _setorientation(iop, _WC_MODE); 214 SET_SEEKABLE(iop); 215 216 *wmemp->wmstr_ubufp = wmemp->wmstr_buf; 217 *wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize); 218 219 return (iop); 220 221 cleanup: 222 free(wmemp->wmstr_buf); 223 free(wmemp); 224 return (NULL); 225 } 226