1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 <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
39struct 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
49static int	fmemopen_read(void *cookie, char *buf, int nbytes);
50static int	fmemopen_write(void *cookie, const char *buf, int nbytes);
51static fpos_t	fmemopen_seek(void *cookie, fpos_t offset, int whence);
52static int	fmemopen_close(void *cookie);
53
54FILE *
55fmemopen(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
166static int
167fmemopen_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
184static int
185fmemopen_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
214static fpos_t
215fmemopen_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
253static int
254fmemopen_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