199 lines
4.8 KiB
C
199 lines
4.8 KiB
C
/* 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 <http://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by Eric Blake <e...@byu.net>, 2010. */
|
|
|
|
|
|
#include "config.h"
|
|
|
|
/* Specification. */
|
|
#include <stdio.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// #include "verify.h"
|
|
|
|
#if !HAVE_OPEN_MEMSTREAM && !_WIN32
|
|
|
|
#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*/
|