0,0 → 1,414 |
/* 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_wmemstream>>---open a write stream around an arbitrary-length string |
|
INDEX |
open_memstream |
INDEX |
open_wmemstream |
|
ANSI_SYNOPSIS |
#include <stdio.h> |
FILE *open_memstream(char **restrict <[buf]>, |
size_t *restrict <[size]>); |
|
#include <wchar.h> |
FILE *open_wmemstream(wchar_t **restrict <[buf]>, |
size_t *restrict <[size]>); |
|
DESCRIPTION |
<<open_memstream>> creates a seekable, byte-oriented <<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>>. |
|
<<open_wmemstream>> is like <<open_memstream>> just with the associated |
stream being wide-oriented. The size set in <[size]> in subsequent |
operations is the number of wide characters. |
|
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 (or wide characters |
in case of <<open_wmemstream>>). However, data previously written beyond |
the current stream offset is not lost, and the NUL value 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 |
POSIX.1-2008 |
|
Supporting OS subroutines required: <<sbrk>>. |
*/ |
|
#include <stdio.h> |
#include <wchar.h> |
#include <errno.h> |
#include <string.h> |
#include <sys/lock.h> |
#include <stdint.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 */ |
union { |
char c; |
wchar_t w; |
} saved; /* saved character that lived at *psize before NUL */ |
int8_t wide; /* wide-oriented (>0) or byte-oriented (<0) */ |
} 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 |
_READ_WRITE_BUFSIZE_TYPE 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 if (c->wide > 0) |
c->saved.w = *(wchar_t *)(cbuf + c->pos); |
else |
c->saved.c = cbuf[c->pos]; |
cbuf[c->pos] = '\0'; |
*c->psize = (c->wide > 0) ? c->pos / sizeof (wchar_t) : 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) |
{ |
if (c->wide > 0) |
*(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w; |
else |
(*c->pbuf)[c->pos] = c->saved.c; |
c->saved.w = L'\0'; |
} |
c->pos = offset; |
if (c->pos < c->eof) |
{ |
if (c->wide > 0) |
{ |
c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos); |
*(wchar_t *)((*c->pbuf) + c->pos) = L'\0'; |
*c->psize = c->pos / sizeof (wchar_t); |
} |
else |
{ |
c->saved.c = (*c->pbuf)[c->pos]; |
(*c->pbuf)[c->pos] = '\0'; |
*c->psize = c->pos; |
} |
} |
else if (c->wide > 0) |
*c->psize = c->eof / sizeof (wchar_t); |
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) |
{ |
if (c->wide > 0) |
*(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w; |
else |
(*c->pbuf)[c->pos] = c->saved.c; |
c->saved.w = L'\0'; |
} |
c->pos = offset; |
if (c->pos < c->eof) |
{ |
if (c->wide > 0) |
{ |
c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos); |
*(wchar_t *)((*c->pbuf) + c->pos) = L'\0'; |
*c->psize = c->pos / sizeof (wchar_t); |
} |
else |
{ |
c->saved.c = (*c->pbuf)[c->pos]; |
(*c->pbuf)[c->pos] = '\0'; |
*c->psize = c->pos; |
} |
} |
else if (c->wide > 0) |
*c->psize = c->eof / sizeof (wchar_t); |
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->wide > 0 ? (*c->psize + 1) * sizeof (wchar_t) |
: *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. */ |
static FILE * |
_DEFUN(internal_open_memstream_r, (ptr, buf, size, wide), |
struct _reent *ptr _AND |
char **buf _AND |
size_t *size _AND |
int wide) |
{ |
FILE *fp; |
memstream *c; |
|
if (!buf || !size) |
{ |
ptr->_errno = EINVAL; |
return NULL; |
} |
if ((fp = __sfp (ptr)) == NULL) |
return NULL; |
if ((c = (memstream *) _malloc_r (ptr, sizeof *c)) == NULL) |
{ |
_newlib_sfp_lock_start (); |
fp->_flags = 0; /* release */ |
#ifndef __SINGLE_THREAD__ |
__lock_close_recursive (fp->_lock); |
#endif |
_newlib_sfp_lock_end (); |
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 (wide == 1) |
c->max *= sizeof(wchar_t); |
if (c->max < 64) |
c->max = 64; |
#if (SIZE_MAX >= 64 * 1024) |
else if (c->max > 64 * 1024) |
c->max = 64 * 1024; |
#endif |
*size = 0; |
*buf = _malloc_r (ptr, c->max); |
if (!*buf) |
{ |
_newlib_sfp_lock_start (); |
fp->_flags = 0; /* release */ |
#ifndef __SINGLE_THREAD__ |
__lock_close_recursive (fp->_lock); |
#endif |
_newlib_sfp_lock_end (); |
_free_r (ptr, c); |
return NULL; |
} |
if (wide == 1) |
**((wchar_t **)buf) = L'\0'; |
else |
**buf = '\0'; |
|
c->storage = c; |
c->pbuf = buf; |
c->psize = size; |
c->pos = 0; |
c->eof = 0; |
c->saved.w = L'\0'; |
c->wide = (int8_t) wide; |
|
_newlib_flockfile_start (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; |
ORIENT (fp, wide); |
_newlib_flockfile_end (fp); |
return fp; |
} |
|
FILE * |
_DEFUN(_open_memstream_r, (ptr, buf, size), |
struct _reent *ptr _AND |
char **buf _AND |
size_t *size) |
{ |
return internal_open_memstream_r (ptr, buf, size, -1); |
} |
|
FILE * |
_DEFUN(_open_wmemstream_r, (ptr, buf, size), |
struct _reent *ptr _AND |
wchar_t **buf _AND |
size_t *size) |
{ |
return internal_open_memstream_r (ptr, (char **)buf, size, 1); |
} |
|
#ifndef _REENT_ONLY |
FILE * |
_DEFUN(open_memstream, (buf, size), |
char **buf _AND |
size_t *size) |
{ |
return _open_memstream_r (_REENT, buf, size); |
} |
|
FILE * |
_DEFUN(open_wmemstream, (buf, size), |
wchar_t **buf _AND |
size_t *size) |
{ |
return _open_wmemstream_r (_REENT, buf, size); |
} |
#endif /* !_REENT_ONLY */ |