415 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
| /* 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
 | |
| 
 | |
| 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 */
 |