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
36struct 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
46static int	fmemopen_read(void *cookie, char *buf, int nbytes);
47static int	fmemopen_write(void *cookie, const char *buf, int nbytes);
48static fpos_t	fmemopen_seek(void *cookie, fpos_t offset, int whence);
49static int	fmemopen_close(void *cookie);
50
51FILE *
52fmemopen(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	 * There's no point in requiring an automatically allocated buffer
78	 * in write-only mode.
79	 */
80	if (!(flags & O_RDWR) && buf == NULL) {
81		errno = EINVAL;
82		return (NULL);
83	}
84
85	ck = malloc(sizeof(struct fmemopen_cookie));
86	if (ck == NULL) {
87		return (NULL);
88	}
89
90	ck->off  = 0;
91	ck->size = size;
92
93	/* Check whether we have to allocate the buffer ourselves. */
94	ck->own = ((ck->buf = buf) == NULL);
95	if (ck->own) {
96		ck->buf = malloc(size);
97		if (ck->buf == NULL) {
98			free(ck);
99			return (NULL);
100		}
101	}
102
103	/*
104	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
105	 * truncate the buffer.
106	 */
107	if (ck->own || mode[0] == 'w') {
108		ck->buf[0] = '\0';
109	}
110
111	/* Check for binary mode. */
112	ck->bin = strchr(mode, 'b') != NULL;
113
114	/*
115	 * The size of the current buffer contents is set depending on the
116	 * mode:
117	 *
118	 * for append (text-mode), the position of the first NULL byte, or the
119	 * size of the buffer if none is found
120	 *
121	 * for append (binary-mode), the size of the buffer
122	 *
123	 * for read, the size of the buffer
124	 *
125	 * for write, 0
126	 */
127	switch (mode[0]) {
128	case 'a':
129		ck->off = ck->len = strnlen(ck->buf, ck->size);
130		break;
131	case 'r':
132		ck->len = size;
133		break;
134	case 'w':
135		ck->len = 0;
136		break;
137	}
138
139	f = funopen(ck,
140	    flags & O_WRONLY ? NULL : fmemopen_read,
141	    flags & 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
163static int
164fmemopen_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
181static int
182fmemopen_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
211static fpos_t
212fmemopen_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
250static int
251fmemopen_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