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