1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright © 2022 Keith Packard
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above
14  *    copyright notice, this list of conditions and the following
15  *    disclaimer in the documentation and/or other materials provided
16  *    with the distribution.
17  *
18  * 3. Neither the name of the copyright holder nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33  * OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include "stdio_private.h"
37 
38 #define __MALL 0x01
39 #define __MAPP 0x02
40 
41 struct __file_mem {
42     struct __file_ext xfile;
43     char *buf;
44     size_t size; /* Current size. */
45     size_t bufsize; /* Upper limit on size. */
46     size_t pos;
47     uint8_t mflags;
48 };
49 
50 static int
__fmem_put(char c,FILE * f)51 __fmem_put(char c, FILE *f)
52 {
53     struct __file_mem *mf = (struct __file_mem *)f;
54     size_t pos = mf->mflags & __MAPP ? mf->size : mf->pos;
55     if ((f->flags & __SWR) == 0) {
56         return _FDEV_ERR;
57     } else if (pos < mf->bufsize) {
58         mf->buf[pos++] = c;
59         if (pos > mf->size) {
60             mf->size = pos;
61             /* When a stream open for update (the mode argument includes '+') or
62              * for writing only is successfully written and the write advances
63              * the current buffer end position, a null byte shall be written at
64              * the new buffer end position if it fits. */
65             if (mf->size < mf->bufsize) {
66                 mf->buf[mf->size] = '\0';
67             }
68         }
69         mf->pos = pos;
70         return (unsigned char)c;
71     } else {
72         return _FDEV_EOF;
73     }
74 }
75 
76 static int
__fmem_get(FILE * f)77 __fmem_get(FILE *f)
78 {
79     struct __file_mem *mf = (struct __file_mem *)f;
80     if ((f->flags & __SRD) == 0) {
81         return _FDEV_ERR;
82     } else if (mf->pos < mf->size) {
83         return (unsigned char)mf->buf[mf->pos++];
84     } else {
85         return _FDEV_EOF;
86     }
87 }
88 
89 static int
__fmem_flush(FILE * f)90 __fmem_flush(FILE *f)
91 {
92     struct __file_mem *mf = (struct __file_mem *)f;
93     if ((f->flags & __SWR) && mf->pos < mf->bufsize) {
94         mf->buf[mf->pos] = '\0';
95         if (mf->pos > mf->size) {
96             mf->size = mf->pos;
97         }
98     }
99     return 0;
100 }
101 
102 static off_t
__fmem_seek(FILE * f,off_t pos,int whence)103 __fmem_seek(FILE *f, off_t pos, int whence)
104 {
105     struct __file_mem *mf = (struct __file_mem *)f;
106 
107     switch (whence) {
108     case SEEK_SET:
109         break;
110     case SEEK_CUR:
111         pos += mf->pos;
112         break;
113     case SEEK_END:
114         pos += mf->size;
115         break;
116     }
117     _Static_assert(sizeof(off_t) >= sizeof(size_t), "must avoid truncation");
118     if (pos < 0 || (off_t)mf->bufsize < pos)
119         return EOF;
120     mf->pos = pos;
121     return pos;
122 }
123 
124 static int
__fmem_close(FILE * f)125 __fmem_close(FILE *f)
126 {
127     struct __file_mem *mf = (struct __file_mem *)f;
128 
129     if (mf->mflags & __MALL)
130         free(mf->buf);
131     else
132         __fmem_flush(f);
133     free(f);
134     return 0;
135 }
136 
137 FILE *
fmemopen(void * buf,size_t size,const char * mode)138 fmemopen(void *buf, size_t size, const char *mode)
139 {
140     int stdio_flags;
141     uint8_t mflags = 0;
142     size_t initial_pos = 0;
143     size_t initial_size;
144     struct __file_mem *mf;
145 
146     stdio_flags = __stdio_sflags(mode);
147 
148     if (stdio_flags == 0 || size == 0) {
149         errno = EINVAL;
150         return NULL;
151     }
152 
153     /* Allocate file structure and necessary buffers */
154     mf = calloc(1, sizeof(struct __file_mem));
155 
156     if (mf == NULL)
157         return NULL;
158 
159     if (buf == NULL) {
160         /* POSIX says return EINVAL if: The buf argument is a null pointer and
161          * the mode argument does not include a '+' character. */
162         if ((stdio_flags & (__SRD | __SWR)) != (__SRD | __SWR)) {
163             free(mf);
164             errno = EINVAL;
165             return NULL;
166         }
167         buf = malloc(size);
168         if (!buf) {
169             free(mf);
170             errno = ENOMEM;
171             return NULL;
172         }
173         *((char *)buf) = '\0';
174         mflags |= __MALL;
175     }
176 
177     /* Check mode directly to avoid _POSIX_IO dependency. */
178     if (mode[0] == 'a') {
179         /* For append the position is set to the first NUL byte or the end. */
180         initial_pos = (mflags & __MALL) ? 0 : strnlen(buf, size);
181         initial_size = initial_pos;
182         mflags |= __MAPP;
183     } else if (mode[0] == 'w') {
184         initial_size = 0;
185         /* w+ mode truncates the buffer, writing NUL */
186         if ((stdio_flags & (__SRD | __SWR)) == (__SRD | __SWR)) {
187             *((char *)buf) = '\0';
188         }
189     } else {
190         initial_size = size;
191     }
192 
193     *mf = (struct __file_mem){
194         .xfile = FDEV_SETUP_EXT(__fmem_put, __fmem_get, __fmem_flush,
195                                 __fmem_close, __fmem_seek, NULL, stdio_flags),
196         .buf = buf,
197         .size = initial_size,
198         .bufsize = size,
199         .pos = initial_pos,
200         .mflags = mflags,
201     };
202 
203     return (FILE *)mf;
204 }
205