/* Open a write stream around a malloc'd string. Copyright (C) 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Written by Eric Blake , 2010. */ #include "config.h" /* Specification. */ #include #include #include #include #include // #include "verify.h" #if !HAVE_OPEN_MEMSTREAM #if !HAVE_FUNOPEN # error Sorry, not ported to your platform yet #else FILE * open_memstream (char **buf, size_t *len); # define INITIAL_ALLOC 64 struct data { char **buf; /* User's argument. */ size_t *len; /* User's argument. Smaller of pos or eof. */ size_t pos; /* Current position. */ size_t eof; /* End-of-file position. */ size_t allocated; /* Allocated size of *buf, always > eof. */ char c; /* Temporary storage for byte overwritten by NUL, if pos < eof. */ }; typedef struct data data; /* Stupid BSD interface uses int/int instead of ssize_t/size_t. */ //verify (sizeof (int) <= sizeof (size_t)); //verify (sizeof (int) <= sizeof (ssize_t)); static int mem_write (void *c, const char *buf, int n) { data *cookie = c; char *cbuf = *cookie->buf; /* Be sure we don't overflow. */ if ((ssize_t) (cookie->pos + n) < 0) { errno = EFBIG; return EOF; } /* Grow the buffer, if necessary. Use geometric growth to avoid quadratic realloc behavior. Overallocate, to accomodate the requirement to always place a trailing NUL not counted by length. Thus, we want max(prev_size*1.5, cookie->posn1). */ if (cookie->allocated <= cookie->pos + n) { size_t newsize = cookie->allocated * 3 / 2; if (newsize < cookie->pos + n + 1) newsize = cookie->pos + n + 1; cbuf = realloc (cbuf, newsize); if (!cbuf) return EOF; *cookie->buf = cbuf; cookie->allocated = newsize; } /* If we have previously done a seek beyond eof, ensure all intermediate bytges are NUL. */ if (cookie->eof < cookie->pos) memset (cbuf + cookie->eof, '\0', cookie->pos - cookie->eof); memcpy (cbuf + cookie->pos, buf, n); cookie->pos = n; /* If the user has previously written beyond the current position, remember what the trailing NUL is overwriting. Otherwise, extend the stream. */ if (cookie->eof < cookie->pos) cookie->eof = cookie->pos; else cookie->c = cbuf[cookie->pos]; cbuf[cookie->pos] = '\0'; *cookie->len = cookie->pos; return n; } static fpos_t mem_seek (void *c, fpos_t pos, int whence) { data *cookie = c; off_t offset = pos; if (whence == SEEK_CUR) offset = cookie->pos; else if (whence == SEEK_END) offset = cookie->eof; if (offset < 0) { errno = EINVAL; offset = -1; } else if ((size_t) offset != offset) { errno = ENOSPC; offset = -1; } else { if (cookie->pos < cookie->eof) { (*cookie->buf)[cookie->pos] = cookie->c; cookie->c = '\0'; } cookie->pos = offset; if (cookie->pos < cookie->eof) { cookie->c = (*cookie->buf)[cookie->pos]; (*cookie->buf)[cookie->pos] = '\0'; *cookie->len = cookie->pos; } else *cookie->len = cookie->eof; } return offset; } static int mem_close (void *c) { data *cookie = c; char *buf; /* Be nice and try to reduce excess memory. */ buf = realloc (*cookie->buf, *cookie->len + 1); if (buf) *cookie->buf = buf; free (cookie); return 0; } FILE * open_memstream (char **buf, size_t *len) { FILE *f; data *cookie; if (!buf || !len) { errno = EINVAL; return NULL; } if (!(cookie = malloc (sizeof *cookie))) return NULL; if (!(*buf = malloc (INITIAL_ALLOC))) { free (cookie); errno = ENOMEM; return NULL; } **buf = '\0'; *len = 0; f = funopen (cookie, NULL, mem_write, mem_seek, mem_close); if (!f) { int saved_errno = errno; free (cookie); errno = saved_errno; } else { cookie->buf = buf; cookie->len = len; cookie->pos = 0; cookie->eof = 0; cookie->c = '\0'; cookie->allocated = INITIAL_ALLOC; } return f; } #endif /* HAVE_FUNOPEN */ #endif /* HAVE_OPEN_MEMSTREAM*/