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