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 fmemopen(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 30 typedef enum fmemopen_flags { 31 /* 32 * Indicates that the user gave us the buffer and so we shouldn't free 33 * it. 34 */ 35 FMO_F_USER_BUFFER = 1 << 0, 36 /* 37 * When the stream is open for update (a, a+) then we have to have 38 * slightly different behavior on write and zeroing the buffer. 39 */ 40 FMO_F_APPEND = 1 << 1 41 } fmemopen_flags_t; 42 43 typedef struct fmemopen { 44 /* 45 * Pointer to the underlying memory stream. 46 */ 47 char *fmo_buf; 48 /* 49 * Allocated length of the buffer. 50 */ 51 size_t fmo_alloc; 52 /* 53 * Current position of the buffer. 54 */ 55 size_t fmo_pos; 56 /* 57 * Current 'size' of the buffer. POSIX describes a size that the buffer 58 * has which is separate from the allocated size, but cannot exceed it. 59 */ 60 size_t fmo_lsize; 61 fmemopen_flags_t fmo_flags; 62 } fmemopen_t; 63 64 static ssize_t 65 fmemopen_read(FILE *iop, char *buf, size_t nbytes) 66 { 67 fmemopen_t *fmp = _xdata(iop); 68 69 nbytes = MIN(nbytes, fmp->fmo_lsize - fmp->fmo_pos); 70 if (nbytes == 0) { 71 return (0); 72 } 73 74 (void) memcpy(buf, fmp->fmo_buf, nbytes); 75 fmp->fmo_pos += nbytes; 76 77 return (nbytes); 78 } 79 80 static ssize_t 81 fmemopen_write(FILE *iop, const char *buf, size_t nbytes) 82 { 83 size_t npos; 84 fmemopen_t *fmp = _xdata(iop); 85 86 if ((fmp->fmo_flags & FMO_F_APPEND) != 0) { 87 /* 88 * POSIX says that if append mode is in effect, we must always 89 * seek to the logical size. This effectively is mimicking the 90 * O_APPEND behavior. 91 */ 92 fmp->fmo_pos = fmp->fmo_lsize; 93 } 94 95 if (nbytes == 0) { 96 return (0); 97 } else if (nbytes >= SSIZE_MAX) { 98 errno = EINVAL; 99 return (-1); 100 } 101 102 npos = fmp->fmo_pos + nbytes; 103 if (npos < nbytes) { 104 errno = EOVERFLOW; 105 return (-1); 106 } else if (npos > fmp->fmo_alloc) { 107 nbytes = fmp->fmo_alloc - fmp->fmo_pos; 108 } 109 110 (void) memcpy(&fmp->fmo_buf[fmp->fmo_pos], buf, nbytes); 111 fmp->fmo_pos += nbytes; 112 113 if (fmp->fmo_pos > fmp->fmo_lsize) { 114 fmp->fmo_lsize = fmp->fmo_pos; 115 116 /* 117 * POSIX distinguishes behavior for writing a NUL in these 118 * streams. Basically if we are open for update and we are at 119 * the end of the buffer, we don't place a NUL. Otherwise, we 120 * always place one at the current position (or the end if we 121 * were over the edge). 122 */ 123 if (fmp->fmo_lsize < fmp->fmo_alloc) { 124 fmp->fmo_buf[fmp->fmo_lsize] = '\0'; 125 } else if ((fmp->fmo_flags & FMO_F_APPEND) == 0) { 126 fmp->fmo_buf[fmp->fmo_alloc - 1] = '\0'; 127 } 128 } 129 130 return (nbytes); 131 } 132 133 static off_t 134 fmemopen_seek(FILE *iop, off_t off, int whence) 135 { 136 fmemopen_t *fmp = _xdata(iop); 137 size_t base, npos; 138 139 switch (whence) { 140 case SEEK_SET: 141 base = 0; 142 break; 143 case SEEK_CUR: 144 base = fmp->fmo_pos; 145 break; 146 case SEEK_END: 147 base = fmp->fmo_lsize; 148 break; 149 default: 150 errno = EINVAL; 151 return (-1); 152 } 153 154 if (!memstream_seek(base, off, fmp->fmo_alloc, &npos)) { 155 errno = EINVAL; 156 return (-1); 157 } 158 fmp->fmo_pos = npos; 159 160 return ((off_t)fmp->fmo_pos); 161 } 162 163 static void 164 fmemopen_free(fmemopen_t *fmp) 165 { 166 if (fmp->fmo_buf != NULL && 167 (fmp->fmo_flags & FMO_F_USER_BUFFER) == 0) { 168 free(fmp->fmo_buf); 169 } 170 171 free(fmp); 172 } 173 174 static int 175 fmemopen_close(FILE *iop) 176 { 177 fmemopen_t *fmp = _xdata(iop); 178 fmemopen_free(fmp); 179 _xunassoc(iop); 180 return (0); 181 } 182 183 FILE * 184 fmemopen(void *_RESTRICT_KYWD buf, size_t size, 185 const char *_RESTRICT_KYWD mode) 186 { 187 int oflags, fflags, err; 188 fmemopen_t *fmp; 189 FILE *iop; 190 191 if (size == 0 || mode == NULL) { 192 errno = EINVAL; 193 return (NULL); 194 } 195 196 if (_stdio_flags(mode, &oflags, &fflags) != 0) { 197 /* errno set for us */ 198 return (NULL); 199 } 200 201 /* 202 * buf is only allowed to be NULL if the '+' is specified. If the '+' 203 * mode was specified, then we'll have fflags set to _IORW. 204 */ 205 if (buf == NULL && fflags != _IORW) { 206 errno = EINVAL; 207 return (NULL); 208 } 209 210 if ((fmp = calloc(1, sizeof (fmemopen_t))) == NULL) { 211 errno = ENOMEM; 212 return (NULL); 213 } 214 215 if (buf == NULL) { 216 fmp->fmo_buf = calloc(size, sizeof (uint8_t)); 217 if (fmp->fmo_buf == NULL) { 218 errno = ENOMEM; 219 goto cleanup; 220 } 221 } else { 222 fmp->fmo_buf = buf; 223 fmp->fmo_flags |= FMO_F_USER_BUFFER; 224 } 225 226 fmp->fmo_alloc = size; 227 228 /* 229 * Set the initial logical size and position depending on whether we're 230 * using r(+), w(+), and a(+). The latter two are identified by O_TRUNC 231 * and O_APPEND in oflags. 232 */ 233 if ((oflags & O_APPEND) != 0) { 234 fmp->fmo_pos = strnlen(fmp->fmo_buf, fmp->fmo_alloc); 235 fmp->fmo_lsize = fmp->fmo_pos; 236 fmp->fmo_flags |= FMO_F_APPEND; 237 } else if ((oflags & O_TRUNC) != 0) { 238 fmp->fmo_buf[0] = '\0'; 239 fmp->fmo_pos = 0; 240 fmp->fmo_lsize = 0; 241 } else { 242 fmp->fmo_pos = 0; 243 fmp->fmo_lsize = size; 244 } 245 246 iop = _findiop(); 247 if (iop == NULL) { 248 goto cleanup; 249 } 250 251 #ifdef _LP64 252 iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | fflags; 253 #else 254 iop->_flag = fflags; 255 #endif 256 if (_xassoc(iop, fmemopen_read, fmemopen_write, fmemopen_seek, 257 fmemopen_close, fmp) != 0) { 258 goto cleanup; 259 } 260 261 SET_SEEKABLE(iop); 262 263 return (iop); 264 265 cleanup: 266 err = errno; 267 fmemopen_free(fmp); 268 errno = err; 269 return (NULL); 270 } 271