1246120Sgahr/*-
2246206Sgahr * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
3246206Sgahr *
4246206Sgahr * Redistribution and use in source and binary forms, with or without
5246206Sgahr * modification, are permitted provided that the following conditions
6246206Sgahr * are met:
7246206Sgahr * 1. Redistributions of source code must retain the above copyright
8246206Sgahr *    notice, this list of conditions and the following disclaimer.
9246206Sgahr * 2. Redistributions in binary form must reproduce the above copyright
10246206Sgahr *    notice, this list of conditions and the following disclaimer in the
11246206Sgahr *    documentation and/or other materials provided with the distribution.
12246206Sgahr *
13246206Sgahr * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14246206Sgahr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15246206Sgahr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16246206Sgahr * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17246206Sgahr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18246206Sgahr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19246206Sgahr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20246206Sgahr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21246206Sgahr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22246206Sgahr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23246206Sgahr * SUCH DAMAGE.
24246206Sgahr */
25246120Sgahr
26246120Sgahr#include <sys/cdefs.h>
27246120Sgahr__FBSDID("$FreeBSD$");
28246120Sgahr
29246148Sgahr#include <fcntl.h>
30246206Sgahr#include <stdbool.h>
31246120Sgahr#include <stdio.h>
32246120Sgahr#include <stdlib.h>
33246120Sgahr#include <string.h>
34246120Sgahr#include <errno.h>
35246148Sgahr#include "local.h"
36246120Sgahr
37246148Sgahrstruct fmemopen_cookie
38246120Sgahr{
39246148Sgahr	char	*buf;	/* pointer to the memory region */
40246206Sgahr	bool	 own;	/* did we allocate the buffer ourselves? */
41246148Sgahr	char     bin;   /* is this a binary buffer? */
42246148Sgahr	size_t	 size;	/* buffer length in bytes */
43246148Sgahr	size_t	 len;	/* data length in bytes */
44246148Sgahr	size_t	 off;	/* current offset into the buffer */
45246120Sgahr};
46246120Sgahr
47246206Sgahrstatic int	fmemopen_read(void *cookie, char *buf, int nbytes);
48246206Sgahrstatic int	fmemopen_write(void *cookie, const char *buf, int nbytes);
49246206Sgahrstatic fpos_t	fmemopen_seek(void *cookie, fpos_t offset, int whence);
50246206Sgahrstatic int	fmemopen_close(void *cookie);
51246120Sgahr
52246120SgahrFILE *
53246206Sgahrfmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
54246120Sgahr{
55246148Sgahr	struct fmemopen_cookie *ck;
56246148Sgahr	FILE *f;
57246148Sgahr	int flags, rc;
58246148Sgahr
59246206Sgahr	/*
60246148Sgahr	 * Retrieve the flags as used by open(2) from the mode argument, and
61246148Sgahr	 * validate them.
62246206Sgahr	 */
63246206Sgahr	rc = __sflags(mode, &flags);
64246148Sgahr	if (rc == 0) {
65246148Sgahr		errno = EINVAL;
66246148Sgahr		return (NULL);
67246148Sgahr	}
68246148Sgahr
69246206Sgahr	/*
70246148Sgahr	 * There's no point in requiring an automatically allocated buffer
71246148Sgahr	 * in write-only mode.
72246148Sgahr	 */
73246148Sgahr	if (!(flags & O_RDWR) && buf == NULL) {
74246148Sgahr		errno = EINVAL;
75246148Sgahr		return (NULL);
76246148Sgahr	}
77246148Sgahr
78246206Sgahr	ck = malloc(sizeof(struct fmemopen_cookie));
79246120Sgahr	if (ck == NULL) {
80246120Sgahr		return (NULL);
81246120Sgahr	}
82246120Sgahr
83246148Sgahr	ck->off  = 0;
84246148Sgahr	ck->size = size;
85246120Sgahr
86246148Sgahr	/* Check whether we have to allocate the buffer ourselves. */
87246120Sgahr	ck->own = ((ck->buf = buf) == NULL);
88246120Sgahr	if (ck->own) {
89246206Sgahr		ck->buf = malloc(size);
90246120Sgahr		if (ck->buf == NULL) {
91246206Sgahr			free(ck);
92246120Sgahr			return (NULL);
93246120Sgahr		}
94246148Sgahr	}
95246148Sgahr
96246148Sgahr	/*
97246148Sgahr	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
98246148Sgahr	 * truncate the buffer.
99246148Sgahr	 */
100246148Sgahr	if (ck->own || mode[0] == 'w') {
101246120Sgahr		ck->buf[0] = '\0';
102246120Sgahr	}
103246120Sgahr
104246148Sgahr	/* Check for binary mode. */
105246148Sgahr	ck->bin = strchr(mode, 'b') != NULL;
106246120Sgahr
107246148Sgahr	/*
108246148Sgahr	 * The size of the current buffer contents is set depending on the
109246148Sgahr	 * mode:
110246206Sgahr	 *
111246148Sgahr	 * for append (text-mode), the position of the first NULL byte, or the
112246148Sgahr	 * size of the buffer if none is found
113246148Sgahr	 *
114246148Sgahr	 * for append (binary-mode), the size of the buffer
115246206Sgahr	 *
116246148Sgahr	 * for read, the size of the buffer
117246206Sgahr	 *
118246148Sgahr	 * for write, 0
119246148Sgahr	 */
120246148Sgahr	switch (mode[0]) {
121246148Sgahr	case 'a':
122246148Sgahr		if (ck->bin) {
123246206Sgahr			/*
124246206Sgahr			 * This isn't useful, since the buffer isn't allowed
125246206Sgahr			 * to grow.
126246148Sgahr			 */
127246148Sgahr			ck->off = ck->len = size;
128246148Sgahr		} else
129246148Sgahr			ck->off = ck->len = strnlen(ck->buf, ck->size);
130246148Sgahr		break;
131246148Sgahr	case 'r':
132246148Sgahr		ck->len = size;
133246148Sgahr		break;
134246148Sgahr	case 'w':
135246148Sgahr		ck->len = 0;
136246148Sgahr		break;
137246148Sgahr	}
138246148Sgahr
139246206Sgahr	f = funopen(ck,
140246148Sgahr	    flags & O_WRONLY ? NULL : fmemopen_read,
141246148Sgahr	    flags & O_RDONLY ? NULL : fmemopen_write,
142246120Sgahr	    fmemopen_seek, fmemopen_close);
143246120Sgahr
144246120Sgahr	if (f == NULL) {
145246120Sgahr		if (ck->own)
146246206Sgahr			free(ck->buf);
147246206Sgahr		free(ck);
148246120Sgahr		return (NULL);
149246120Sgahr	}
150246120Sgahr
151246148Sgahr	/*
152246148Sgahr	 * Turn off buffering, so a write past the end of the buffer
153246148Sgahr	 * correctly returns a short object count.
154246148Sgahr	 */
155246206Sgahr	setvbuf(f, NULL, _IONBF, 0);
156246120Sgahr
157246120Sgahr	return (f);
158246120Sgahr}
159246120Sgahr
160246120Sgahrstatic int
161246206Sgahrfmemopen_read(void *cookie, char *buf, int nbytes)
162246120Sgahr{
163246148Sgahr	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
171246206Sgahr	memcpy(buf, ck->buf + ck->off, nbytes);
172246120Sgahr
173246120Sgahr	ck->off += nbytes;
174246120Sgahr
175246120Sgahr	return (nbytes);
176246120Sgahr}
177246120Sgahr
178246120Sgahrstatic int
179246206Sgahrfmemopen_write(void *cookie, const char *buf, int nbytes)
180246120Sgahr{
181246148Sgahr	struct fmemopen_cookie *ck = cookie;
182246120Sgahr
183246148Sgahr	if (nbytes > ck->size - ck->off)
184246148Sgahr		nbytes = ck->size - ck->off;
185246120Sgahr
186246120Sgahr	if (nbytes == 0)
187246120Sgahr		return (0);
188246120Sgahr
189246206Sgahr	memcpy(ck->buf + ck->off, buf, nbytes);
190246120Sgahr
191246120Sgahr	ck->off += nbytes;
192246120Sgahr
193246148Sgahr	if (ck->off > ck->len)
194246148Sgahr		ck->len = ck->off;
195246148Sgahr
196246148Sgahr	/*
197246148Sgahr	 * We append a NULL byte if all these conditions are met:
198246148Sgahr	 * - the buffer is not binary
199246148Sgahr	 * - the buffer is not full
200246148Sgahr	 * - the data just written doesn't already end with a NULL byte
201246148Sgahr	 */
202246148Sgahr	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
209246206Sgahrfmemopen_seek(void *cookie, fpos_t offset, int whence)
210246120Sgahr{
211246148Sgahr	struct fmemopen_cookie *ck = cookie;
212246120Sgahr
213246120Sgahr
214246120Sgahr	switch (whence) {
215246120Sgahr	case SEEK_SET:
216246148Sgahr		if (offset > ck->size) {
217246120Sgahr			errno = EINVAL;
218246120Sgahr			return (-1);
219246120Sgahr		}
220246120Sgahr		ck->off = offset;
221246120Sgahr		break;
222246120Sgahr
223246120Sgahr	case SEEK_CUR:
224246148Sgahr		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
248246206Sgahrfmemopen_close(void *cookie)
249246120Sgahr{
250246148Sgahr	struct fmemopen_cookie *ck = cookie;
251246120Sgahr
252246120Sgahr	if (ck->own)
253246206Sgahr		free(ck->buf);
254246120Sgahr
255246206Sgahr	free(ck);
256246120Sgahr
257246120Sgahr	return (0);
258246120Sgahr}
259