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