fmemopen.c revision 246148
1246120Sgahr/*-
2246120SgahrCopyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
3246120Sgahr
4246120SgahrRedistribution and use in source and binary forms, with or without
5246120Sgahrmodification, are permitted provided that the following conditions
6246120Sgahrare met:
7246120Sgahr1. Redistributions of source code must retain the above copyright
8246120Sgahr   notice, this list of conditions and the following disclaimer.
9246120Sgahr2. Redistributions in binary form must reproduce the above copyright
10246120Sgahr   notice, this list of conditions and the following disclaimer in the
11246120Sgahr   documentation and/or other materials provided with the distribution.
12246120Sgahr
13246120SgahrTHIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14246120SgahrANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15246120SgahrIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16246120SgahrARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17246120SgahrFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18246120SgahrDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19246120SgahrOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20246120SgahrHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21246120SgahrLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22246120SgahrOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23246120SgahrSUCH DAMAGE.
24246120Sgahr*/
25246120Sgahr
26246120Sgahr#include <sys/cdefs.h>
27246120Sgahr__FBSDID("$FreeBSD: head/lib/libc/stdio/fmemopen.c 246148 2013-01-31 16:39:50Z gahr $");
28246120Sgahr
29246148Sgahr#include <fcntl.h>
30246120Sgahr#include <stdio.h>
31246120Sgahr#include <stdlib.h>
32246120Sgahr#include <string.h>
33246120Sgahr#include <errno.h>
34246148Sgahr#include "local.h"
35246120Sgahr
36246148Sgahrstruct fmemopen_cookie
37246120Sgahr{
38246148Sgahr	char	*buf;	/* pointer to the memory region */
39246148Sgahr	char	 own;	/* did we allocate the buffer ourselves? */
40246148Sgahr	char     bin;   /* is this a binary buffer? */
41246148Sgahr	size_t	 size;	/* buffer length in bytes */
42246148Sgahr	size_t	 len;	/* data length in bytes */
43246148Sgahr	size_t	 off;	/* current offset into the buffer */
44246120Sgahr};
45246120Sgahr
46246120Sgahrstatic int	fmemopen_read  (void *cookie, char *buf, int nbytes);
47246120Sgahrstatic int	fmemopen_write (void *cookie, const char *buf, int nbytes);
48246120Sgahrstatic fpos_t	fmemopen_seek  (void *cookie, fpos_t offset, int whence);
49246120Sgahrstatic int	fmemopen_close (void *cookie);
50246120Sgahr
51246120SgahrFILE *
52246120Sgahrfmemopen (void * __restrict buf, size_t size, const char * __restrict mode)
53246120Sgahr{
54246148Sgahr	struct fmemopen_cookie *ck;
55246148Sgahr	FILE *f;
56246148Sgahr	int flags, rc;
57246148Sgahr
58246148Sgahr	/*
59246148Sgahr	 * Retrieve the flags as used by open(2) from the mode argument, and
60246148Sgahr	 * validate them.
61246148Sgahr	 * */
62246148Sgahr	rc = __sflags (mode, &flags);
63246148Sgahr	if (rc == 0) {
64246148Sgahr		errno = EINVAL;
65246148Sgahr		return (NULL);
66246148Sgahr	}
67246148Sgahr
68246148Sgahr	/*
69246148Sgahr	 * There's no point in requiring an automatically allocated buffer
70246148Sgahr	 * in write-only mode.
71246148Sgahr	 */
72246148Sgahr	if (!(flags & O_RDWR) && buf == NULL) {
73246148Sgahr		errno = EINVAL;
74246148Sgahr		return (NULL);
75246148Sgahr	}
76246148Sgahr
77246148Sgahr	/* Allocate a cookie. */
78246148Sgahr	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) {
89246120Sgahr		ck->buf = malloc (size);
90246120Sgahr		if (ck->buf == NULL) {
91246120Sgahr			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:
110246148Sgahr	 *
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
115246148Sgahr	 *
116246148Sgahr	 * for read, the size of the buffer
117246148Sgahr	 *
118246148Sgahr	 * for write, 0
119246148Sgahr	 */
120246148Sgahr	switch (mode[0]) {
121246148Sgahr	case 'a':
122246148Sgahr		if (ck->bin) {
123246148Sgahr			/*
124246148Sgahr			 * This isn't useful, since the buffer isn't
125246148Sgahr			 * allowed 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
139246148Sgahr	/* Actuall wrapper. */
140246148Sgahr	f = funopen ((void *)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)
147246120Sgahr			free (ck->buf);
148246120Sgahr		free (ck);
149246120Sgahr		return (NULL);
150246120Sgahr	}
151246120Sgahr
152246148Sgahr	/*
153246148Sgahr	 * Turn off buffering, so a write past the end of the buffer
154246148Sgahr	 * correctly returns a short object count.
155246148Sgahr	 */
156246120Sgahr	setvbuf (f, (char *) NULL, _IONBF, 0);
157246120Sgahr
158246120Sgahr	return (f);
159246120Sgahr}
160246120Sgahr
161246120Sgahrstatic int
162246120Sgahrfmemopen_read (void *cookie, char *buf, int nbytes)
163246120Sgahr{
164246148Sgahr	struct fmemopen_cookie *ck = cookie;
165246120Sgahr
166246120Sgahr	if (nbytes > ck->len - ck->off)
167246120Sgahr		nbytes = ck->len - ck->off;
168246120Sgahr
169246120Sgahr	if (nbytes == 0)
170246120Sgahr		return (0);
171246120Sgahr
172246120Sgahr	memcpy (buf, ck->buf + ck->off, nbytes);
173246120Sgahr
174246120Sgahr	ck->off += nbytes;
175246120Sgahr
176246120Sgahr	return (nbytes);
177246120Sgahr}
178246120Sgahr
179246120Sgahrstatic int
180246120Sgahrfmemopen_write (void *cookie, const char *buf, int nbytes)
181246120Sgahr{
182246148Sgahr	struct fmemopen_cookie *ck = cookie;
183246120Sgahr
184246148Sgahr	if (nbytes > ck->size - ck->off)
185246148Sgahr		nbytes = ck->size - ck->off;
186246120Sgahr
187246120Sgahr	if (nbytes == 0)
188246120Sgahr		return (0);
189246120Sgahr
190246120Sgahr	memcpy (ck->buf + ck->off, buf, nbytes);
191246120Sgahr
192246120Sgahr	ck->off += nbytes;
193246120Sgahr
194246148Sgahr	if (ck->off > ck->len)
195246148Sgahr		ck->len = ck->off;
196246148Sgahr
197246148Sgahr	/*
198246148Sgahr	 * We append a NULL byte if all these conditions are met:
199246148Sgahr	 * - the buffer is not binary
200246148Sgahr	 * - the buffer is not full
201246148Sgahr	 * - the data just written doesn't already end with a NULL byte
202246148Sgahr	 */
203246148Sgahr	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
204246120Sgahr		ck->buf[ck->off] = '\0';
205246120Sgahr
206246120Sgahr	return (nbytes);
207246120Sgahr}
208246120Sgahr
209246120Sgahrstatic fpos_t
210246120Sgahrfmemopen_seek (void *cookie, fpos_t offset, int whence)
211246120Sgahr{
212246148Sgahr	struct fmemopen_cookie *ck = cookie;
213246120Sgahr
214246120Sgahr
215246120Sgahr	switch (whence) {
216246120Sgahr	case SEEK_SET:
217246148Sgahr		if (offset > ck->size) {
218246120Sgahr			errno = EINVAL;
219246120Sgahr			return (-1);
220246120Sgahr		}
221246120Sgahr		ck->off = offset;
222246120Sgahr		break;
223246120Sgahr
224246120Sgahr	case SEEK_CUR:
225246148Sgahr		if (ck->off + offset > ck->size) {
226246120Sgahr			errno = EINVAL;
227246120Sgahr			return (-1);
228246120Sgahr		}
229246120Sgahr		ck->off += offset;
230246120Sgahr		break;
231246120Sgahr
232246120Sgahr	case SEEK_END:
233246120Sgahr		if (offset > 0 || -offset > ck->len) {
234246120Sgahr			errno = EINVAL;
235246120Sgahr			return (-1);
236246120Sgahr		}
237246120Sgahr		ck->off = ck->len + offset;
238246120Sgahr		break;
239246120Sgahr
240246120Sgahr	default:
241246120Sgahr		errno = EINVAL;
242246120Sgahr		return (-1);
243246120Sgahr	}
244246120Sgahr
245246120Sgahr	return (ck->off);
246246120Sgahr}
247246120Sgahr
248246120Sgahrstatic int
249246120Sgahrfmemopen_close (void *cookie)
250246120Sgahr{
251246148Sgahr	struct fmemopen_cookie *ck = cookie;
252246120Sgahr
253246120Sgahr	if (ck->own)
254246120Sgahr		free (ck->buf);
255246120Sgahr
256246120Sgahr	free (ck);
257246120Sgahr
258246120Sgahr	return (0);
259246120Sgahr}
260