1246120Sgahr/*- 2252343Sjhb * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org> 3252343Sjhb * 4252343Sjhb * Redistribution and use in source and binary forms, with or without 5252343Sjhb * modification, are permitted provided that the following conditions 6252343Sjhb * are met: 7252343Sjhb * 1. Redistributions of source code must retain the above copyright 8252343Sjhb * notice, this list of conditions and the following disclaimer. 9252343Sjhb * 2. Redistributions in binary form must reproduce the above copyright 10252343Sjhb * notice, this list of conditions and the following disclaimer in the 11252343Sjhb * documentation and/or other materials provided with the distribution. 12252343Sjhb * 13252343Sjhb * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14252343Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15252343Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16252343Sjhb * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17252343Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18252343Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19252343Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20252343Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21252343Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22252343Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23252343Sjhb * SUCH DAMAGE. 24252343Sjhb */ 25246120Sgahr 26246120Sgahr#include <sys/cdefs.h> 27246120Sgahr__FBSDID("$FreeBSD$"); 28246120Sgahr 29252343Sjhb#include <fcntl.h> 30252343Sjhb#include <stdbool.h> 31246120Sgahr#include <stdio.h> 32246120Sgahr#include <stdlib.h> 33246120Sgahr#include <string.h> 34246120Sgahr#include <errno.h> 35252343Sjhb#include "local.h" 36246120Sgahr 37252343Sjhbstruct fmemopen_cookie 38246120Sgahr{ 39252343Sjhb char *buf; /* pointer to the memory region */ 40252343Sjhb bool own; /* did we allocate the buffer ourselves? */ 41252343Sjhb char bin; /* is this a binary buffer? */ 42252343Sjhb size_t size; /* buffer length in bytes */ 43252343Sjhb size_t len; /* data length in bytes */ 44252343Sjhb size_t off; /* current offset into the buffer */ 45246120Sgahr}; 46246120Sgahr 47252343Sjhbstatic int fmemopen_read(void *cookie, char *buf, int nbytes); 48252343Sjhbstatic int fmemopen_write(void *cookie, const char *buf, int nbytes); 49252343Sjhbstatic fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence); 50252343Sjhbstatic int fmemopen_close(void *cookie); 51246120Sgahr 52246120SgahrFILE * 53252343Sjhbfmemopen(void * __restrict buf, size_t size, const char * __restrict mode) 54246120Sgahr{ 55252343Sjhb struct fmemopen_cookie *ck; 56252343Sjhb FILE *f; 57252343Sjhb int flags, rc; 58252343Sjhb 59252343Sjhb /* 60252343Sjhb * Retrieve the flags as used by open(2) from the mode argument, and 61252343Sjhb * validate them. 62252343Sjhb */ 63252343Sjhb rc = __sflags(mode, &flags); 64252343Sjhb if (rc == 0) { 65252343Sjhb errno = EINVAL; 66252343Sjhb return (NULL); 67252343Sjhb } 68252343Sjhb 69252343Sjhb /* 70252343Sjhb * There's no point in requiring an automatically allocated buffer 71252343Sjhb * in write-only mode. 72252343Sjhb */ 73252343Sjhb if (!(flags & O_RDWR) && buf == NULL) { 74252343Sjhb errno = EINVAL; 75252343Sjhb return (NULL); 76252343Sjhb } 77252343Sjhb 78252343Sjhb ck = malloc(sizeof(struct fmemopen_cookie)); 79246120Sgahr if (ck == NULL) { 80246120Sgahr return (NULL); 81246120Sgahr } 82246120Sgahr 83252343Sjhb ck->off = 0; 84252343Sjhb ck->size = size; 85246120Sgahr 86252343Sjhb /* Check whether we have to allocate the buffer ourselves. */ 87246120Sgahr ck->own = ((ck->buf = buf) == NULL); 88246120Sgahr if (ck->own) { 89252343Sjhb ck->buf = malloc(size); 90246120Sgahr if (ck->buf == NULL) { 91252343Sjhb free(ck); 92246120Sgahr return (NULL); 93246120Sgahr } 94252343Sjhb } 95252343Sjhb 96252343Sjhb /* 97252343Sjhb * POSIX distinguishes between w+ and r+, in that w+ is supposed to 98252343Sjhb * truncate the buffer. 99252343Sjhb */ 100252343Sjhb if (ck->own || mode[0] == 'w') { 101246120Sgahr ck->buf[0] = '\0'; 102246120Sgahr } 103246120Sgahr 104252343Sjhb /* Check for binary mode. */ 105252343Sjhb ck->bin = strchr(mode, 'b') != NULL; 106246120Sgahr 107252343Sjhb /* 108252343Sjhb * The size of the current buffer contents is set depending on the 109252343Sjhb * mode: 110252343Sjhb * 111252343Sjhb * for append (text-mode), the position of the first NULL byte, or the 112252343Sjhb * size of the buffer if none is found 113252343Sjhb * 114252343Sjhb * for append (binary-mode), the size of the buffer 115252343Sjhb * 116252343Sjhb * for read, the size of the buffer 117252343Sjhb * 118252343Sjhb * for write, 0 119252343Sjhb */ 120252343Sjhb switch (mode[0]) { 121252343Sjhb case 'a': 122252343Sjhb if (ck->bin) { 123252343Sjhb /* 124252343Sjhb * This isn't useful, since the buffer isn't allowed 125252343Sjhb * to grow. 126252343Sjhb */ 127252343Sjhb ck->off = ck->len = size; 128252343Sjhb } else 129252343Sjhb ck->off = ck->len = strnlen(ck->buf, ck->size); 130252343Sjhb break; 131252343Sjhb case 'r': 132252343Sjhb ck->len = size; 133252343Sjhb break; 134252343Sjhb case 'w': 135252343Sjhb ck->len = 0; 136252343Sjhb break; 137252343Sjhb } 138252343Sjhb 139252343Sjhb f = funopen(ck, 140252343Sjhb flags & O_WRONLY ? NULL : fmemopen_read, 141252343Sjhb flags & O_RDONLY ? NULL : fmemopen_write, 142246120Sgahr fmemopen_seek, fmemopen_close); 143246120Sgahr 144246120Sgahr if (f == NULL) { 145246120Sgahr if (ck->own) 146252343Sjhb free(ck->buf); 147252343Sjhb free(ck); 148246120Sgahr return (NULL); 149246120Sgahr } 150246120Sgahr 151252343Sjhb /* 152252343Sjhb * Turn off buffering, so a write past the end of the buffer 153252343Sjhb * correctly returns a short object count. 154252343Sjhb */ 155252343Sjhb setvbuf(f, NULL, _IONBF, 0); 156246120Sgahr 157246120Sgahr return (f); 158246120Sgahr} 159246120Sgahr 160246120Sgahrstatic int 161252343Sjhbfmemopen_read(void *cookie, char *buf, int nbytes) 162246120Sgahr{ 163252343Sjhb struct fmemopen_cookie *ck = cookie; 164246120Sgahr 165246120Sgahr if (nbytes > ck->len - ck->off) 166246120Sgahr nbytes = ck->len - ck->off; 167246120Sgahr 168246120Sgahr if (nbytes == 0) 169246120Sgahr return (0); 170246120Sgahr 171252343Sjhb memcpy(buf, ck->buf + ck->off, nbytes); 172246120Sgahr 173246120Sgahr ck->off += nbytes; 174246120Sgahr 175246120Sgahr return (nbytes); 176246120Sgahr} 177246120Sgahr 178246120Sgahrstatic int 179252343Sjhbfmemopen_write(void *cookie, const char *buf, int nbytes) 180246120Sgahr{ 181252343Sjhb struct fmemopen_cookie *ck = cookie; 182246120Sgahr 183252343Sjhb if (nbytes > ck->size - ck->off) 184252343Sjhb nbytes = ck->size - ck->off; 185246120Sgahr 186246120Sgahr if (nbytes == 0) 187246120Sgahr return (0); 188246120Sgahr 189252343Sjhb memcpy(ck->buf + ck->off, buf, nbytes); 190246120Sgahr 191246120Sgahr ck->off += nbytes; 192246120Sgahr 193252343Sjhb if (ck->off > ck->len) 194252343Sjhb ck->len = ck->off; 195252343Sjhb 196252343Sjhb /* 197252343Sjhb * We append a NULL byte if all these conditions are met: 198252343Sjhb * - the buffer is not binary 199252343Sjhb * - the buffer is not full 200252343Sjhb * - the data just written doesn't already end with a NULL byte 201252343Sjhb */ 202252343Sjhb if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0') 203246120Sgahr ck->buf[ck->off] = '\0'; 204246120Sgahr 205246120Sgahr return (nbytes); 206246120Sgahr} 207246120Sgahr 208246120Sgahrstatic fpos_t 209252343Sjhbfmemopen_seek(void *cookie, fpos_t offset, int whence) 210246120Sgahr{ 211252343Sjhb struct fmemopen_cookie *ck = cookie; 212246120Sgahr 213246120Sgahr 214246120Sgahr switch (whence) { 215246120Sgahr case SEEK_SET: 216252343Sjhb if (offset > ck->size) { 217246120Sgahr errno = EINVAL; 218246120Sgahr return (-1); 219246120Sgahr } 220246120Sgahr ck->off = offset; 221246120Sgahr break; 222246120Sgahr 223246120Sgahr case SEEK_CUR: 224252343Sjhb if (ck->off + offset > ck->size) { 225246120Sgahr errno = EINVAL; 226246120Sgahr return (-1); 227246120Sgahr } 228246120Sgahr ck->off += offset; 229246120Sgahr break; 230246120Sgahr 231246120Sgahr case SEEK_END: 232246120Sgahr if (offset > 0 || -offset > ck->len) { 233246120Sgahr errno = EINVAL; 234246120Sgahr return (-1); 235246120Sgahr } 236246120Sgahr ck->off = ck->len + offset; 237246120Sgahr break; 238246120Sgahr 239246120Sgahr default: 240246120Sgahr errno = EINVAL; 241246120Sgahr return (-1); 242246120Sgahr } 243246120Sgahr 244246120Sgahr return (ck->off); 245246120Sgahr} 246246120Sgahr 247246120Sgahrstatic int 248252343Sjhbfmemopen_close(void *cookie) 249246120Sgahr{ 250252343Sjhb struct fmemopen_cookie *ck = cookie; 251246120Sgahr 252246120Sgahr if (ck->own) 253252343Sjhb free(ck->buf); 254246120Sgahr 255252343Sjhb free(ck); 256246120Sgahr 257246120Sgahr return (0); 258246120Sgahr} 259