1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (C) 2013 Pietro Cerutti <[email protected]>
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 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30
31 #include <fcntl.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <errno.h>
37 #include "local.h"
38
39 struct fmemopen_cookie
40 {
41 char *buf; /* pointer to the memory region */
42 bool own; /* did we allocate the buffer ourselves? */
43 char bin; /* is this a binary buffer? */
44 size_t size; /* buffer length in bytes */
45 size_t len; /* data length in bytes */
46 size_t off; /* current offset into the buffer */
47 };
48
49 static int fmemopen_read(void *cookie, char *buf, int nbytes);
50 static int fmemopen_write(void *cookie, const char *buf, int nbytes);
51 static fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence);
52 static int fmemopen_close(void *cookie);
53
54 FILE *
fmemopen(void * __restrict buf,size_t size,const char * __restrict mode)55 fmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
56 {
57 struct fmemopen_cookie *ck;
58 FILE *f;
59 int flags, rc;
60
61 /*
62 * POSIX says we shall return EINVAL if size is 0.
63 */
64 if (size == 0) {
65 errno = EINVAL;
66 return (NULL);
67 }
68
69 /*
70 * Retrieve the flags as used by open(2) from the mode argument, and
71 * validate them.
72 */
73 rc = __sflags(mode, &flags);
74 if (rc == 0) {
75 errno = EINVAL;
76 return (NULL);
77 }
78
79 /*
80 * There's no point in requiring an automatically allocated buffer
81 * in write-only mode.
82 */
83 if (!(flags & O_RDWR) && buf == NULL) {
84 errno = EINVAL;
85 return (NULL);
86 }
87
88 ck = malloc(sizeof(struct fmemopen_cookie));
89 if (ck == NULL) {
90 return (NULL);
91 }
92
93 ck->off = 0;
94 ck->size = size;
95
96 /* Check whether we have to allocate the buffer ourselves. */
97 ck->own = ((ck->buf = buf) == NULL);
98 if (ck->own) {
99 ck->buf = malloc(size);
100 if (ck->buf == NULL) {
101 free(ck);
102 return (NULL);
103 }
104 }
105
106 /*
107 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
108 * truncate the buffer.
109 */
110 if (ck->own || mode[0] == 'w') {
111 ck->buf[0] = '\0';
112 }
113
114 /* Check for binary mode. */
115 ck->bin = strchr(mode, 'b') != NULL;
116
117 /*
118 * The size of the current buffer contents is set depending on the
119 * mode:
120 *
121 * for append (text-mode), the position of the first NULL byte, or the
122 * size of the buffer if none is found
123 *
124 * for append (binary-mode), the size of the buffer
125 *
126 * for read, the size of the buffer
127 *
128 * for write, 0
129 */
130 switch (mode[0]) {
131 case 'a':
132 ck->off = ck->len = strnlen(ck->buf, ck->size);
133 break;
134 case 'r':
135 ck->len = size;
136 break;
137 case 'w':
138 ck->len = 0;
139 break;
140 }
141
142 f = funopen(ck,
143 flags & O_WRONLY ? NULL : fmemopen_read,
144 flags & O_RDONLY ? NULL : fmemopen_write,
145 fmemopen_seek, fmemopen_close);
146
147 if (f == NULL) {
148 if (ck->own)
149 free(ck->buf);
150 free(ck);
151 return (NULL);
152 }
153
154 if (mode[0] == 'a')
155 f->_flags |= __SAPP;
156
157 /*
158 * Turn off buffering, so a write past the end of the buffer
159 * correctly returns a short object count.
160 */
161 setvbuf(f, NULL, _IONBF, 0);
162
163 return (f);
164 }
165
166 static int
fmemopen_read(void * cookie,char * buf,int nbytes)167 fmemopen_read(void *cookie, char *buf, int nbytes)
168 {
169 struct fmemopen_cookie *ck = cookie;
170
171 if (nbytes > ck->len - ck->off)
172 nbytes = ck->len - ck->off;
173
174 if (nbytes == 0)
175 return (0);
176
177 memcpy(buf, ck->buf + ck->off, nbytes);
178
179 ck->off += nbytes;
180
181 return (nbytes);
182 }
183
184 static int
fmemopen_write(void * cookie,const char * buf,int nbytes)185 fmemopen_write(void *cookie, const char *buf, int nbytes)
186 {
187 struct fmemopen_cookie *ck = cookie;
188
189 if (nbytes > ck->size - ck->off)
190 nbytes = ck->size - ck->off;
191
192 if (nbytes == 0)
193 return (0);
194
195 memcpy(ck->buf + ck->off, buf, nbytes);
196
197 ck->off += nbytes;
198
199 if (ck->off > ck->len)
200 ck->len = ck->off;
201
202 /*
203 * We append a NULL byte if all these conditions are met:
204 * - the buffer is not binary
205 * - the buffer is not full
206 * - the data just written doesn't already end with a NULL byte
207 */
208 if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
209 ck->buf[ck->off] = '\0';
210
211 return (nbytes);
212 }
213
214 static fpos_t
fmemopen_seek(void * cookie,fpos_t offset,int whence)215 fmemopen_seek(void *cookie, fpos_t offset, int whence)
216 {
217 struct fmemopen_cookie *ck = cookie;
218
219
220 switch (whence) {
221 case SEEK_SET:
222 if (offset > ck->size) {
223 errno = EINVAL;
224 return (-1);
225 }
226 ck->off = offset;
227 break;
228
229 case SEEK_CUR:
230 if (ck->off + offset > ck->size) {
231 errno = EINVAL;
232 return (-1);
233 }
234 ck->off += offset;
235 break;
236
237 case SEEK_END:
238 if (offset > 0 || -offset > ck->len) {
239 errno = EINVAL;
240 return (-1);
241 }
242 ck->off = ck->len + offset;
243 break;
244
245 default:
246 errno = EINVAL;
247 return (-1);
248 }
249
250 return (ck->off);
251 }
252
253 static int
fmemopen_close(void * cookie)254 fmemopen_close(void *cookie)
255 {
256 struct fmemopen_cookie *ck = cookie;
257
258 if (ck->own)
259 free(ck->buf);
260
261 free(ck);
262
263 return (0);
264 }
265