1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
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 <fcntl.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include "local.h"
35
36 struct fmemopen_cookie
37 {
38 char *buf; /* pointer to the memory region */
39 bool own; /* did we allocate the buffer ourselves? */
40 char bin; /* is this a binary buffer? */
41 size_t size; /* buffer length in bytes */
42 size_t len; /* data length in bytes */
43 size_t off; /* current offset into the buffer */
44 };
45
46 static int fmemopen_read(void *cookie, char *buf, int nbytes);
47 static int fmemopen_write(void *cookie, const char *buf, int nbytes);
48 static fpos_t fmemopen_seek(void *cookie, fpos_t offset, int whence);
49 static int fmemopen_close(void *cookie);
50
51 FILE *
fmemopen(void * __restrict buf,size_t size,const char * __restrict mode)52 fmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
53 {
54 struct fmemopen_cookie *ck;
55 FILE *f;
56 int flags, rc;
57
58 /*
59 * POSIX says we shall return EINVAL if size is 0.
60 */
61 if (size == 0) {
62 errno = EINVAL;
63 return (NULL);
64 }
65
66 /*
67 * Retrieve the flags as used by open(2) from the mode argument, and
68 * validate them.
69 */
70 rc = __sflags(mode, &flags);
71 if (rc == 0) {
72 errno = EINVAL;
73 return (NULL);
74 }
75
76 /*
77 * An automatically allocated buffer is only allowed in read-write mode.
78 */
79 if ((flags & O_ACCMODE) != O_RDWR && buf == NULL) {
80 errno = EINVAL;
81 return (NULL);
82 }
83
84 ck = malloc(sizeof(struct fmemopen_cookie));
85 if (ck == NULL) {
86 return (NULL);
87 }
88
89 ck->off = 0;
90 ck->size = size;
91
92 /* Check whether we have to allocate the buffer ourselves. */
93 ck->own = ((ck->buf = buf) == NULL);
94 if (ck->own) {
95 ck->buf = malloc(size);
96 if (ck->buf == NULL) {
97 free(ck);
98 return (NULL);
99 }
100 }
101
102 /*
103 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
104 * truncate the buffer.
105 */
106 if (ck->own || mode[0] == 'w') {
107 ck->buf[0] = '\0';
108 }
109
110 /* Check for binary mode. */
111 ck->bin = strchr(mode, 'b') != NULL;
112
113 /*
114 * The size of the current buffer contents is set depending on the
115 * mode:
116 *
117 * for append (text-mode), the position of the first NULL byte, or the
118 * size of the buffer if none is found
119 *
120 * for append (binary-mode), the size of the buffer
121 *
122 * for read, the size of the buffer
123 *
124 * for write, 0
125 */
126 switch (mode[0]) {
127 case 'a':
128 ck->off = ck->len = strnlen(ck->buf, ck->size);
129 break;
130 case 'r':
131 ck->len = size;
132 break;
133 case 'w':
134 ck->len = 0;
135 break;
136 }
137
138 /* Disable read in O_WRONLY mode, and write in O_RDONLY mode. */
139 f = funopen(ck,
140 (flags & O_ACCMODE) == O_WRONLY ? NULL : fmemopen_read,
141 (flags & O_ACCMODE) == O_RDONLY ? NULL : fmemopen_write,
142 fmemopen_seek, fmemopen_close);
143
144 if (f == NULL) {
145 if (ck->own)
146 free(ck->buf);
147 free(ck);
148 return (NULL);
149 }
150
151 if (mode[0] == 'a')
152 f->_flags |= __SAPP;
153
154 /*
155 * Turn off buffering, so a write past the end of the buffer
156 * correctly returns a short object count.
157 */
158 setvbuf(f, NULL, _IONBF, 0);
159
160 return (f);
161 }
162
163 static int
fmemopen_read(void * cookie,char * buf,int nbytes)164 fmemopen_read(void *cookie, char *buf, int nbytes)
165 {
166 struct fmemopen_cookie *ck = cookie;
167
168 if (nbytes > ck->len - ck->off)
169 nbytes = ck->len - ck->off;
170
171 if (nbytes == 0)
172 return (0);
173
174 memcpy(buf, ck->buf + ck->off, nbytes);
175
176 ck->off += nbytes;
177
178 return (nbytes);
179 }
180
181 static int
fmemopen_write(void * cookie,const char * buf,int nbytes)182 fmemopen_write(void *cookie, const char *buf, int nbytes)
183 {
184 struct fmemopen_cookie *ck = cookie;
185
186 if (nbytes > ck->size - ck->off)
187 nbytes = ck->size - ck->off;
188
189 if (nbytes == 0)
190 return (0);
191
192 memcpy(ck->buf + ck->off, buf, nbytes);
193
194 ck->off += nbytes;
195
196 if (ck->off > ck->len)
197 ck->len = ck->off;
198
199 /*
200 * We append a NULL byte if all these conditions are met:
201 * - the buffer is not binary
202 * - the buffer is not full
203 * - the data just written doesn't already end with a NULL byte
204 */
205 if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
206 ck->buf[ck->off] = '\0';
207
208 return (nbytes);
209 }
210
211 static fpos_t
fmemopen_seek(void * cookie,fpos_t offset,int whence)212 fmemopen_seek(void *cookie, fpos_t offset, int whence)
213 {
214 struct fmemopen_cookie *ck = cookie;
215
216
217 switch (whence) {
218 case SEEK_SET:
219 if (offset > ck->size) {
220 errno = EINVAL;
221 return (-1);
222 }
223 ck->off = offset;
224 break;
225
226 case SEEK_CUR:
227 if (ck->off + offset > ck->size) {
228 errno = EINVAL;
229 return (-1);
230 }
231 ck->off += offset;
232 break;
233
234 case SEEK_END:
235 if (offset > 0 || -offset > ck->len) {
236 errno = EINVAL;
237 return (-1);
238 }
239 ck->off = ck->len + offset;
240 break;
241
242 default:
243 errno = EINVAL;
244 return (-1);
245 }
246
247 return (ck->off);
248 }
249
250 static int
fmemopen_close(void * cookie)251 fmemopen_close(void *cookie)
252 {
253 struct fmemopen_cookie *ck = cookie;
254
255 if (ck->own)
256 free(ck->buf);
257
258 free(ck);
259
260 return (0);
261 }
262