1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org> 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 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 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 <fcntl.h> 32 #include <stdbool.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <errno.h> 37 #include "local.h" 38 39 struct fmemopen_cookie 40 { 41 char *buf; /* pointer to the memory region */ 42 bool own; /* did we allocate the buffer ourselves? */ 43 char bin; /* is this a binary buffer? */ 44 size_t size; /* buffer length in bytes */ 45 size_t len; /* data length in bytes */ 46 size_t off; /* current offset into the buffer */ 47 }; 48 49 static int fmemopen_read(void *cookie, char *buf, int nbytes); 50 static int fmemopen_write(void *cookie, const char *buf, int nbytes); 51 static fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence); 52 static int fmemopen_close(void *cookie); 53 54 FILE * 55 fmemopen(void * __restrict buf, size_t size, const char * __restrict mode) 56 { 57 struct fmemopen_cookie *ck; 58 FILE *f; 59 int flags, rc; 60 61 /* 62 * POSIX says we shall return EINVAL if size is 0. 63 */ 64 if (size == 0) { 65 errno = EINVAL; 66 return (NULL); 67 } 68 69 /* 70 * Retrieve the flags as used by open(2) from the mode argument, and 71 * validate them. 72 */ 73 rc = __sflags(mode, &flags); 74 if (rc == 0) { 75 errno = EINVAL; 76 return (NULL); 77 } 78 79 /* 80 * There's no point in requiring an automatically allocated buffer 81 * in write-only mode. 82 */ 83 if (!(flags & O_RDWR) && buf == NULL) { 84 errno = EINVAL; 85 return (NULL); 86 } 87 88 ck = malloc(sizeof(struct fmemopen_cookie)); 89 if (ck == NULL) { 90 return (NULL); 91 } 92 93 ck->off = 0; 94 ck->size = size; 95 96 /* Check whether we have to allocate the buffer ourselves. */ 97 ck->own = ((ck->buf = buf) == NULL); 98 if (ck->own) { 99 ck->buf = malloc(size); 100 if (ck->buf == NULL) { 101 free(ck); 102 return (NULL); 103 } 104 } 105 106 /* 107 * POSIX distinguishes between w+ and r+, in that w+ is supposed to 108 * truncate the buffer. 109 */ 110 if (ck->own || mode[0] == 'w') { 111 ck->buf[0] = '\0'; 112 } 113 114 /* Check for binary mode. */ 115 ck->bin = strchr(mode, 'b') != NULL; 116 117 /* 118 * The size of the current buffer contents is set depending on the 119 * mode: 120 * 121 * for append (text-mode), the position of the first NULL byte, or the 122 * size of the buffer if none is found 123 * 124 * for append (binary-mode), the size of the buffer 125 * 126 * for read, the size of the buffer 127 * 128 * for write, 0 129 */ 130 switch (mode[0]) { 131 case 'a': 132 ck->off = ck->len = strnlen(ck->buf, ck->size); 133 break; 134 case 'r': 135 ck->len = size; 136 break; 137 case 'w': 138 ck->len = 0; 139 break; 140 } 141 142 f = funopen(ck, 143 flags & O_WRONLY ? NULL : fmemopen_read, 144 flags & O_RDONLY ? NULL : fmemopen_write, 145 fmemopen_seek, fmemopen_close); 146 147 if (f == NULL) { 148 if (ck->own) 149 free(ck->buf); 150 free(ck); 151 return (NULL); 152 } 153 154 if (mode[0] == 'a') 155 f->_flags |= __SAPP; 156 157 /* 158 * Turn off buffering, so a write past the end of the buffer 159 * correctly returns a short object count. 160 */ 161 setvbuf(f, NULL, _IONBF, 0); 162 163 return (f); 164 } 165 166 static int 167 fmemopen_read(void *cookie, char *buf, int nbytes) 168 { 169 struct fmemopen_cookie *ck = cookie; 170 171 if (nbytes > ck->len - ck->off) 172 nbytes = ck->len - ck->off; 173 174 if (nbytes == 0) 175 return (0); 176 177 memcpy(buf, ck->buf + ck->off, nbytes); 178 179 ck->off += nbytes; 180 181 return (nbytes); 182 } 183 184 static int 185 fmemopen_write(void *cookie, const char *buf, int nbytes) 186 { 187 struct fmemopen_cookie *ck = cookie; 188 189 if (nbytes > ck->size - ck->off) 190 nbytes = ck->size - ck->off; 191 192 if (nbytes == 0) 193 return (0); 194 195 memcpy(ck->buf + ck->off, buf, nbytes); 196 197 ck->off += nbytes; 198 199 if (ck->off > ck->len) 200 ck->len = ck->off; 201 202 /* 203 * We append a NULL byte if all these conditions are met: 204 * - the buffer is not binary 205 * - the buffer is not full 206 * - the data just written doesn't already end with a NULL byte 207 */ 208 if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0') 209 ck->buf[ck->off] = '\0'; 210 211 return (nbytes); 212 } 213 214 static fpos_t 215 fmemopen_seek(void *cookie, fpos_t offset, int whence) 216 { 217 struct fmemopen_cookie *ck = cookie; 218 219 220 switch (whence) { 221 case SEEK_SET: 222 if (offset > ck->size) { 223 errno = EINVAL; 224 return (-1); 225 } 226 ck->off = offset; 227 break; 228 229 case SEEK_CUR: 230 if (ck->off + offset > ck->size) { 231 errno = EINVAL; 232 return (-1); 233 } 234 ck->off += offset; 235 break; 236 237 case SEEK_END: 238 if (offset > 0 || -offset > ck->len) { 239 errno = EINVAL; 240 return (-1); 241 } 242 ck->off = ck->len + offset; 243 break; 244 245 default: 246 errno = EINVAL; 247 return (-1); 248 } 249 250 return (ck->off); 251 } 252 253 static int 254 fmemopen_close(void *cookie) 255 { 256 struct fmemopen_cookie *ck = cookie; 257 258 if (ck->own) 259 free(ck->buf); 260 261 free(ck); 262 263 return (0); 264 } 265