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	/*
60266971Sgahr	 * POSIX says we shall return EINVAL if size is 0.
61266971Sgahr	 */
62266971Sgahr	if (size == 0) {
63266971Sgahr		errno = EINVAL;
64266971Sgahr		return (NULL);
65266971Sgahr	}
66266971Sgahr
67266971Sgahr	/*
68246148Sgahr	 * Retrieve the flags as used by open(2) from the mode argument, and
69246148Sgahr	 * validate them.
70246206Sgahr	 */
71246206Sgahr	rc = __sflags(mode, &flags);
72246148Sgahr	if (rc == 0) {
73246148Sgahr		errno = EINVAL;
74246148Sgahr		return (NULL);
75246148Sgahr	}
76246148Sgahr
77246206Sgahr	/*
78246148Sgahr	 * There's no point in requiring an automatically allocated buffer
79246148Sgahr	 * in write-only mode.
80246148Sgahr	 */
81246148Sgahr	if (!(flags & O_RDWR) && buf == NULL) {
82246148Sgahr		errno = EINVAL;
83246148Sgahr		return (NULL);
84246148Sgahr	}
85246148Sgahr
86246206Sgahr	ck = malloc(sizeof(struct fmemopen_cookie));
87246120Sgahr	if (ck == NULL) {
88246120Sgahr		return (NULL);
89246120Sgahr	}
90246120Sgahr
91246148Sgahr	ck->off  = 0;
92246148Sgahr	ck->size = size;
93246120Sgahr
94246148Sgahr	/* Check whether we have to allocate the buffer ourselves. */
95246120Sgahr	ck->own = ((ck->buf = buf) == NULL);
96246120Sgahr	if (ck->own) {
97246206Sgahr		ck->buf = malloc(size);
98246120Sgahr		if (ck->buf == NULL) {
99246206Sgahr			free(ck);
100246120Sgahr			return (NULL);
101246120Sgahr		}
102246148Sgahr	}
103246148Sgahr
104246148Sgahr	/*
105246148Sgahr	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
106246148Sgahr	 * truncate the buffer.
107246148Sgahr	 */
108246148Sgahr	if (ck->own || mode[0] == 'w') {
109246120Sgahr		ck->buf[0] = '\0';
110246120Sgahr	}
111246120Sgahr
112246148Sgahr	/* Check for binary mode. */
113246148Sgahr	ck->bin = strchr(mode, 'b') != NULL;
114246120Sgahr
115246148Sgahr	/*
116246148Sgahr	 * The size of the current buffer contents is set depending on the
117246148Sgahr	 * mode:
118246206Sgahr	 *
119246148Sgahr	 * for append (text-mode), the position of the first NULL byte, or the
120246148Sgahr	 * size of the buffer if none is found
121246148Sgahr	 *
122246148Sgahr	 * for append (binary-mode), the size of the buffer
123246206Sgahr	 *
124246148Sgahr	 * for read, the size of the buffer
125246206Sgahr	 *
126246148Sgahr	 * for write, 0
127246148Sgahr	 */
128246148Sgahr	switch (mode[0]) {
129246148Sgahr	case 'a':
130266971Sgahr		ck->off = ck->len = strnlen(ck->buf, ck->size);
131246148Sgahr		break;
132246148Sgahr	case 'r':
133246148Sgahr		ck->len = size;
134246148Sgahr		break;
135246148Sgahr	case 'w':
136246148Sgahr		ck->len = 0;
137246148Sgahr		break;
138246148Sgahr	}
139246148Sgahr
140246206Sgahr	f = funopen(ck,
141246148Sgahr	    flags & O_WRONLY ? NULL : fmemopen_read,
142246148Sgahr	    flags & O_RDONLY ? NULL : fmemopen_write,
143246120Sgahr	    fmemopen_seek, fmemopen_close);
144246120Sgahr
145246120Sgahr	if (f == NULL) {
146246120Sgahr		if (ck->own)
147246206Sgahr			free(ck->buf);
148246206Sgahr		free(ck);
149246120Sgahr		return (NULL);
150246120Sgahr	}
151246120Sgahr
152289931Sache	if (mode[0] == 'a')
153289931Sache		f->_flags |= __SAPP;
154289931Sache
155246148Sgahr	/*
156246148Sgahr	 * Turn off buffering, so a write past the end of the buffer
157246148Sgahr	 * correctly returns a short object count.
158246148Sgahr	 */
159246206Sgahr	setvbuf(f, NULL, _IONBF, 0);
160246120Sgahr
161246120Sgahr	return (f);
162246120Sgahr}
163246120Sgahr
164246120Sgahrstatic int
165246206Sgahrfmemopen_read(void *cookie, char *buf, int nbytes)
166246120Sgahr{
167246148Sgahr	struct fmemopen_cookie *ck = cookie;
168246120Sgahr
169246120Sgahr	if (nbytes > ck->len - ck->off)
170246120Sgahr		nbytes = ck->len - ck->off;
171246120Sgahr
172246120Sgahr	if (nbytes == 0)
173246120Sgahr		return (0);
174246120Sgahr
175246206Sgahr	memcpy(buf, ck->buf + ck->off, nbytes);
176246120Sgahr
177246120Sgahr	ck->off += nbytes;
178246120Sgahr
179246120Sgahr	return (nbytes);
180246120Sgahr}
181246120Sgahr
182246120Sgahrstatic int
183246206Sgahrfmemopen_write(void *cookie, const char *buf, int nbytes)
184246120Sgahr{
185246148Sgahr	struct fmemopen_cookie *ck = cookie;
186246120Sgahr
187246148Sgahr	if (nbytes > ck->size - ck->off)
188246148Sgahr		nbytes = ck->size - ck->off;
189246120Sgahr
190246120Sgahr	if (nbytes == 0)
191246120Sgahr		return (0);
192246120Sgahr
193246206Sgahr	memcpy(ck->buf + ck->off, buf, nbytes);
194246120Sgahr
195246120Sgahr	ck->off += nbytes;
196246120Sgahr
197246148Sgahr	if (ck->off > ck->len)
198246148Sgahr		ck->len = ck->off;
199246148Sgahr
200246148Sgahr	/*
201246148Sgahr	 * We append a NULL byte if all these conditions are met:
202246148Sgahr	 * - the buffer is not binary
203246148Sgahr	 * - the buffer is not full
204246148Sgahr	 * - the data just written doesn't already end with a NULL byte
205246148Sgahr	 */
206246148Sgahr	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
207246120Sgahr		ck->buf[ck->off] = '\0';
208246120Sgahr
209246120Sgahr	return (nbytes);
210246120Sgahr}
211246120Sgahr
212246120Sgahrstatic fpos_t
213246206Sgahrfmemopen_seek(void *cookie, fpos_t offset, int whence)
214246120Sgahr{
215246148Sgahr	struct fmemopen_cookie *ck = cookie;
216246120Sgahr
217246120Sgahr
218246120Sgahr	switch (whence) {
219246120Sgahr	case SEEK_SET:
220246148Sgahr		if (offset > ck->size) {
221246120Sgahr			errno = EINVAL;
222246120Sgahr			return (-1);
223246120Sgahr		}
224246120Sgahr		ck->off = offset;
225246120Sgahr		break;
226246120Sgahr
227246120Sgahr	case SEEK_CUR:
228246148Sgahr		if (ck->off + offset > ck->size) {
229246120Sgahr			errno = EINVAL;
230246120Sgahr			return (-1);
231246120Sgahr		}
232246120Sgahr		ck->off += offset;
233246120Sgahr		break;
234246120Sgahr
235246120Sgahr	case SEEK_END:
236246120Sgahr		if (offset > 0 || -offset > ck->len) {
237246120Sgahr			errno = EINVAL;
238246120Sgahr			return (-1);
239246120Sgahr		}
240246120Sgahr		ck->off = ck->len + offset;
241246120Sgahr		break;
242246120Sgahr
243246120Sgahr	default:
244246120Sgahr		errno = EINVAL;
245246120Sgahr		return (-1);
246246120Sgahr	}
247246120Sgahr
248246120Sgahr	return (ck->off);
249246120Sgahr}
250246120Sgahr
251246120Sgahrstatic int
252246206Sgahrfmemopen_close(void *cookie)
253246120Sgahr{
254246148Sgahr	struct fmemopen_cookie *ck = cookie;
255246120Sgahr
256246120Sgahr	if (ck->own)
257246206Sgahr		free(ck->buf);
258246120Sgahr
259246206Sgahr	free(ck);
260246120Sgahr
261246120Sgahr	return (0);
262246120Sgahr}
263