1246120Sgahr/*- 2246206Sgahr * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org> 3246206Sgahr * 4246206Sgahr * Redistribution and use in source and binary forms, with or without 5246206Sgahr * modification, are permitted provided that the following conditions 6246206Sgahr * are met: 7246206Sgahr * 1. Redistributions of source code must retain the above copyright 8246206Sgahr * notice, this list of conditions and the following disclaimer. 9246206Sgahr * 2. Redistributions in binary form must reproduce the above copyright 10246206Sgahr * notice, this list of conditions and the following disclaimer in the 11246206Sgahr * documentation and/or other materials provided with the distribution. 12246206Sgahr * 13246206Sgahr * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14246206Sgahr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15246206Sgahr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16246206Sgahr * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17246206Sgahr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18246206Sgahr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19246206Sgahr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20246206Sgahr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21246206Sgahr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22246206Sgahr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23246206Sgahr * SUCH DAMAGE. 24246206Sgahr */ 25246120Sgahr 26246120Sgahr#include <sys/cdefs.h> 27246120Sgahr__FBSDID("$FreeBSD$"); 28246120Sgahr 29246148Sgahr#include <fcntl.h> 30246206Sgahr#include <stdbool.h> 31246120Sgahr#include <stdio.h> 32246120Sgahr#include <stdlib.h> 33246120Sgahr#include <string.h> 34246120Sgahr#include <errno.h> 35246148Sgahr#include "local.h" 36246120Sgahr 37246148Sgahrstruct fmemopen_cookie 38246120Sgahr{ 39246148Sgahr char *buf; /* pointer to the memory region */ 40246206Sgahr bool own; /* did we allocate the buffer ourselves? */ 41246148Sgahr char bin; /* is this a binary buffer? */ 42246148Sgahr size_t size; /* buffer length in bytes */ 43246148Sgahr size_t len; /* data length in bytes */ 44246148Sgahr size_t off; /* current offset into the buffer */ 45246120Sgahr}; 46246120Sgahr 47246206Sgahrstatic int fmemopen_read(void *cookie, char *buf, int nbytes); 48246206Sgahrstatic int fmemopen_write(void *cookie, const char *buf, int nbytes); 49246206Sgahrstatic fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence); 50246206Sgahrstatic int fmemopen_close(void *cookie); 51246120Sgahr 52246120SgahrFILE * 53246206Sgahrfmemopen(void * __restrict buf, size_t size, const char * __restrict mode) 54246120Sgahr{ 55246148Sgahr struct fmemopen_cookie *ck; 56246148Sgahr FILE *f; 57246148Sgahr int flags, rc; 58246148Sgahr 59246206Sgahr /* 60246148Sgahr * Retrieve the flags as used by open(2) from the mode argument, and 61246148Sgahr * validate them. 62246206Sgahr */ 63246206Sgahr rc = __sflags(mode, &flags); 64246148Sgahr if (rc == 0) { 65246148Sgahr errno = EINVAL; 66246148Sgahr return (NULL); 67246148Sgahr } 68246148Sgahr 69246206Sgahr /* 70246148Sgahr * There's no point in requiring an automatically allocated buffer 71246148Sgahr * in write-only mode. 72246148Sgahr */ 73246148Sgahr if (!(flags & O_RDWR) && buf == NULL) { 74246148Sgahr errno = EINVAL; 75246148Sgahr return (NULL); 76246148Sgahr } 77246148Sgahr 78246206Sgahr ck = malloc(sizeof(struct fmemopen_cookie)); 79246120Sgahr if (ck == NULL) { 80246120Sgahr return (NULL); 81246120Sgahr } 82246120Sgahr 83246148Sgahr ck->off = 0; 84246148Sgahr ck->size = size; 85246120Sgahr 86246148Sgahr /* Check whether we have to allocate the buffer ourselves. */ 87246120Sgahr ck->own = ((ck->buf = buf) == NULL); 88246120Sgahr if (ck->own) { 89246206Sgahr ck->buf = malloc(size); 90246120Sgahr if (ck->buf == NULL) { 91246206Sgahr free(ck); 92246120Sgahr return (NULL); 93246120Sgahr } 94246148Sgahr } 95246148Sgahr 96246148Sgahr /* 97246148Sgahr * POSIX distinguishes between w+ and r+, in that w+ is supposed to 98246148Sgahr * truncate the buffer. 99246148Sgahr */ 100246148Sgahr if (ck->own || mode[0] == 'w') { 101246120Sgahr ck->buf[0] = '\0'; 102246120Sgahr } 103246120Sgahr 104246148Sgahr /* Check for binary mode. */ 105246148Sgahr ck->bin = strchr(mode, 'b') != NULL; 106246120Sgahr 107246148Sgahr /* 108246148Sgahr * The size of the current buffer contents is set depending on the 109246148Sgahr * mode: 110246206Sgahr * 111246148Sgahr * for append (text-mode), the position of the first NULL byte, or the 112246148Sgahr * size of the buffer if none is found 113246148Sgahr * 114246148Sgahr * for append (binary-mode), the size of the buffer 115246206Sgahr * 116246148Sgahr * for read, the size of the buffer 117246206Sgahr * 118246148Sgahr * for write, 0 119246148Sgahr */ 120246148Sgahr switch (mode[0]) { 121246148Sgahr case 'a': 122246148Sgahr if (ck->bin) { 123246206Sgahr /* 124246206Sgahr * This isn't useful, since the buffer isn't allowed 125246206Sgahr * to grow. 126246148Sgahr */ 127246148Sgahr ck->off = ck->len = size; 128246148Sgahr } else 129246148Sgahr ck->off = ck->len = strnlen(ck->buf, ck->size); 130246148Sgahr break; 131246148Sgahr case 'r': 132246148Sgahr ck->len = size; 133246148Sgahr break; 134246148Sgahr case 'w': 135246148Sgahr ck->len = 0; 136246148Sgahr break; 137246148Sgahr } 138246148Sgahr 139246206Sgahr f = funopen(ck, 140246148Sgahr flags & O_WRONLY ? NULL : fmemopen_read, 141246148Sgahr flags & O_RDONLY ? NULL : fmemopen_write, 142246120Sgahr fmemopen_seek, fmemopen_close); 143246120Sgahr 144246120Sgahr if (f == NULL) { 145246120Sgahr if (ck->own) 146246206Sgahr free(ck->buf); 147246206Sgahr free(ck); 148246120Sgahr return (NULL); 149246120Sgahr } 150246120Sgahr 151246148Sgahr /* 152246148Sgahr * Turn off buffering, so a write past the end of the buffer 153246148Sgahr * correctly returns a short object count. 154246148Sgahr */ 155246206Sgahr setvbuf(f, NULL, _IONBF, 0); 156246120Sgahr 157246120Sgahr return (f); 158246120Sgahr} 159246120Sgahr 160246120Sgahrstatic int 161246206Sgahrfmemopen_read(void *cookie, char *buf, int nbytes) 162246120Sgahr{ 163246148Sgahr 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 171246206Sgahr memcpy(buf, ck->buf + ck->off, nbytes); 172246120Sgahr 173246120Sgahr ck->off += nbytes; 174246120Sgahr 175246120Sgahr return (nbytes); 176246120Sgahr} 177246120Sgahr 178246120Sgahrstatic int 179246206Sgahrfmemopen_write(void *cookie, const char *buf, int nbytes) 180246120Sgahr{ 181246148Sgahr struct fmemopen_cookie *ck = cookie; 182246120Sgahr 183246148Sgahr if (nbytes > ck->size - ck->off) 184246148Sgahr nbytes = ck->size - ck->off; 185246120Sgahr 186246120Sgahr if (nbytes == 0) 187246120Sgahr return (0); 188246120Sgahr 189246206Sgahr memcpy(ck->buf + ck->off, buf, nbytes); 190246120Sgahr 191246120Sgahr ck->off += nbytes; 192246120Sgahr 193246148Sgahr if (ck->off > ck->len) 194246148Sgahr ck->len = ck->off; 195246148Sgahr 196246148Sgahr /* 197246148Sgahr * We append a NULL byte if all these conditions are met: 198246148Sgahr * - the buffer is not binary 199246148Sgahr * - the buffer is not full 200246148Sgahr * - the data just written doesn't already end with a NULL byte 201246148Sgahr */ 202246148Sgahr 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 209246206Sgahrfmemopen_seek(void *cookie, fpos_t offset, int whence) 210246120Sgahr{ 211246148Sgahr struct fmemopen_cookie *ck = cookie; 212246120Sgahr 213246120Sgahr 214246120Sgahr switch (whence) { 215246120Sgahr case SEEK_SET: 216246148Sgahr if (offset > ck->size) { 217246120Sgahr errno = EINVAL; 218246120Sgahr return (-1); 219246120Sgahr } 220246120Sgahr ck->off = offset; 221246120Sgahr break; 222246120Sgahr 223246120Sgahr case SEEK_CUR: 224246148Sgahr 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 248246206Sgahrfmemopen_close(void *cookie) 249246120Sgahr{ 250246148Sgahr struct fmemopen_cookie *ck = cookie; 251246120Sgahr 252246120Sgahr if (ck->own) 253246206Sgahr free(ck->buf); 254246120Sgahr 255246206Sgahr free(ck); 256246120Sgahr 257246120Sgahr return (0); 258246120Sgahr} 259