xref: /freebsd/lib/libc/stdio/fmemopen.c (revision 646b68f04d021f978a3ded82442d5387e610e2a5)
196c95412SPietro Cerutti /*-
296c95412SPietro Cerutti Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
396c95412SPietro Cerutti 
496c95412SPietro Cerutti Redistribution and use in source and binary forms, with or without
596c95412SPietro Cerutti modification, are permitted provided that the following conditions
696c95412SPietro Cerutti are met:
796c95412SPietro Cerutti 1. Redistributions of source code must retain the above copyright
896c95412SPietro Cerutti    notice, this list of conditions and the following disclaimer.
996c95412SPietro Cerutti 2. Redistributions in binary form must reproduce the above copyright
1096c95412SPietro Cerutti    notice, this list of conditions and the following disclaimer in the
1196c95412SPietro Cerutti    documentation and/or other materials provided with the distribution.
1296c95412SPietro Cerutti 
1396c95412SPietro Cerutti THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1496c95412SPietro Cerutti ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1596c95412SPietro Cerutti IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1696c95412SPietro Cerutti ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
1796c95412SPietro Cerutti FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1896c95412SPietro Cerutti DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1996c95412SPietro Cerutti OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2096c95412SPietro Cerutti HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2196c95412SPietro Cerutti LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2296c95412SPietro Cerutti OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2396c95412SPietro Cerutti SUCH DAMAGE.
2496c95412SPietro Cerutti */
2596c95412SPietro Cerutti 
2696c95412SPietro Cerutti #include <sys/cdefs.h>
2796c95412SPietro Cerutti __FBSDID("$FreeBSD$");
2896c95412SPietro Cerutti 
29*646b68f0SPietro Cerutti #include <fcntl.h>
3096c95412SPietro Cerutti #include <stdio.h>
3196c95412SPietro Cerutti #include <stdlib.h>
3296c95412SPietro Cerutti #include <string.h>
3396c95412SPietro Cerutti #include <errno.h>
34*646b68f0SPietro Cerutti #include "local.h"
3596c95412SPietro Cerutti 
36*646b68f0SPietro Cerutti struct fmemopen_cookie
3796c95412SPietro Cerutti {
3896c95412SPietro Cerutti 	char	*buf;	/* pointer to the memory region */
3996c95412SPietro Cerutti 	char	 own;	/* did we allocate the buffer ourselves? */
40*646b68f0SPietro Cerutti 	char     bin;   /* is this a binary buffer? */
41*646b68f0SPietro Cerutti 	size_t	 size;	/* buffer length in bytes */
42*646b68f0SPietro Cerutti 	size_t	 len;	/* data length in bytes */
43*646b68f0SPietro Cerutti 	size_t	 off;	/* current offset into the buffer */
4496c95412SPietro Cerutti };
4596c95412SPietro Cerutti 
4696c95412SPietro Cerutti static int	fmemopen_read  (void *cookie, char *buf, int nbytes);
4796c95412SPietro Cerutti static int	fmemopen_write (void *cookie, const char *buf, int nbytes);
4896c95412SPietro Cerutti static fpos_t	fmemopen_seek  (void *cookie, fpos_t offset, int whence);
4996c95412SPietro Cerutti static int	fmemopen_close (void *cookie);
5096c95412SPietro Cerutti 
5196c95412SPietro Cerutti FILE *
5296c95412SPietro Cerutti fmemopen (void * __restrict buf, size_t size, const char * __restrict mode)
5396c95412SPietro Cerutti {
54*646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck;
55*646b68f0SPietro Cerutti 	FILE *f;
56*646b68f0SPietro Cerutti 	int flags, rc;
57*646b68f0SPietro Cerutti 
58*646b68f0SPietro Cerutti 	/*
59*646b68f0SPietro Cerutti 	 * Retrieve the flags as used by open(2) from the mode argument, and
60*646b68f0SPietro Cerutti 	 * validate them.
61*646b68f0SPietro Cerutti 	 * */
62*646b68f0SPietro Cerutti 	rc = __sflags (mode, &flags);
63*646b68f0SPietro Cerutti 	if (rc == 0) {
64*646b68f0SPietro Cerutti 		errno = EINVAL;
65*646b68f0SPietro Cerutti 		return (NULL);
66*646b68f0SPietro Cerutti 	}
67*646b68f0SPietro Cerutti 
68*646b68f0SPietro Cerutti 	/*
69*646b68f0SPietro Cerutti 	 * There's no point in requiring an automatically allocated buffer
70*646b68f0SPietro Cerutti 	 * in write-only mode.
71*646b68f0SPietro Cerutti 	 */
72*646b68f0SPietro Cerutti 	if (!(flags & O_RDWR) && buf == NULL) {
73*646b68f0SPietro Cerutti 		errno = EINVAL;
74*646b68f0SPietro Cerutti 		return (NULL);
75*646b68f0SPietro Cerutti 	}
76*646b68f0SPietro Cerutti 
77*646b68f0SPietro Cerutti 	/* Allocate a cookie. */
78*646b68f0SPietro Cerutti 	ck = malloc (sizeof (struct fmemopen_cookie));
7996c95412SPietro Cerutti 	if (ck == NULL) {
8096c95412SPietro Cerutti 		return (NULL);
8196c95412SPietro Cerutti 	}
8296c95412SPietro Cerutti 
8396c95412SPietro Cerutti 	ck->off  = 0;
84*646b68f0SPietro Cerutti 	ck->size = size;
8596c95412SPietro Cerutti 
86*646b68f0SPietro Cerutti 	/* Check whether we have to allocate the buffer ourselves. */
8796c95412SPietro Cerutti 	ck->own = ((ck->buf = buf) == NULL);
8896c95412SPietro Cerutti 	if (ck->own) {
8996c95412SPietro Cerutti 		ck->buf = malloc (size);
9096c95412SPietro Cerutti 		if (ck->buf == NULL) {
9196c95412SPietro Cerutti 			free (ck);
9296c95412SPietro Cerutti 			return (NULL);
9396c95412SPietro Cerutti 		}
94*646b68f0SPietro Cerutti 	}
95*646b68f0SPietro Cerutti 
96*646b68f0SPietro Cerutti 	/*
97*646b68f0SPietro Cerutti 	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
98*646b68f0SPietro Cerutti 	 * truncate the buffer.
99*646b68f0SPietro Cerutti 	 */
100*646b68f0SPietro Cerutti 	if (ck->own || mode[0] == 'w') {
10196c95412SPietro Cerutti 		ck->buf[0] = '\0';
10296c95412SPietro Cerutti 	}
10396c95412SPietro Cerutti 
104*646b68f0SPietro Cerutti 	/* Check for binary mode. */
105*646b68f0SPietro Cerutti 	ck->bin = strchr(mode, 'b') != NULL;
10696c95412SPietro Cerutti 
107*646b68f0SPietro Cerutti 	/*
108*646b68f0SPietro Cerutti 	 * The size of the current buffer contents is set depending on the
109*646b68f0SPietro Cerutti 	 * mode:
110*646b68f0SPietro Cerutti 	 *
111*646b68f0SPietro Cerutti 	 * for append (text-mode), the position of the first NULL byte, or the
112*646b68f0SPietro Cerutti 	 * size of the buffer if none is found
113*646b68f0SPietro Cerutti 	 *
114*646b68f0SPietro Cerutti 	 * for append (binary-mode), the size of the buffer
115*646b68f0SPietro Cerutti 	 *
116*646b68f0SPietro Cerutti 	 * for read, the size of the buffer
117*646b68f0SPietro Cerutti 	 *
118*646b68f0SPietro Cerutti 	 * for write, 0
119*646b68f0SPietro Cerutti 	 */
120*646b68f0SPietro Cerutti 	switch (mode[0]) {
121*646b68f0SPietro Cerutti 	case 'a':
122*646b68f0SPietro Cerutti 		if (ck->bin) {
123*646b68f0SPietro Cerutti 			/*
124*646b68f0SPietro Cerutti 			 * This isn't useful, since the buffer isn't
125*646b68f0SPietro Cerutti 			 * allowed to grow.
126*646b68f0SPietro Cerutti 			 */
127*646b68f0SPietro Cerutti 			ck->off = ck->len = size;
128*646b68f0SPietro Cerutti 		} else
129*646b68f0SPietro Cerutti 			ck->off = ck->len = strnlen(ck->buf, ck->size);
130*646b68f0SPietro Cerutti 		break;
131*646b68f0SPietro Cerutti 	case 'r':
132*646b68f0SPietro Cerutti 		ck->len = size;
133*646b68f0SPietro Cerutti 		break;
134*646b68f0SPietro Cerutti 	case 'w':
135*646b68f0SPietro Cerutti 		ck->len = 0;
136*646b68f0SPietro Cerutti 		break;
137*646b68f0SPietro Cerutti 	}
138*646b68f0SPietro Cerutti 
139*646b68f0SPietro Cerutti 	/* Actuall wrapper. */
140*646b68f0SPietro Cerutti 	f = funopen ((void *)ck,
141*646b68f0SPietro Cerutti 	    flags & O_WRONLY ? NULL : fmemopen_read,
142*646b68f0SPietro Cerutti 	    flags & O_RDONLY ? NULL : fmemopen_write,
14396c95412SPietro Cerutti 	    fmemopen_seek, fmemopen_close);
14496c95412SPietro Cerutti 
14596c95412SPietro Cerutti 	if (f == NULL) {
14696c95412SPietro Cerutti 		if (ck->own)
14796c95412SPietro Cerutti 			free (ck->buf);
14896c95412SPietro Cerutti 		free (ck);
14996c95412SPietro Cerutti 		return (NULL);
15096c95412SPietro Cerutti 	}
15196c95412SPietro Cerutti 
152*646b68f0SPietro Cerutti 	/*
153*646b68f0SPietro Cerutti 	 * Turn off buffering, so a write past the end of the buffer
154*646b68f0SPietro Cerutti 	 * correctly returns a short object count.
155*646b68f0SPietro Cerutti 	 */
15696c95412SPietro Cerutti 	setvbuf (f, (char *) NULL, _IONBF, 0);
15796c95412SPietro Cerutti 
15896c95412SPietro Cerutti 	return (f);
15996c95412SPietro Cerutti }
16096c95412SPietro Cerutti 
16196c95412SPietro Cerutti static int
16296c95412SPietro Cerutti fmemopen_read (void *cookie, char *buf, int nbytes)
16396c95412SPietro Cerutti {
164*646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
16596c95412SPietro Cerutti 
16696c95412SPietro Cerutti 	if (nbytes > ck->len - ck->off)
16796c95412SPietro Cerutti 		nbytes = ck->len - ck->off;
16896c95412SPietro Cerutti 
16996c95412SPietro Cerutti 	if (nbytes == 0)
17096c95412SPietro Cerutti 		return (0);
17196c95412SPietro Cerutti 
17296c95412SPietro Cerutti 	memcpy (buf, ck->buf + ck->off, nbytes);
17396c95412SPietro Cerutti 
17496c95412SPietro Cerutti 	ck->off += nbytes;
17596c95412SPietro Cerutti 
17696c95412SPietro Cerutti 	return (nbytes);
17796c95412SPietro Cerutti }
17896c95412SPietro Cerutti 
17996c95412SPietro Cerutti static int
18096c95412SPietro Cerutti fmemopen_write (void *cookie, const char *buf, int nbytes)
18196c95412SPietro Cerutti {
182*646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
18396c95412SPietro Cerutti 
184*646b68f0SPietro Cerutti 	if (nbytes > ck->size - ck->off)
185*646b68f0SPietro Cerutti 		nbytes = ck->size - ck->off;
18696c95412SPietro Cerutti 
18796c95412SPietro Cerutti 	if (nbytes == 0)
18896c95412SPietro Cerutti 		return (0);
18996c95412SPietro Cerutti 
19096c95412SPietro Cerutti 	memcpy (ck->buf + ck->off, buf, nbytes);
19196c95412SPietro Cerutti 
19296c95412SPietro Cerutti 	ck->off += nbytes;
19396c95412SPietro Cerutti 
194*646b68f0SPietro Cerutti 	if (ck->off > ck->len)
195*646b68f0SPietro Cerutti 		ck->len = ck->off;
196*646b68f0SPietro Cerutti 
197*646b68f0SPietro Cerutti 	/*
198*646b68f0SPietro Cerutti 	 * We append a NULL byte if all these conditions are met:
199*646b68f0SPietro Cerutti 	 * - the buffer is not binary
200*646b68f0SPietro Cerutti 	 * - the buffer is not full
201*646b68f0SPietro Cerutti 	 * - the data just written doesn't already end with a NULL byte
202*646b68f0SPietro Cerutti 	 */
203*646b68f0SPietro Cerutti 	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
20496c95412SPietro Cerutti 		ck->buf[ck->off] = '\0';
20596c95412SPietro Cerutti 
20696c95412SPietro Cerutti 	return (nbytes);
20796c95412SPietro Cerutti }
20896c95412SPietro Cerutti 
20996c95412SPietro Cerutti static fpos_t
21096c95412SPietro Cerutti fmemopen_seek (void *cookie, fpos_t offset, int whence)
21196c95412SPietro Cerutti {
212*646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
21396c95412SPietro Cerutti 
21496c95412SPietro Cerutti 
21596c95412SPietro Cerutti 	switch (whence) {
21696c95412SPietro Cerutti 	case SEEK_SET:
217*646b68f0SPietro Cerutti 		if (offset > ck->size) {
21896c95412SPietro Cerutti 			errno = EINVAL;
21996c95412SPietro Cerutti 			return (-1);
22096c95412SPietro Cerutti 		}
22196c95412SPietro Cerutti 		ck->off = offset;
22296c95412SPietro Cerutti 		break;
22396c95412SPietro Cerutti 
22496c95412SPietro Cerutti 	case SEEK_CUR:
225*646b68f0SPietro Cerutti 		if (ck->off + offset > ck->size) {
22696c95412SPietro Cerutti 			errno = EINVAL;
22796c95412SPietro Cerutti 			return (-1);
22896c95412SPietro Cerutti 		}
22996c95412SPietro Cerutti 		ck->off += offset;
23096c95412SPietro Cerutti 		break;
23196c95412SPietro Cerutti 
23296c95412SPietro Cerutti 	case SEEK_END:
23396c95412SPietro Cerutti 		if (offset > 0 || -offset > ck->len) {
23496c95412SPietro Cerutti 			errno = EINVAL;
23596c95412SPietro Cerutti 			return (-1);
23696c95412SPietro Cerutti 		}
23796c95412SPietro Cerutti 		ck->off = ck->len + offset;
23896c95412SPietro Cerutti 		break;
23996c95412SPietro Cerutti 
24096c95412SPietro Cerutti 	default:
24196c95412SPietro Cerutti 		errno = EINVAL;
24296c95412SPietro Cerutti 		return (-1);
24396c95412SPietro Cerutti 	}
24496c95412SPietro Cerutti 
24596c95412SPietro Cerutti 	return (ck->off);
24696c95412SPietro Cerutti }
24796c95412SPietro Cerutti 
24896c95412SPietro Cerutti static int
24996c95412SPietro Cerutti fmemopen_close (void *cookie)
25096c95412SPietro Cerutti {
251*646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
25296c95412SPietro Cerutti 
25396c95412SPietro Cerutti 	if (ck->own)
25496c95412SPietro Cerutti 		free (ck->buf);
25596c95412SPietro Cerutti 
25696c95412SPietro Cerutti 	free (ck);
25796c95412SPietro Cerutti 
25896c95412SPietro Cerutti 	return (0);
25996c95412SPietro Cerutti }
260