1 /*- 2 * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org> 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <fcntl.h> 30 #include <stdbool.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <errno.h> 35 #include "local.h" 36 37 struct fmemopen_cookie 38 { 39 char *buf; /* pointer to the memory region */ 40 bool own; /* did we allocate the buffer ourselves? */ 41 char bin; /* is this a binary buffer? */ 42 size_t size; /* buffer length in bytes */ 43 size_t len; /* data length in bytes */ 44 size_t off; /* current offset into the buffer */ 45 }; 46 47 static int fmemopen_read(void *cookie, char *buf, int nbytes); 48 static int fmemopen_write(void *cookie, const char *buf, int nbytes); 49 static fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence); 50 static int fmemopen_close(void *cookie); 51 52 FILE * 53 fmemopen(void * __restrict buf, size_t size, const char * __restrict mode) 54 { 55 struct fmemopen_cookie *ck; 56 FILE *f; 57 int flags, rc; 58 59 /* 60 * POSIX says we shall return EINVAL if size is 0. 61 */ 62 if (size == 0) { 63 errno = EINVAL; 64 return (NULL); 65 } 66 67 /* 68 * Retrieve the flags as used by open(2) from the mode argument, and 69 * validate them. 70 */ 71 rc = __sflags(mode, &flags); 72 if (rc == 0) { 73 errno = EINVAL; 74 return (NULL); 75 } 76 77 /* 78 * There's no point in requiring an automatically allocated buffer 79 * in write-only mode. 80 */ 81 if (!(flags & O_RDWR) && buf == NULL) { 82 errno = EINVAL; 83 return (NULL); 84 } 85 86 ck = malloc(sizeof(struct fmemopen_cookie)); 87 if (ck == NULL) { 88 return (NULL); 89 } 90 91 ck->off = 0; 92 ck->size = size; 93 94 /* Check whether we have to allocate the buffer ourselves. */ 95 ck->own = ((ck->buf = buf) == NULL); 96 if (ck->own) { 97 ck->buf = malloc(size); 98 if (ck->buf == NULL) { 99 free(ck); 100 return (NULL); 101 } 102 } 103 104 /* 105 * POSIX distinguishes between w+ and r+, in that w+ is supposed to 106 * truncate the buffer. 107 */ 108 if (ck->own || mode[0] == 'w') { 109 ck->buf[0] = '\0'; 110 } 111 112 /* Check for binary mode. */ 113 ck->bin = strchr(mode, 'b') != NULL; 114 115 /* 116 * The size of the current buffer contents is set depending on the 117 * mode: 118 * 119 * for append (text-mode), the position of the first NULL byte, or the 120 * size of the buffer if none is found 121 * 122 * for append (binary-mode), the size of the buffer 123 * 124 * for read, the size of the buffer 125 * 126 * for write, 0 127 */ 128 switch (mode[0]) { 129 case 'a': 130 ck->off = ck->len = strnlen(ck->buf, ck->size); 131 break; 132 case 'r': 133 ck->len = size; 134 break; 135 case 'w': 136 ck->len = 0; 137 break; 138 } 139 140 f = funopen(ck, 141 flags & O_WRONLY ? NULL : fmemopen_read, 142 flags & O_RDONLY ? NULL : fmemopen_write, 143 fmemopen_seek, fmemopen_close); 144 145 if (f == NULL) { 146 if (ck->own) 147 free(ck->buf); 148 free(ck); 149 return (NULL); 150 } 151 152 if (mode[0] == 'a') 153 f->_flags |= __SAPP; 154 155 /* 156 * Turn off buffering, so a write past the end of the buffer 157 * correctly returns a short object count. 158 */ 159 setvbuf(f, NULL, _IONBF, 0); 160 161 return (f); 162 } 163 164 static int 165 fmemopen_read(void *cookie, char *buf, int nbytes) 166 { 167 struct fmemopen_cookie *ck = cookie; 168 169 if (nbytes > ck->len - ck->off) 170 nbytes = ck->len - ck->off; 171 172 if (nbytes == 0) 173 return (0); 174 175 memcpy(buf, ck->buf + ck->off, nbytes); 176 177 ck->off += nbytes; 178 179 return (nbytes); 180 } 181 182 static int 183 fmemopen_write(void *cookie, const char *buf, int nbytes) 184 { 185 struct fmemopen_cookie *ck = cookie; 186 187 if (nbytes > ck->size - ck->off) 188 nbytes = ck->size - ck->off; 189 190 if (nbytes == 0) 191 return (0); 192 193 memcpy(ck->buf + ck->off, buf, nbytes); 194 195 ck->off += nbytes; 196 197 if (ck->off > ck->len) 198 ck->len = ck->off; 199 200 /* 201 * We append a NULL byte if all these conditions are met: 202 * - the buffer is not binary 203 * - the buffer is not full 204 * - the data just written doesn't already end with a NULL byte 205 */ 206 if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0') 207 ck->buf[ck->off] = '\0'; 208 209 return (nbytes); 210 } 211 212 static fpos_t 213 fmemopen_seek(void *cookie, fpos_t offset, int whence) 214 { 215 struct fmemopen_cookie *ck = cookie; 216 217 218 switch (whence) { 219 case SEEK_SET: 220 if (offset > ck->size) { 221 errno = EINVAL; 222 return (-1); 223 } 224 ck->off = offset; 225 break; 226 227 case SEEK_CUR: 228 if (ck->off + offset > ck->size) { 229 errno = EINVAL; 230 return (-1); 231 } 232 ck->off += offset; 233 break; 234 235 case SEEK_END: 236 if (offset > 0 || -offset > ck->len) { 237 errno = EINVAL; 238 return (-1); 239 } 240 ck->off = ck->len + offset; 241 break; 242 243 default: 244 errno = EINVAL; 245 return (-1); 246 } 247 248 return (ck->off); 249 } 250 251 static int 252 fmemopen_close(void *cookie) 253 { 254 struct fmemopen_cookie *ck = cookie; 255 256 if (ck->own) 257 free(ck->buf); 258 259 free(ck); 260 261 return (0); 262 } 263