1246120Sgahr/*-
2252343Sjhb * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
3252343Sjhb *
4252343Sjhb * Redistribution and use in source and binary forms, with or without
5252343Sjhb * modification, are permitted provided that the following conditions
6252343Sjhb * are met:
7252343Sjhb * 1. Redistributions of source code must retain the above copyright
8252343Sjhb *    notice, this list of conditions and the following disclaimer.
9252343Sjhb * 2. Redistributions in binary form must reproduce the above copyright
10252343Sjhb *    notice, this list of conditions and the following disclaimer in the
11252343Sjhb *    documentation and/or other materials provided with the distribution.
12252343Sjhb *
13252343Sjhb * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14252343Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15252343Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16252343Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17252343Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18252343Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19252343Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20252343Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21252343Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22252343Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23252343Sjhb * SUCH DAMAGE.
24252343Sjhb */
25246120Sgahr
26246120Sgahr#include <sys/cdefs.h>
27246120Sgahr__FBSDID("$FreeBSD$");
28246120Sgahr
29252343Sjhb#include <fcntl.h>
30252343Sjhb#include <stdbool.h>
31246120Sgahr#include <stdio.h>
32246120Sgahr#include <stdlib.h>
33246120Sgahr#include <string.h>
34246120Sgahr#include <errno.h>
35252343Sjhb#include "local.h"
36246120Sgahr
37252343Sjhbstruct fmemopen_cookie
38246120Sgahr{
39252343Sjhb	char	*buf;	/* pointer to the memory region */
40252343Sjhb	bool	 own;	/* did we allocate the buffer ourselves? */
41252343Sjhb	char     bin;   /* is this a binary buffer? */
42252343Sjhb	size_t	 size;	/* buffer length in bytes */
43252343Sjhb	size_t	 len;	/* data length in bytes */
44252343Sjhb	size_t	 off;	/* current offset into the buffer */
45246120Sgahr};
46246120Sgahr
47252343Sjhbstatic int	fmemopen_read(void *cookie, char *buf, int nbytes);
48252343Sjhbstatic int	fmemopen_write(void *cookie, const char *buf, int nbytes);
49252343Sjhbstatic fpos_t	fmemopen_seek(void *cookie, fpos_t offset, int whence);
50252343Sjhbstatic int	fmemopen_close(void *cookie);
51246120Sgahr
52246120SgahrFILE *
53252343Sjhbfmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
54246120Sgahr{
55252343Sjhb	struct fmemopen_cookie *ck;
56252343Sjhb	FILE *f;
57252343Sjhb	int flags, rc;
58252343Sjhb
59252343Sjhb	/*
60252343Sjhb	 * Retrieve the flags as used by open(2) from the mode argument, and
61252343Sjhb	 * validate them.
62252343Sjhb	 */
63252343Sjhb	rc = __sflags(mode, &flags);
64252343Sjhb	if (rc == 0) {
65252343Sjhb		errno = EINVAL;
66252343Sjhb		return (NULL);
67252343Sjhb	}
68252343Sjhb
69252343Sjhb	/*
70252343Sjhb	 * There's no point in requiring an automatically allocated buffer
71252343Sjhb	 * in write-only mode.
72252343Sjhb	 */
73252343Sjhb	if (!(flags & O_RDWR) && buf == NULL) {
74252343Sjhb		errno = EINVAL;
75252343Sjhb		return (NULL);
76252343Sjhb	}
77252343Sjhb
78252343Sjhb	ck = malloc(sizeof(struct fmemopen_cookie));
79246120Sgahr	if (ck == NULL) {
80246120Sgahr		return (NULL);
81246120Sgahr	}
82246120Sgahr
83252343Sjhb	ck->off  = 0;
84252343Sjhb	ck->size = size;
85246120Sgahr
86252343Sjhb	/* Check whether we have to allocate the buffer ourselves. */
87246120Sgahr	ck->own = ((ck->buf = buf) == NULL);
88246120Sgahr	if (ck->own) {
89252343Sjhb		ck->buf = malloc(size);
90246120Sgahr		if (ck->buf == NULL) {
91252343Sjhb			free(ck);
92246120Sgahr			return (NULL);
93246120Sgahr		}
94252343Sjhb	}
95252343Sjhb
96252343Sjhb	/*
97252343Sjhb	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
98252343Sjhb	 * truncate the buffer.
99252343Sjhb	 */
100252343Sjhb	if (ck->own || mode[0] == 'w') {
101246120Sgahr		ck->buf[0] = '\0';
102246120Sgahr	}
103246120Sgahr
104252343Sjhb	/* Check for binary mode. */
105252343Sjhb	ck->bin = strchr(mode, 'b') != NULL;
106246120Sgahr
107252343Sjhb	/*
108252343Sjhb	 * The size of the current buffer contents is set depending on the
109252343Sjhb	 * mode:
110252343Sjhb	 *
111252343Sjhb	 * for append (text-mode), the position of the first NULL byte, or the
112252343Sjhb	 * size of the buffer if none is found
113252343Sjhb	 *
114252343Sjhb	 * for append (binary-mode), the size of the buffer
115252343Sjhb	 *
116252343Sjhb	 * for read, the size of the buffer
117252343Sjhb	 *
118252343Sjhb	 * for write, 0
119252343Sjhb	 */
120252343Sjhb	switch (mode[0]) {
121252343Sjhb	case 'a':
122252343Sjhb		if (ck->bin) {
123252343Sjhb			/*
124252343Sjhb			 * This isn't useful, since the buffer isn't allowed
125252343Sjhb			 * to grow.
126252343Sjhb			 */
127252343Sjhb			ck->off = ck->len = size;
128252343Sjhb		} else
129252343Sjhb			ck->off = ck->len = strnlen(ck->buf, ck->size);
130252343Sjhb		break;
131252343Sjhb	case 'r':
132252343Sjhb		ck->len = size;
133252343Sjhb		break;
134252343Sjhb	case 'w':
135252343Sjhb		ck->len = 0;
136252343Sjhb		break;
137252343Sjhb	}
138252343Sjhb
139252343Sjhb	f = funopen(ck,
140252343Sjhb	    flags & O_WRONLY ? NULL : fmemopen_read,
141252343Sjhb	    flags & O_RDONLY ? NULL : fmemopen_write,
142246120Sgahr	    fmemopen_seek, fmemopen_close);
143246120Sgahr
144246120Sgahr	if (f == NULL) {
145246120Sgahr		if (ck->own)
146252343Sjhb			free(ck->buf);
147252343Sjhb		free(ck);
148246120Sgahr		return (NULL);
149246120Sgahr	}
150246120Sgahr
151252343Sjhb	/*
152252343Sjhb	 * Turn off buffering, so a write past the end of the buffer
153252343Sjhb	 * correctly returns a short object count.
154252343Sjhb	 */
155252343Sjhb	setvbuf(f, NULL, _IONBF, 0);
156246120Sgahr
157246120Sgahr	return (f);
158246120Sgahr}
159246120Sgahr
160246120Sgahrstatic int
161252343Sjhbfmemopen_read(void *cookie, char *buf, int nbytes)
162246120Sgahr{
163252343Sjhb	struct fmemopen_cookie *ck = cookie;
164246120Sgahr
165246120Sgahr	if (nbytes > ck->len - ck->off)
166246120Sgahr		nbytes = ck->len - ck->off;
167246120Sgahr
168246120Sgahr	if (nbytes == 0)
169246120Sgahr		return (0);
170246120Sgahr
171252343Sjhb	memcpy(buf, ck->buf + ck->off, nbytes);
172246120Sgahr
173246120Sgahr	ck->off += nbytes;
174246120Sgahr
175246120Sgahr	return (nbytes);
176246120Sgahr}
177246120Sgahr
178246120Sgahrstatic int
179252343Sjhbfmemopen_write(void *cookie, const char *buf, int nbytes)
180246120Sgahr{
181252343Sjhb	struct fmemopen_cookie *ck = cookie;
182246120Sgahr
183252343Sjhb	if (nbytes > ck->size - ck->off)
184252343Sjhb		nbytes = ck->size - ck->off;
185246120Sgahr
186246120Sgahr	if (nbytes == 0)
187246120Sgahr		return (0);
188246120Sgahr
189252343Sjhb	memcpy(ck->buf + ck->off, buf, nbytes);
190246120Sgahr
191246120Sgahr	ck->off += nbytes;
192246120Sgahr
193252343Sjhb	if (ck->off > ck->len)
194252343Sjhb		ck->len = ck->off;
195252343Sjhb
196252343Sjhb	/*
197252343Sjhb	 * We append a NULL byte if all these conditions are met:
198252343Sjhb	 * - the buffer is not binary
199252343Sjhb	 * - the buffer is not full
200252343Sjhb	 * - the data just written doesn't already end with a NULL byte
201252343Sjhb	 */
202252343Sjhb	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
203246120Sgahr		ck->buf[ck->off] = '\0';
204246120Sgahr
205246120Sgahr	return (nbytes);
206246120Sgahr}
207246120Sgahr
208246120Sgahrstatic fpos_t
209252343Sjhbfmemopen_seek(void *cookie, fpos_t offset, int whence)
210246120Sgahr{
211252343Sjhb	struct fmemopen_cookie *ck = cookie;
212246120Sgahr
213246120Sgahr
214246120Sgahr	switch (whence) {
215246120Sgahr	case SEEK_SET:
216252343Sjhb		if (offset > ck->size) {
217246120Sgahr			errno = EINVAL;
218246120Sgahr			return (-1);
219246120Sgahr		}
220246120Sgahr		ck->off = offset;
221246120Sgahr		break;
222246120Sgahr
223246120Sgahr	case SEEK_CUR:
224252343Sjhb		if (ck->off + offset > ck->size) {
225246120Sgahr			errno = EINVAL;
226246120Sgahr			return (-1);
227246120Sgahr		}
228246120Sgahr		ck->off += offset;
229246120Sgahr		break;
230246120Sgahr
231246120Sgahr	case SEEK_END:
232246120Sgahr		if (offset > 0 || -offset > ck->len) {
233246120Sgahr			errno = EINVAL;
234246120Sgahr			return (-1);
235246120Sgahr		}
236246120Sgahr		ck->off = ck->len + offset;
237246120Sgahr		break;
238246120Sgahr
239246120Sgahr	default:
240246120Sgahr		errno = EINVAL;
241246120Sgahr		return (-1);
242246120Sgahr	}
243246120Sgahr
244246120Sgahr	return (ck->off);
245246120Sgahr}
246246120Sgahr
247246120Sgahrstatic int
248252343Sjhbfmemopen_close(void *cookie)
249246120Sgahr{
250252343Sjhb	struct fmemopen_cookie *ck = cookie;
251246120Sgahr
252246120Sgahr	if (ck->own)
253252343Sjhb		free(ck->buf);
254246120Sgahr
255252343Sjhb	free(ck);
256246120Sgahr
257246120Sgahr	return (0);
258246120Sgahr}
259