diff options
Diffstat (limited to 'newlib/libc/stdio/open_memstream.c')
-rw-r--r-- | newlib/libc/stdio/open_memstream.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/newlib/libc/stdio/open_memstream.c b/newlib/libc/stdio/open_memstream.c new file mode 100644 index 000000000..6a0680fbd --- /dev/null +++ b/newlib/libc/stdio/open_memstream.c @@ -0,0 +1,330 @@ +/* Copyright (C) 2007 Eric Blake + * Permission to use, copy, modify, and distribute this software + * is freely granted, provided that this notice is preserved. + */ + +/* +FUNCTION +<<open_memstream>>---open a write stream around an arbitrary-length string + +INDEX + open_memstream + +ANSI_SYNOPSIS + #include <stdio.h> + FILE *open_memstream(char **restrict <[buf]>, + size_t *restrict <[size]>); + +DESCRIPTION +<<open_memstream>> creates a seekable <<FILE>> stream that wraps an +arbitrary-length buffer, created as if by <<malloc>>. The current +contents of *<[buf]> are ignored; this implementation uses *<[size]> +as a hint of the maximum size expected, but does not fail if the hint +was wrong. The parameters <[buf]> and <[size]> are later stored +through following any call to <<fflush>> or <<fclose>>, set to the +current address and usable size of the allocated string; although +after fflush, the pointer is only valid until another stream operation +that results in a write. Behavior is undefined if the user alters +either *<[buf]> or *<[size]> prior to <<fclose>>. + +The stream is write-only, since the user can directly read *<[buf]> +after a flush; see <<fmemopen>> for a way to wrap a string with a +readable stream. The user is responsible for calling <<free>> on +the final *<[buf]> after <<fclose>>. + +Any time the stream is flushed, a NUL byte is written at the current +position (but is not counted in the buffer length), so that the string +is always NUL-terminated after at most *<[size]> bytes. However, data +previously written beyond the current stream offset is not lost, and +the NUL byte written during a flush is restored to its previous value +when seeking elsewhere in the string. + +RETURNS +The return value is an open FILE pointer on success. On error, +<<NULL>> is returned, and <<errno>> will be set to EINVAL if <[buf]> +or <[size]> is NULL, ENOMEM if memory could not be allocated, or +EMFILE if too many streams are already open. + +PORTABILITY +This function is being added to POSIX 200x, but is not in POSIX 2001. + +Supporting OS subroutines required: <<sbrk>>. +*/ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/lock.h> +#include "local.h" + +#ifndef __LARGE64_FILES +# define OFF_T off_t +#else +# define OFF_T _off64_t +#endif + +/* Describe details of an open memstream. */ +typedef struct memstream { + void *storage; /* storage to free on close */ + char **pbuf; /* pointer to the current buffer */ + size_t *psize; /* pointer to the current size, smaller of pos or eof */ + size_t pos; /* current position */ + size_t eof; /* current file size */ + size_t max; /* current malloc buffer size, always > eof */ + char saved; /* saved character that lived at *psize before NUL */ +} memstream; + +/* Write up to non-zero N bytes of BUF into the stream described by COOKIE, + returning the number of bytes written or EOF on failure. */ +static _READ_WRITE_RETURN_TYPE +_DEFUN(memwriter, (ptr, cookie, buf, n), + struct _reent *ptr _AND + void *cookie _AND + const char *buf _AND + int n) +{ + memstream *c = (memstream *) cookie; + char *cbuf = *c->pbuf; + + /* size_t is unsigned, but off_t is signed. Don't let stream get so + big that user cannot do ftello. */ + if (sizeof (OFF_T) == sizeof (size_t) && (ssize_t) (c->pos + n) < 0) + { + ptr->_errno = EFBIG; + return EOF; + } + /* Grow the buffer, if necessary. Choose a geometric growth factor + to avoid quadratic realloc behavior, but use a rate less than + (1+sqrt(5))/2 to accomodate malloc overhead. Overallocate, so + that we can add a trailing \0 without reallocating. The new + allocation should thus be max(prev_size*1.5, c->pos+n+1). */ + if (c->pos + n >= c->max) + { + size_t newsize = c->max * 3 / 2; + if (newsize < c->pos + n + 1) + newsize = c->pos + n + 1; + cbuf = _realloc_r (ptr, cbuf, newsize); + if (! cbuf) + return EOF; /* errno already set to ENOMEM */ + *c->pbuf = cbuf; + c->max = newsize; + } + /* If we have previously done a seek beyond eof, ensure all + intermediate bytes are NUL. */ + if (c->pos > c->eof) + memset (cbuf + c->eof, '\0', c->pos - c->eof); + memcpy (cbuf + c->pos, buf, n); + c->pos += n; + /* If the user has previously written further, remember what the + trailing NUL is overwriting. Otherwise, extend the stream. */ + if (c->pos > c->eof) + c->eof = c->pos; + else + c->saved = cbuf[c->pos]; + cbuf[c->pos] = '\0'; + *c->psize = c->pos; + return n; +} + +/* Seek to position POS relative to WHENCE within stream described by + COOKIE; return resulting position or fail with EOF. */ +static _fpos_t +_DEFUN(memseeker, (ptr, cookie, pos, whence), + struct _reent *ptr _AND + void *cookie _AND + _fpos_t pos _AND + int whence) +{ + memstream *c = (memstream *) cookie; + OFF_T offset = (OFF_T) pos; + + if (whence == SEEK_CUR) + offset += c->pos; + else if (whence == SEEK_END) + offset += c->eof; + if (offset < 0) + { + ptr->_errno = EINVAL; + offset = -1; + } + else if ((size_t) offset != offset) + { + ptr->_errno = ENOSPC; + offset = -1; + } +#ifdef __LARGE64_FILES + else if ((_fpos_t) offset != offset) + { + ptr->_errno = EOVERFLOW; + offset = -1; + } +#endif /* __LARGE64_FILES */ + else + { + if (c->pos < c->eof) + { + (*c->pbuf)[c->pos] = c->saved; + c->saved = '\0'; + } + c->pos = offset; + if (c->pos < c->eof) + { + c->saved = (*c->pbuf)[c->pos]; + (*c->pbuf)[c->pos] = '\0'; + *c->psize = c->pos; + } + else + *c->psize = c->eof; + } + return (_fpos_t) offset; +} + +/* Seek to position POS relative to WHENCE within stream described by + COOKIE; return resulting position or fail with EOF. */ +#ifdef __LARGE64_FILES +static _fpos64_t +_DEFUN(memseeker64, (ptr, cookie, pos, whence), + struct _reent *ptr _AND + void *cookie _AND + _fpos64_t pos _AND + int whence) +{ + _off64_t offset = (_off64_t) pos; + memstream *c = (memstream *) cookie; + + if (whence == SEEK_CUR) + offset += c->pos; + else if (whence == SEEK_END) + offset += c->eof; + if (offset < 0) + { + ptr->_errno = EINVAL; + offset = -1; + } + else if ((size_t) offset != offset) + { + ptr->_errno = ENOSPC; + offset = -1; + } + else + { + if (c->pos < c->eof) + { + (*c->pbuf)[c->pos] = c->saved; + c->saved = '\0'; + } + c->pos = offset; + if (c->pos < c->eof) + { + c->saved = (*c->pbuf)[c->pos]; + (*c->pbuf)[c->pos] = '\0'; + *c->psize = c->pos; + } + else + *c->psize = c->eof; + } + return (_fpos64_t) offset; +} +#endif /* __LARGE64_FILES */ + +/* Reclaim resources used by stream described by COOKIE. */ +static int +_DEFUN(memcloser, (ptr, cookie), + struct _reent *ptr _AND + void *cookie) +{ + memstream *c = (memstream *) cookie; + char *buf; + + /* Be nice and try to reduce any unused memory. */ + buf = _realloc_r (ptr, *c->pbuf, *c->psize + 1); + if (buf) + *c->pbuf = buf; + _free_r (ptr, c->storage); + return 0; +} + +/* Open a memstream that tracks a dynamic buffer in BUF and SIZE. + Return the new stream, or fail with NULL. */ +FILE * +_DEFUN(_open_memstream_r, (ptr, buf, size), + struct _reent *ptr _AND + char **buf _AND + size_t *size) +{ + FILE *fp; + memstream *c; + int flags; + + if (!buf || !size) + { + ptr->_errno = EINVAL; + return NULL; + } + if ((fp = __sfp (ptr)) == NULL) + return NULL; + if ((c = (memstream *) _malloc_r (ptr, sizeof *c)) == NULL) + { + __sfp_lock_acquire (); + fp->_flags = 0; /* release */ +#ifndef __SINGLE_THREAD__ + __lock_close_recursive (fp->_lock); +#endif + __sfp_lock_release (); + return NULL; + } + /* Use *size as a hint for initial sizing, but bound the initial + malloc between 64 bytes (same as asprintf, to avoid frequent + mallocs on small strings) and 64k bytes (to avoid overusing the + heap if *size was garbage). */ + c->max = *size; + if (c->max < 64) + c->max = 64; + else if (c->max > 64 * 1024) + c->max = 64 * 1024; + *size = 0; + *buf = _malloc_r (ptr, c->max); + if (!*buf) + { + __sfp_lock_acquire (); + fp->_flags = 0; /* release */ +#ifndef __SINGLE_THREAD__ + __lock_close_recursive (fp->_lock); +#endif + __sfp_lock_release (); + _free_r (ptr, c); + return NULL; + } + **buf = '\0'; + + c->storage = c; + c->pbuf = buf; + c->psize = size; + c->eof = 0; + c->saved = '\0'; + + _flockfile (fp); + fp->_file = -1; + fp->_flags = __SWR; + fp->_cookie = c; + fp->_read = NULL; + fp->_write = memwriter; + fp->_seek = memseeker; +#ifdef __LARGE64_FILES + fp->_seek64 = memseeker64; + fp->_flags |= __SL64; +#endif + fp->_close = memcloser; + _funlockfile (fp); + return fp; +} + +#ifndef _REENT_ONLY +FILE * +_DEFUN(open_memstream, (buf, size), + char **buf _AND + size_t *size) +{ + return _open_memstream_r (_REENT, buf, size); +} +#endif /* !_REENT_ONLY */ |