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