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 <<fmemopen>>---open a stream around a fixed-length string
9
10 INDEX
11 fmemopen
12
13 SYNOPSIS
14 #include <stdio.h>
15 FILE *fmemopen(void *restrict <[buf]>, size_t <[size]>,
16 const char *restrict <[mode]>);
17
18 DESCRIPTION
19 <<fmemopen>> creates a seekable <<FILE>> stream that wraps a
20 fixed-length buffer of <[size]> bytes starting at <[buf]>. The stream
21 is opened with <[mode]> treated as in <<fopen>>, where append mode
22 starts writing at the first NUL byte. If <[buf]> is NULL, then
23 <[size]> bytes are automatically provided as if by <<malloc>>, with
24 the initial size of 0, and <[mode]> must contain <<+>> so that data
25 can be read after it is written.
26
27 The stream maintains a current position, which moves according to
28 bytes read or written, and which can be one past the end of the array.
29 The stream also maintains a current file size, which is never greater
30 than <[size]>. If <[mode]> starts with <<r>>, the position starts at
31 <<0>>, and file size starts at <[size]> if <[buf]> was provided. If
32 <[mode]> starts with <<w>>, the position and file size start at <<0>>,
33 and if <[buf]> was provided, the first byte is set to NUL. If
34 <[mode]> starts with <<a>>, the position and file size start at the
35 location of the first NUL byte, or else <[size]> if <[buf]> was
36 provided.
37
38 When reading, NUL bytes have no significance, and reads cannot exceed
39 the current file size. When writing, the file size can increase up to
40 <[size]> as needed, and NUL bytes may be embedded in the stream (see
41 <<open_memstream>> for an alternative that automatically enlarges the
42 buffer). When the stream is flushed or closed after a write that
43 changed the file size, a NUL byte is written at the current position
44 if there is still room; if the stream is not also open for reading, a
45 NUL byte is additionally written at the last byte of <[buf]> when the
46 stream has exceeded <[size]>, so that a write-only <[buf]> is always
47 NUL-terminated when the stream is flushed or closed (and the initial
48 <[size]> should take this into account). It is not possible to seek
49 outside the bounds of <[size]>. A NUL byte written during a flush is
50 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 <[size]>
55 is zero or <[mode]> is invalid, ENOMEM if <[buf]> was NULL and memory
56 could not be allocated, or EMFILE if too many streams are already
57 open.
58
59 PORTABILITY
60 This function is being added to POSIX 200x, but is not in POSIX 2001.
61
62 Supporting OS subroutines required: <<sbrk>>.
63 */
64
65 #define _DEFAULT_SOURCE
66 #include <stdio.h>
67 #include <errno.h>
68 #include <string.h>
69 #include <sys/lock.h>
70 #include "local.h"
71
72 /* Describe details of an open memstream. */
73 typedef struct fmemcookie {
74 void *storage; /* storage to free on close */
75 char *buf; /* buffer start */
76 size_t pos; /* current position */
77 size_t eof; /* current file size */
78 size_t max; /* maximum file size */
79 char append; /* nonzero if appending */
80 char writeonly; /* 1 if write-only */
81 char saved; /* saved character that lived at pos before write-only NUL */
82 } fmemcookie;
83
84 /* Read up to non-zero N bytes into BUF from stream described by
85 COOKIE; return number of bytes read (0 on EOF). */
86 static ssize_t
fmemreader(void * cookie,char * buf,size_t n)87 fmemreader (
88 void *cookie,
89 char *buf,
90 size_t n)
91 {
92 fmemcookie *c = (fmemcookie *) cookie;
93 /* Can't read beyond current size, but EOF condition is not an error. */
94 if (c->pos > c->eof)
95 return 0;
96 if (n >= c->eof - c->pos)
97 n = c->eof - c->pos;
98 memcpy (buf, c->buf + c->pos, n);
99 c->pos += n;
100 return n;
101 }
102
103 /* Write up to non-zero N bytes of BUF into the stream described by COOKIE,
104 returning the number of bytes written or EOF on failure. */
105 static ssize_t
fmemwriter(void * cookie,const char * buf,size_t n)106 fmemwriter (
107 void *cookie,
108 const char *buf,
109 size_t n)
110 {
111 fmemcookie *c = (fmemcookie *) cookie;
112 int adjust = 0; /* true if at EOF, but still need to write NUL. */
113
114 /* Append always seeks to eof; otherwise, if we have previously done
115 a seek beyond eof, ensure all intermediate bytes are NUL. */
116 if (c->append)
117 c->pos = c->eof;
118 else if (c->pos > c->eof)
119 memset (c->buf + c->eof, '\0', c->pos - c->eof);
120 /* Do not write beyond EOF; saving room for NUL on write-only stream. */
121 if (c->pos + n > c->max - c->writeonly)
122 {
123 adjust = c->writeonly;
124 n = c->max - c->pos;
125 }
126 /* Now n is the number of bytes being modified, and adjust is 1 if
127 the last byte is NUL instead of from buf. Write a NUL if
128 write-only; or if read-write, eof changed, and there is still
129 room. When we are within the file contents, remember what we
130 overwrite so we can restore it if we seek elsewhere later. */
131 if (c->pos + n > c->eof)
132 {
133 c->eof = c->pos + n;
134 if (c->eof - adjust < c->max)
135 c->saved = c->buf[c->eof - adjust] = '\0';
136 }
137 else if (c->writeonly)
138 {
139 if (n)
140 {
141 c->saved = c->buf[c->pos + n - adjust];
142 c->buf[c->pos + n - adjust] = '\0';
143 }
144 else
145 adjust = 0;
146 }
147 c->pos += n;
148 if (n - adjust)
149 memcpy (c->buf + c->pos - n, buf, n - adjust);
150 else
151 {
152 _REENT_ERRNO(ptr) = ENOSPC;
153 return EOF;
154 }
155 return n;
156 }
157
158 /* Seek to position POS relative to WHENCE within stream described by
159 COOKIE; return resulting position or fail with EOF. */
160 static _fpos_t
fmemseeker(void * cookie,_fpos_t pos,int whence)161 fmemseeker (
162 void *cookie,
163 _fpos_t pos,
164 int whence)
165 {
166 fmemcookie *c = (fmemcookie *) cookie;
167 #ifndef __LARGE64_FILES
168 off_t offset = (off_t) pos;
169 #else /* __LARGE64_FILES */
170 _off64_t offset = (_off64_t) pos;
171 #endif /* __LARGE64_FILES */
172
173 if (whence == SEEK_CUR)
174 offset += c->pos;
175 else if (whence == SEEK_END)
176 offset += c->eof;
177 if (offset < 0)
178 {
179 _REENT_ERRNO(ptr) = EINVAL;
180 offset = -1;
181 }
182 else if (offset > (off_t) c->max)
183 {
184 _REENT_ERRNO(ptr) = ENOSPC;
185 offset = -1;
186 }
187 #ifdef __LARGE64_FILES
188 else if ((_fpos_t) offset != offset)
189 {
190 _REENT_ERRNO(ptr) = EOVERFLOW;
191 offset = -1;
192 }
193 #endif /* __LARGE64_FILES */
194 else
195 {
196 if (c->writeonly && c->pos < c->eof)
197 {
198 c->buf[c->pos] = c->saved;
199 c->saved = '\0';
200 }
201 c->pos = offset;
202 if (c->writeonly && c->pos < c->eof)
203 {
204 c->saved = c->buf[c->pos];
205 c->buf[c->pos] = '\0';
206 }
207 }
208 return (_fpos_t) offset;
209 }
210
211 /* Seek to position POS relative to WHENCE within stream described by
212 COOKIE; return resulting position or fail with EOF. */
213 #ifdef __LARGE64_FILES
214 static _fpos64_t
fmemseeker64(void * cookie,_fpos64_t pos,int whence)215 fmemseeker64 (
216 void *cookie,
217 _fpos64_t pos,
218 int whence)
219 {
220 _off64_t offset = (_off64_t) pos;
221 fmemcookie *c = (fmemcookie *) cookie;
222 if (whence == SEEK_CUR)
223 offset += c->pos;
224 else if (whence == SEEK_END)
225 offset += c->eof;
226 if (offset < 0)
227 {
228 _REENT_ERRNO(ptr) = EINVAL;
229 offset = -1;
230 }
231 else if (offset > (_off64_t) c->max)
232 {
233 _REENT_ERRNO(ptr) = ENOSPC;
234 offset = -1;
235 }
236 else
237 {
238 if (c->writeonly && c->pos < c->eof)
239 {
240 c->buf[c->pos] = c->saved;
241 c->saved = '\0';
242 }
243 c->pos = offset;
244 if (c->writeonly && c->pos < c->eof)
245 {
246 c->saved = c->buf[c->pos];
247 c->buf[c->pos] = '\0';
248 }
249 }
250 return (_fpos64_t) offset;
251 }
252 #endif /* __LARGE64_FILES */
253
254 /* Reclaim resources used by stream described by COOKIE. */
255 static int
fmemcloser(void * cookie)256 fmemcloser (
257 void *cookie)
258 {
259 fmemcookie *c = (fmemcookie *) cookie;
260 free (c->storage);
261 return 0;
262 }
263
264 /* Open a memstream around buffer BUF of SIZE bytes, using MODE.
265 Return the new stream, or fail with NULL. */
266 FILE *
fmemopen(void * __restrict buf,size_t size,const char * __restrict mode)267 fmemopen (
268 void *__restrict buf,
269 size_t size,
270 const char *__restrict mode)
271 {
272 FILE *fp;
273 fmemcookie *c;
274 int flags;
275 int dummy;
276
277 if ((flags = __sflags (mode, &dummy)) == 0)
278 return NULL;
279 if (!size || !(buf || flags & __SRW))
280 {
281 _REENT_ERRNO(ptr) = EINVAL;
282 return NULL;
283 }
284 if ((fp = __sfp ()) == NULL)
285 return NULL;
286 if ((c = (fmemcookie *) malloc (sizeof *c + (buf ? 0 : size)))
287 == NULL)
288 {
289 _newlib_sfp_lock_start ();
290 fp->_flags = 0; /* release */
291 #ifndef __SINGLE_THREAD__
292 __lock_close_recursive (fp->_lock);
293 #endif
294 _newlib_sfp_lock_end ();
295 return NULL;
296 }
297
298 c->storage = c;
299 c->max = size;
300 /* 9 modes to worry about. */
301 /* w/a, buf or no buf: Guarantee a NUL after any file writes. */
302 c->writeonly = (flags & __SWR) != 0;
303 c->saved = '\0';
304 if (!buf)
305 {
306 /* r+/w+/a+, and no buf: file starts empty. */
307 c->buf = (char *) (c + 1);
308 c->buf[0] = '\0';
309 c->pos = c->eof = 0;
310 c->append = (flags & __SAPP) != 0;
311 }
312 else
313 {
314 c->buf = (char *) buf;
315 switch (*mode)
316 {
317 case 'a':
318 /* a/a+ and buf: position and size at first NUL. */
319 buf = memchr (c->buf, '\0', size);
320 c->eof = c->pos = buf ? (size_t) ((char *) buf - c->buf) : size;
321 if (!buf && c->writeonly)
322 /* a: guarantee a NUL within size even if no writes. */
323 c->buf[size - 1] = '\0';
324 c->append = 1;
325 break;
326 case 'r':
327 /* r/r+ and buf: read at beginning, full size available. */
328 c->pos = c->append = 0;
329 c->eof = size;
330 break;
331 case 'w':
332 /* w/w+ and buf: write at beginning, truncate to empty. */
333 c->pos = c->append = c->eof = 0;
334 *c->buf = '\0';
335 break;
336 default:
337 abort ();
338 }
339 }
340
341 _newlib_flockfile_start (fp);
342 fp->_file = -1;
343 fp->_flags = flags;
344 fp->_cookie = c;
345 fp->_read = flags & (__SRD | __SRW) ? fmemreader : NULL;
346 fp->_write = flags & (__SWR | __SRW) ? fmemwriter : NULL;
347 fp->_seek = fmemseeker;
348 #ifdef __LARGE64_FILES
349 fp->_seek64 = fmemseeker64;
350 fp->_flags |= __SL64;
351 #endif
352 fp->_close = fmemcloser;
353 _newlib_flockfile_end (fp);
354 return fp;
355 }
356