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