1 /* Copyright (C) 2007 Eric Blake
2  * Permission to use, copy, modify, and distribute this software
3  * is freely granted, provided that this notice is preserved.
4  */
5 
6 /*
7 FUNCTION
8 <<open_memstream>>, <<open_wmemstream>>---open a write stream around an arbitrary-length string
9 
10 INDEX
11 	open_memstream
12 INDEX
13 	open_wmemstream
14 
15 SYNOPSIS
16 	#include <stdio.h>
17 	FILE *open_memstream(char **restrict <[buf]>,
18 			     size_t *restrict <[size]>);
19 
20 	#include <wchar.h>
21 	FILE *open_wmemstream(wchar_t **restrict <[buf]>,
22 			      size_t *restrict <[size]>);
23 
24 DESCRIPTION
25 <<open_memstream>> creates a seekable, byte-oriented <<FILE>> stream that
26 wraps an arbitrary-length buffer, created as if by <<malloc>>.  The current
27 contents of *<[buf]> are ignored; this implementation uses *<[size]>
28 as a hint of the maximum size expected, but does not fail if the hint
29 was wrong.  The parameters <[buf]> and <[size]> are later stored
30 through following any call to <<fflush>> or <<fclose>>, set to the
31 current address and usable size of the allocated string; although
32 after fflush, the pointer is only valid until another stream operation
33 that results in a write.  Behavior is undefined if the user alters
34 either *<[buf]> or *<[size]> prior to <<fclose>>.
35 
36 <<open_wmemstream>> is like <<open_memstream>> just with the associated
37 stream being wide-oriented.  The size set in <[size]> in subsequent
38 operations is the number of wide characters.
39 
40 The stream is write-only, since the user can directly read *<[buf]>
41 after a flush; see <<fmemopen>> for a way to wrap a string with a
42 readable stream.  The user is responsible for calling <<free>> on
43 the final *<[buf]> after <<fclose>>.
44 
45 Any time the stream is flushed, a NUL byte is written at the current
46 position (but is not counted in the buffer length), so that the string
47 is always NUL-terminated after at most *<[size]> bytes (or wide characters
48 in case of <<open_wmemstream>>).  However, data previously written beyond
49 the current stream offset is not lost, and the NUL value written during a
50 flush is restored to its previous value when seeking elsewhere in the string.
51 
52 RETURNS
53 The return value is an open FILE pointer on success.  On error,
54 <<NULL>> is returned, and <<errno>> will be set to EINVAL if <[buf]>
55 or <[size]> is NULL, ENOMEM if memory could not be allocated, or
56 EMFILE if too many streams are already open.
57 
58 PORTABILITY
59 POSIX.1-2008
60 
61 Supporting OS subroutines required: <<sbrk>>.
62 */
63 
64 #define _DEFAULT_SOURCE
65 #include <stdio.h>
66 #include <wchar.h>
67 #include <errno.h>
68 #include <string.h>
69 #include <sys/lock.h>
70 #include <stdint.h>
71 #include "local.h"
72 
73 #ifndef __LARGE64_FILES
74 # define OFF_T off_t
75 #else
76 # define OFF_T _off64_t
77 #endif
78 
79 /* Describe details of an open memstream.  */
80 typedef struct memstream {
81   void *storage; /* storage to free on close */
82   char **pbuf; /* pointer to the current buffer */
83   size_t *psize; /* pointer to the current size, smaller of pos or eof */
84   size_t pos; /* current position */
85   size_t eof; /* current file size */
86   size_t max; /* current malloc buffer size, always > eof */
87   union {
88     char c;
89     wchar_t w;
90   } saved; /* saved character that lived at *psize before NUL */
91   int8_t wide; /* wide-oriented (>0) or byte-oriented (<0) */
92 } memstream;
93 
94 /* Write up to non-zero N bytes of BUF into the stream described by COOKIE,
95    returning the number of bytes written or EOF on failure.  */
96 static ssize_t
memwriter(void * cookie,const char * buf,size_t n)97 memwriter (
98        void *cookie,
99        const char *buf,
100        size_t n)
101 {
102   memstream *c = (memstream *) cookie;
103   char *cbuf = *c->pbuf;
104 
105   /* size_t is unsigned, but off_t is signed.  Don't let stream get so
106      big that user cannot do ftello.  */
107   if (sizeof (OFF_T) == sizeof (size_t) && (ssize_t) (c->pos + n) < 0)
108     {
109       _REENT_ERRNO(ptr) = EFBIG;
110       return EOF;
111     }
112   /* Grow the buffer, if necessary.  Choose a geometric growth factor
113      to avoid quadratic realloc behavior, but use a rate less than
114      (1+sqrt(5))/2 to accomodate malloc overhead.  Overallocate, so
115      that we can add a trailing \0 without reallocating.  The new
116      allocation should thus be max(prev_size*1.5, c->pos+n+1). */
117   if (c->pos + n >= c->max)
118     {
119       size_t newsize = c->max * 3 / 2;
120       if (newsize < c->pos + n + 1)
121 	newsize = c->pos + n + 1;
122       cbuf = realloc (cbuf, newsize);
123       if (! cbuf)
124 	return EOF; /* errno already set to ENOMEM */
125       *c->pbuf = cbuf;
126       c->max = newsize;
127     }
128   /* If we have previously done a seek beyond eof, ensure all
129      intermediate bytes are NUL.  */
130   if (c->pos > c->eof)
131     memset (cbuf + c->eof, '\0', c->pos - c->eof);
132   memcpy (cbuf + c->pos, buf, n);
133   c->pos += n;
134   /* If the user has previously written further, remember what the
135      trailing NUL is overwriting.  Otherwise, extend the stream.  */
136   if (c->pos > c->eof)
137     c->eof = c->pos;
138   else if (c->wide > 0)
139     c->saved.w = *(wchar_t *)(cbuf + c->pos);
140   else
141     c->saved.c = cbuf[c->pos];
142   cbuf[c->pos] = '\0';
143   *c->psize = (c->wide > 0) ? c->pos / sizeof (wchar_t) : c->pos;
144   return n;
145 }
146 
147 /* Seek to position POS relative to WHENCE within stream described by
148    COOKIE; return resulting position or fail with EOF.  */
149 static _fpos_t
memseeker(void * cookie,_fpos_t pos,int whence)150 memseeker (
151        void *cookie,
152        _fpos_t pos,
153        int whence)
154 {
155   memstream *c = (memstream *) cookie;
156   OFF_T offset = (OFF_T) pos;
157 
158   if (whence == SEEK_CUR)
159     offset += c->pos;
160   else if (whence == SEEK_END)
161     offset += c->eof;
162   if (offset < 0)
163     {
164       _REENT_ERRNO(ptr) = EINVAL;
165       offset = -1;
166     }
167   else if ((OFF_T) (size_t) offset != offset)
168     {
169       _REENT_ERRNO(ptr) = ENOSPC;
170       offset = -1;
171     }
172 #ifdef __LARGE64_FILES
173   else if ((_fpos_t) offset != offset)
174     {
175       _REENT_ERRNO(ptr) = EOVERFLOW;
176       offset = -1;
177     }
178 #endif /* __LARGE64_FILES */
179   else
180     {
181       if (c->pos < c->eof)
182 	{
183 	  if (c->wide > 0)
184 	    *(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w;
185 	  else
186 	    (*c->pbuf)[c->pos] = c->saved.c;
187 	  c->saved.w = L'\0';
188 	}
189       c->pos = offset;
190       if (c->pos < c->eof)
191 	{
192 	  if (c->wide > 0)
193 	    {
194 	      c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos);
195 	      *(wchar_t *)((*c->pbuf) + c->pos) = L'\0';
196 	      *c->psize = c->pos / sizeof (wchar_t);
197 	    }
198 	  else
199 	    {
200 	      c->saved.c = (*c->pbuf)[c->pos];
201 	      (*c->pbuf)[c->pos] = '\0';
202 	      *c->psize = c->pos;
203 	    }
204 	}
205       else if (c->wide > 0)
206 	*c->psize = c->eof / sizeof (wchar_t);
207       else
208 	*c->psize = c->eof;
209     }
210   return (_fpos_t) offset;
211 }
212 
213 /* Seek to position POS relative to WHENCE within stream described by
214    COOKIE; return resulting position or fail with EOF.  */
215 #ifdef __LARGE64_FILES
216 static _fpos64_t
memseeker64(void * cookie,_fpos64_t pos,int whence)217 memseeker64 (
218        void *cookie,
219        _fpos64_t pos,
220        int whence)
221 {
222   _off64_t offset = (_off64_t) pos;
223   memstream *c = (memstream *) cookie;
224 
225   if (whence == SEEK_CUR)
226     offset += c->pos;
227   else if (whence == SEEK_END)
228     offset += c->eof;
229   if (offset < 0)
230     {
231       _REENT_ERRNO(ptr) = EINVAL;
232       offset = -1;
233     }
234   else if ((_off64_t) (size_t) offset != offset)
235     {
236       _REENT_ERRNO(ptr) = ENOSPC;
237       offset = -1;
238     }
239   else
240     {
241       if (c->pos < c->eof)
242 	{
243 	  if (c->wide > 0)
244 	    *(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w;
245 	  else
246 	    (*c->pbuf)[c->pos] = c->saved.c;
247 	  c->saved.w = L'\0';
248 	}
249       c->pos = offset;
250       if (c->pos < c->eof)
251 	{
252 	  if (c->wide > 0)
253 	    {
254 	      c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos);
255 	      *(wchar_t *)((*c->pbuf) + c->pos) = L'\0';
256 	      *c->psize = c->pos / sizeof (wchar_t);
257 	    }
258 	  else
259 	    {
260 	      c->saved.c = (*c->pbuf)[c->pos];
261 	      (*c->pbuf)[c->pos] = '\0';
262 	      *c->psize = c->pos;
263 	    }
264 	}
265       else if (c->wide > 0)
266 	*c->psize = c->eof / sizeof (wchar_t);
267       else
268 	*c->psize = c->eof;
269     }
270   return (_fpos64_t) offset;
271 }
272 #endif /* __LARGE64_FILES */
273 
274 /* Reclaim resources used by stream described by COOKIE.  */
275 static int
memcloser(void * cookie)276 memcloser (
277        void *cookie)
278 {
279   memstream *c = (memstream *) cookie;
280   char *buf;
281 
282   /* Be nice and try to reduce any unused memory.  */
283   buf = realloc (*c->pbuf,
284 		    c->wide > 0 ? (*c->psize + 1) * sizeof (wchar_t)
285 				: *c->psize + 1);
286   if (buf)
287     *c->pbuf = buf;
288   free (c->storage);
289   return 0;
290 }
291 
292 /* Open a memstream that tracks a dynamic buffer in BUF and SIZE.
293    Return the new stream, or fail with NULL.  */
294 static FILE *
internalopen_memstream(char ** buf,size_t * size,int wide)295 internalopen_memstream (
296        char **buf,
297        size_t *size,
298        int wide)
299 {
300   FILE *fp;
301   memstream *c;
302 
303   if (!buf || !size)
304     {
305       _REENT_ERRNO(ptr) = EINVAL;
306       return NULL;
307     }
308   if ((fp = __sfp ()) == NULL)
309     return NULL;
310   if ((c = (memstream *) malloc (sizeof *c)) == NULL)
311     {
312       _newlib_sfp_lock_start ();
313       fp->_flags = 0;		/* release */
314 #ifndef __SINGLE_THREAD__
315       __lock_close_recursive (fp->_lock);
316 #endif
317       _newlib_sfp_lock_end ();
318       return NULL;
319     }
320   /* Use *size as a hint for initial sizing, but bound the initial
321      malloc between 64 bytes (same as asprintf, to avoid frequent
322      mallocs on small strings) and 64k bytes (to avoid overusing the
323      heap if *size was garbage).  */
324   c->max = *size;
325   if (wide == 1)
326     c->max *= sizeof(wchar_t);
327   if (c->max < 64)
328     c->max = 64;
329 #if (SIZE_MAX >= 64 * 1024)
330   else if (c->max > (size_t)64 * 1024)
331     c->max = (size_t)64 * 1024;
332 #endif
333   *size = 0;
334   *buf = malloc (c->max);
335   if (!*buf)
336     {
337       _newlib_sfp_lock_start ();
338       fp->_flags = 0;		/* release */
339 #ifndef __SINGLE_THREAD__
340       __lock_close_recursive (fp->_lock);
341 #endif
342       _newlib_sfp_lock_end ();
343       free (c);
344       return NULL;
345     }
346   if (wide == 1)
347     **((wchar_t **)buf) = L'\0';
348   else
349     **buf = '\0';
350 
351   c->storage = c;
352   c->pbuf = buf;
353   c->psize = size;
354   c->pos = 0;
355   c->eof = 0;
356   c->saved.w = L'\0';
357   c->wide = (int8_t) wide;
358 
359   _newlib_flockfile_start (fp);
360   fp->_file = -1;
361   fp->_flags = __SWR;
362   fp->_cookie = c;
363   fp->_read = NULL;
364   fp->_write = memwriter;
365   fp->_seek = memseeker;
366 #ifdef __LARGE64_FILES
367   fp->_seek64 = memseeker64;
368   fp->_flags |= __SL64;
369 #endif
370   fp->_close = memcloser;
371   ORIENT (fp, wide);
372   _newlib_flockfile_end (fp);
373   return fp;
374 }
375 
376 FILE *
open_memstream(char ** buf,size_t * size)377 open_memstream (
378        char **buf,
379        size_t *size)
380 {
381   return internalopen_memstream ( buf, size, -1);
382 }
383 
384 FILE *
open_wmemstream(wchar_t ** buf,size_t * size)385 open_wmemstream (
386        wchar_t **buf,
387        size_t *size)
388 {
389   return internalopen_memstream ( (char **)buf, size, 1);
390 }
391