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