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 /* 153 * Turn off buffering, so a write past the end of the buffer 154 * correctly returns a short object count. 155 */ 156 setvbuf(f, NULL, _IONBF, 0); 157 158 return (f); 159 } 160 161 static int 162 fmemopen_read(void *cookie, char *buf, int nbytes) 163 { 164 struct fmemopen_cookie *ck = cookie; 165 166 if (nbytes > ck->len - ck->off) 167 nbytes = ck->len - ck->off; 168 169 if (nbytes == 0) 170 return (0); 171 172 memcpy(buf, ck->buf + ck->off, nbytes); 173 174 ck->off += nbytes; 175 176 return (nbytes); 177 } 178 179 static int 180 fmemopen_write(void *cookie, const char *buf, int nbytes) 181 { 182 struct fmemopen_cookie *ck = cookie; 183 184 if (nbytes > ck->size - ck->off) 185 nbytes = ck->size - ck->off; 186 187 if (nbytes == 0) 188 return (0); 189 190 memcpy(ck->buf + ck->off, buf, nbytes); 191 192 ck->off += nbytes; 193 194 if (ck->off > ck->len) 195 ck->len = ck->off; 196 197 /* 198 * We append a NULL byte if all these conditions are met: 199 * - the buffer is not binary 200 * - the buffer is not full 201 * - the data just written doesn't already end with a NULL byte 202 */ 203 if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0') 204 ck->buf[ck->off] = '\0'; 205 206 return (nbytes); 207 } 208 209 static fpos_t 210 fmemopen_seek(void *cookie, fpos_t offset, int whence) 211 { 212 struct fmemopen_cookie *ck = cookie; 213 214 215 switch (whence) { 216 case SEEK_SET: 217 if (offset > ck->size) { 218 errno = EINVAL; 219 return (-1); 220 } 221 ck->off = offset; 222 break; 223 224 case SEEK_CUR: 225 if (ck->off + offset > ck->size) { 226 errno = EINVAL; 227 return (-1); 228 } 229 ck->off += offset; 230 break; 231 232 case SEEK_END: 233 if (offset > 0 || -offset > ck->len) { 234 errno = EINVAL; 235 return (-1); 236 } 237 ck->off = ck->len + offset; 238 break; 239 240 default: 241 errno = EINVAL; 242 return (-1); 243 } 244 245 return (ck->off); 246 } 247 248 static int 249 fmemopen_close(void *cookie) 250 { 251 struct fmemopen_cookie *ck = cookie; 252 253 if (ck->own) 254 free(ck->buf); 255 256 free(ck); 257 258 return (0); 259 } 260