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, char *buf, size_t nbytes) 48 { 49 errno = EBADF; 50 return (-1); 51 } 52 53 static ssize_t 54 open_wmemstream_write(FILE *iop, const char *buf, size_t nbytes) 55 { 56 wmemstream_t *wmemp = _xdata(iop); 57 size_t newsize, mbscount; 58 ssize_t nwritten = 0; 59 int ret; 60 61 /* 62 * nbytes is in bytes not wide characters. However, the most 63 * pathological case from a writing perspective is using ASCII 64 * characters. Thus if we size things assuming that nbytes will all 65 * possibly be valid wchar_t values on their own, then we'll always have 66 * enough buffer space. 67 */ 68 nbytes = MIN(nbytes, WMEMSTREAM_MAX); 69 ret = memstream_newsize(wmemp->wmstr_pos, wmemp->wmstr_alloc, nbytes, 70 &newsize); 71 if (ret < 0) { 72 return (-1); 73 } else if (ret > 0) { 74 void *temp; 75 temp = recallocarray(wmemp->wmstr_buf, wmemp->wmstr_alloc, 76 newsize, sizeof (wchar_t)); 77 if (temp == NULL) { 78 return (-1); 79 } 80 wmemp->wmstr_buf = temp; 81 wmemp->wmstr_alloc = newsize; 82 *wmemp->wmstr_ubufp = temp; 83 84 } 85 86 while (nbytes > 0) { 87 size_t nchars; 88 89 nchars = mbrtowc_nz(&wmemp->wmstr_buf[wmemp->wmstr_pos], 90 &buf[nwritten], nbytes, &wmemp->wmstr_mbs); 91 if (nchars == (size_t)-1) { 92 if (nwritten > 0) { 93 errno = 0; 94 break; 95 } else { 96 /* 97 * Overwrite errno in this case to be EIO. Most 98 * callers of stdio routines don't expect 99 * EILSEQ and it's not documented in POSIX, so 100 * we use this instead. 101 */ 102 errno = EIO; 103 return (-1); 104 } 105 } else if (nchars == (size_t)-2) { 106 nwritten += nbytes; 107 nbytes = 0; 108 } else { 109 nwritten += nchars; 110 nbytes -= nchars; 111 wmemp->wmstr_pos++; 112 } 113 } 114 115 if (wmemp->wmstr_pos > wmemp->wmstr_lsize) { 116 wmemp->wmstr_lsize = wmemp->wmstr_pos; 117 wmemp->wmstr_buf[wmemp->wmstr_pos] = L'\0'; 118 } 119 *wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize); 120 return (nwritten); 121 } 122 123 static off_t 124 open_wmemstream_seek(FILE *iop, off_t off, int whence) 125 { 126 wmemstream_t *wmemp = _xdata(iop); 127 size_t base, npos; 128 129 switch (whence) { 130 case SEEK_SET: 131 base = 0; 132 break; 133 case SEEK_CUR: 134 base = wmemp->wmstr_pos; 135 break; 136 case SEEK_END: 137 base = wmemp->wmstr_lsize; 138 break; 139 default: 140 errno = EINVAL; 141 return (-1); 142 } 143 144 if (!memstream_seek(base, off, WMEMSTREAM_MAX, &npos)) { 145 errno = EINVAL; 146 return (-1); 147 } 148 149 wmemp->wmstr_pos = npos; 150 *wmemp->wmstr_usizep = MIN(wmemp->wmstr_pos, wmemp->wmstr_lsize); 151 152 return ((off_t)wmemp->wmstr_pos); 153 } 154 155 static int 156 open_wmemstream_close(FILE *iop) 157 { 158 wmemstream_t *wmemp = _xdata(iop); 159 free(wmemp); 160 _xunassoc(iop); 161 return (0); 162 } 163 164 165 FILE * 166 open_wmemstream(wchar_t **bufp, size_t *sizep) 167 { 168 int err; 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