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 #ifdef __GNUC__
65 #pragma GCC diagnostic ignored "-Wpragmas"
66 #pragma GCC diagnostic ignored "-Wunknown-warning-option"
67 #pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
68 #endif
69 
70 #define _DEFAULT_SOURCE
71 #include <stdio.h>
72 #include <wchar.h>
73 #include <errno.h>
74 #include <string.h>
75 #include <sys/lock.h>
76 #include <stdint.h>
77 #include "local.h"
78 
79 #ifndef __LARGE64_FILES
80 # define OFF_T off_t
81 #else
82 # define OFF_T _off64_t
83 #endif
84 
85 /* Describe details of an open memstream.  */
86 typedef struct memstream {
87   void *storage; /* storage to free on close */
88   char **pbuf; /* pointer to the current buffer */
89   size_t *psize; /* pointer to the current size, smaller of pos or eof */
90   size_t pos; /* current position */
91   size_t eof; /* current file size */
92   size_t max; /* current malloc buffer size, always > eof */
93   union {
94     char c;
95     wchar_t w;
96   } saved; /* saved character that lived at *psize before NUL */
97   int8_t wide; /* wide-oriented (>0) or byte-oriented (<0) */
98 } memstream;
99 
100 /* Write up to non-zero N bytes of BUF into the stream described by COOKIE,
101    returning the number of bytes written or EOF on failure.  */
102 static ssize_t
memwriter(void * cookie,const char * buf,size_t n)103 memwriter (
104        void *cookie,
105        const char *buf,
106        size_t n)
107 {
108   memstream *c = (memstream *) cookie;
109   char *cbuf = *c->pbuf;
110 
111   /* size_t is unsigned, but off_t is signed.  Don't let stream get so
112      big that user cannot do ftello.  */
113   if (sizeof (OFF_T) == sizeof (size_t) && (ssize_t) (c->pos + n) < 0)
114     {
115       _REENT_ERRNO(ptr) = EFBIG;
116       return EOF;
117     }
118   /* Grow the buffer, if necessary.  Choose a geometric growth factor
119      to avoid quadratic realloc behavior, but use a rate less than
120      (1+sqrt(5))/2 to accomodate malloc overhead.  Overallocate, so
121      that we can add a trailing \0 without reallocating.  The new
122      allocation should thus be max(prev_size*1.5, c->pos+n+1). */
123   if (c->pos + n >= c->max)
124     {
125       size_t newsize = c->max * 3 / 2;
126       if (newsize < c->pos + n + 1)
127 	newsize = c->pos + n + 1;
128       cbuf = realloc (cbuf, newsize);
129       if (! cbuf)
130 	return EOF; /* errno already set to ENOMEM */
131       *c->pbuf = cbuf;
132       c->max = newsize;
133     }
134   /* If we have previously done a seek beyond eof, ensure all
135      intermediate bytes are NUL.  */
136   if (c->pos > c->eof)
137     memset (cbuf + c->eof, '\0', c->pos - c->eof);
138   memcpy (cbuf + c->pos, buf, n);
139   c->pos += n;
140   /* If the user has previously written further, remember what the
141      trailing NUL is overwriting.  Otherwise, extend the stream.  */
142   if (c->pos > c->eof)
143     c->eof = c->pos;
144   else if (c->wide > 0)
145     c->saved.w = *(wchar_t *)(cbuf + c->pos);
146   else
147     c->saved.c = cbuf[c->pos];
148   cbuf[c->pos] = '\0';
149   *c->psize = (c->wide > 0) ? c->pos / sizeof (wchar_t) : c->pos;
150   return n;
151 }
152 
153 /* Seek to position POS relative to WHENCE within stream described by
154    COOKIE; return resulting position or fail with EOF.  */
155 static _fpos_t
memseeker(void * cookie,_fpos_t pos,int whence)156 memseeker (
157        void *cookie,
158        _fpos_t pos,
159        int whence)
160 {
161   memstream *c = (memstream *) cookie;
162   OFF_T offset = (OFF_T) pos;
163 
164   if (whence == SEEK_CUR)
165     offset += c->pos;
166   else if (whence == SEEK_END)
167     offset += c->eof;
168   if (offset < 0)
169     {
170       _REENT_ERRNO(ptr) = EINVAL;
171       offset = -1;
172     }
173   else if ((OFF_T) (size_t) offset != offset)
174     {
175       _REENT_ERRNO(ptr) = ENOSPC;
176       offset = -1;
177     }
178 #ifdef __LARGE64_FILES
179   else if ((_fpos_t) offset != offset)
180     {
181       _REENT_ERRNO(ptr) = EOVERFLOW;
182       offset = -1;
183     }
184 #endif /* __LARGE64_FILES */
185   else
186     {
187       if (c->pos < c->eof)
188 	{
189 	  if (c->wide > 0)
190 	    *(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w;
191 	  else
192 	    (*c->pbuf)[c->pos] = c->saved.c;
193 	  c->saved.w = L'\0';
194 	}
195       c->pos = offset;
196       if (c->pos < c->eof)
197 	{
198 	  if (c->wide > 0)
199 	    {
200 	      c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos);
201 	      *(wchar_t *)((*c->pbuf) + c->pos) = L'\0';
202 	      *c->psize = c->pos / sizeof (wchar_t);
203 	    }
204 	  else
205 	    {
206 	      c->saved.c = (*c->pbuf)[c->pos];
207 	      (*c->pbuf)[c->pos] = '\0';
208 	      *c->psize = c->pos;
209 	    }
210 	}
211       else if (c->wide > 0)
212 	*c->psize = c->eof / sizeof (wchar_t);
213       else
214 	*c->psize = c->eof;
215     }
216   return (_fpos_t) offset;
217 }
218 
219 /* Seek to position POS relative to WHENCE within stream described by
220    COOKIE; return resulting position or fail with EOF.  */
221 #ifdef __LARGE64_FILES
222 static _fpos64_t
memseeker64(void * cookie,_fpos64_t pos,int whence)223 memseeker64 (
224        void *cookie,
225        _fpos64_t pos,
226        int whence)
227 {
228   _off64_t offset = (_off64_t) pos;
229   memstream *c = (memstream *) cookie;
230 
231   if (whence == SEEK_CUR)
232     offset += c->pos;
233   else if (whence == SEEK_END)
234     offset += c->eof;
235   if (offset < 0)
236     {
237       _REENT_ERRNO(ptr) = EINVAL;
238       offset = -1;
239     }
240   else if ((_off64_t) (size_t) offset != offset)
241     {
242       _REENT_ERRNO(ptr) = ENOSPC;
243       offset = -1;
244     }
245   else
246     {
247       if (c->pos < c->eof)
248 	{
249 	  if (c->wide > 0)
250 	    *(wchar_t *)((*c->pbuf) + c->pos) = c->saved.w;
251 	  else
252 	    (*c->pbuf)[c->pos] = c->saved.c;
253 	  c->saved.w = L'\0';
254 	}
255       c->pos = offset;
256       if (c->pos < c->eof)
257 	{
258 	  if (c->wide > 0)
259 	    {
260 	      c->saved.w = *(wchar_t *)((*c->pbuf) + c->pos);
261 	      *(wchar_t *)((*c->pbuf) + c->pos) = L'\0';
262 	      *c->psize = c->pos / sizeof (wchar_t);
263 	    }
264 	  else
265 	    {
266 	      c->saved.c = (*c->pbuf)[c->pos];
267 	      (*c->pbuf)[c->pos] = '\0';
268 	      *c->psize = c->pos;
269 	    }
270 	}
271       else if (c->wide > 0)
272 	*c->psize = c->eof / sizeof (wchar_t);
273       else
274 	*c->psize = c->eof;
275     }
276   return (_fpos64_t) offset;
277 }
278 #endif /* __LARGE64_FILES */
279 
280 /* Reclaim resources used by stream described by COOKIE.  */
281 static int
memcloser(void * cookie)282 memcloser (
283        void *cookie)
284 {
285   memstream *c = (memstream *) cookie;
286   char *buf;
287 
288   /* Be nice and try to reduce any unused memory.  */
289   buf = realloc (*c->pbuf,
290 		    c->wide > 0 ? (*c->psize + 1) * sizeof (wchar_t)
291 				: *c->psize + 1);
292   if (buf)
293     *c->pbuf = buf;
294   free (c->storage);
295   return 0;
296 }
297 
298 /* Open a memstream that tracks a dynamic buffer in BUF and SIZE.
299    Return the new stream, or fail with NULL.  */
300 static FILE *
internalopen_memstream(char ** buf,size_t * size,int wide)301 internalopen_memstream (
302        char **buf,
303        size_t *size,
304        int wide)
305 {
306   FILE *fp;
307   memstream *c;
308 
309   if (!buf || !size)
310     {
311       _REENT_ERRNO(ptr) = EINVAL;
312       return NULL;
313     }
314   if ((fp = __sfp ()) == NULL)
315     return NULL;
316   if ((c = (memstream *) malloc (sizeof *c)) == NULL)
317     {
318       _newlib_sfp_lock_start ();
319       fp->_flags = 0;		/* release */
320 #ifndef __SINGLE_THREAD__
321       __lock_close_recursive (fp->_lock);
322 #endif
323       _newlib_sfp_lock_end ();
324       return NULL;
325     }
326   /* Use *size as a hint for initial sizing, but bound the initial
327      malloc between 64 bytes (same as asprintf, to avoid frequent
328      mallocs on small strings) and 64k bytes (to avoid overusing the
329      heap if *size was garbage).  */
330   c->max = *size;
331   if (wide == 1)
332     c->max *= sizeof(wchar_t);
333   if (c->max < 64)
334     c->max = 64;
335 #if (SIZE_MAX >= 64 * 1024)
336   else if (c->max > (size_t)64 * 1024)
337     c->max = (size_t)64 * 1024;
338 #endif
339   *size = 0;
340   *buf = malloc (c->max);
341   if (!*buf)
342     {
343       _newlib_sfp_lock_start ();
344       fp->_flags = 0;		/* release */
345 #ifndef __SINGLE_THREAD__
346       __lock_close_recursive (fp->_lock);
347 #endif
348       _newlib_sfp_lock_end ();
349       free (c);
350       return NULL;
351     }
352   if (wide == 1)
353     **((wchar_t **)buf) = L'\0';
354   else
355     **buf = '\0';
356 
357   c->storage = c;
358   c->pbuf = buf;
359   c->psize = size;
360   c->pos = 0;
361   c->eof = 0;
362   c->saved.w = L'\0';
363   c->wide = (int8_t) wide;
364 
365   _newlib_flockfile_start (fp);
366   fp->_file = -1;
367   fp->_flags = __SWR;
368   fp->_cookie = c;
369   fp->_read = NULL;
370   fp->_write = memwriter;
371   fp->_seek = memseeker;
372 #ifdef __LARGE64_FILES
373   fp->_seek64 = memseeker64;
374   fp->_flags |= __SL64;
375 #endif
376   fp->_close = memcloser;
377   (void) ORIENT (fp, wide);
378   _newlib_flockfile_end (fp);
379   return fp;
380 }
381 
382 FILE *
open_memstream(char ** buf,size_t * size)383 open_memstream (
384        char **buf,
385        size_t *size)
386 {
387   return internalopen_memstream ( buf, size, -1);
388 }
389 
390 FILE *
open_wmemstream(wchar_t ** buf,size_t * size)391 open_wmemstream (
392        wchar_t **buf,
393        size_t *size)
394 {
395   return internalopen_memstream ( (char **)buf, size, 1);
396 }
397