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