xref: /freebsd/lib/libc/stdio/fmemopen.c (revision f6d1992dc3c6adabe3e6bfc3020b06202c85c0a0)
196c95412SPietro Cerutti /*-
271796d33SPietro Cerutti  * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
371796d33SPietro Cerutti  *
471796d33SPietro Cerutti  * Redistribution and use in source and binary forms, with or without
571796d33SPietro Cerutti  * modification, are permitted provided that the following conditions
671796d33SPietro Cerutti  * are met:
771796d33SPietro Cerutti  * 1. Redistributions of source code must retain the above copyright
871796d33SPietro Cerutti  *    notice, this list of conditions and the following disclaimer.
971796d33SPietro Cerutti  * 2. Redistributions in binary form must reproduce the above copyright
1071796d33SPietro Cerutti  *    notice, this list of conditions and the following disclaimer in the
1171796d33SPietro Cerutti  *    documentation and/or other materials provided with the distribution.
1271796d33SPietro Cerutti  *
1371796d33SPietro Cerutti  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1471796d33SPietro Cerutti  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1571796d33SPietro Cerutti  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1671796d33SPietro Cerutti  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
1771796d33SPietro Cerutti  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1871796d33SPietro Cerutti  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1971796d33SPietro Cerutti  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2071796d33SPietro Cerutti  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2171796d33SPietro Cerutti  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2271796d33SPietro Cerutti  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2371796d33SPietro Cerutti  * SUCH DAMAGE.
2496c95412SPietro Cerutti  */
2596c95412SPietro Cerutti 
2696c95412SPietro Cerutti #include <sys/cdefs.h>
2796c95412SPietro Cerutti __FBSDID("$FreeBSD$");
2896c95412SPietro Cerutti 
29646b68f0SPietro Cerutti #include <fcntl.h>
3071796d33SPietro Cerutti #include <stdbool.h>
3196c95412SPietro Cerutti #include <stdio.h>
3296c95412SPietro Cerutti #include <stdlib.h>
3396c95412SPietro Cerutti #include <string.h>
3496c95412SPietro Cerutti #include <errno.h>
35646b68f0SPietro Cerutti #include "local.h"
3696c95412SPietro Cerutti 
37646b68f0SPietro Cerutti struct fmemopen_cookie
3896c95412SPietro Cerutti {
3996c95412SPietro Cerutti 	char	*buf;	/* pointer to the memory region */
4071796d33SPietro Cerutti 	bool	 own;	/* did we allocate the buffer ourselves? */
41646b68f0SPietro Cerutti 	char     bin;   /* is this a binary buffer? */
42646b68f0SPietro Cerutti 	size_t	 size;	/* buffer length in bytes */
43646b68f0SPietro Cerutti 	size_t	 len;	/* data length in bytes */
44646b68f0SPietro Cerutti 	size_t	 off;	/* current offset into the buffer */
4596c95412SPietro Cerutti };
4696c95412SPietro Cerutti 
4796c95412SPietro Cerutti static int	fmemopen_read(void *cookie, char *buf, int nbytes);
4896c95412SPietro Cerutti static int	fmemopen_write(void *cookie, const char *buf, int nbytes);
4996c95412SPietro Cerutti static fpos_t	fmemopen_seek(void *cookie, fpos_t offset, int whence);
5096c95412SPietro Cerutti static int	fmemopen_close(void *cookie);
5196c95412SPietro Cerutti 
5296c95412SPietro Cerutti FILE *
5396c95412SPietro Cerutti fmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
5496c95412SPietro Cerutti {
55646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck;
56646b68f0SPietro Cerutti 	FILE *f;
57646b68f0SPietro Cerutti 	int flags, rc;
58646b68f0SPietro Cerutti 
59646b68f0SPietro Cerutti 	/*
604c524a42SPietro Cerutti 	 * POSIX says we shall return EINVAL if size is 0.
614c524a42SPietro Cerutti 	 */
624c524a42SPietro Cerutti 	if (size == 0) {
634c524a42SPietro Cerutti 		errno = EINVAL;
644c524a42SPietro Cerutti 		return (NULL);
654c524a42SPietro Cerutti 	}
664c524a42SPietro Cerutti 
674c524a42SPietro Cerutti 	/*
68646b68f0SPietro Cerutti 	 * Retrieve the flags as used by open(2) from the mode argument, and
69646b68f0SPietro Cerutti 	 * validate them.
7071796d33SPietro Cerutti 	 */
71646b68f0SPietro Cerutti 	rc = __sflags(mode, &flags);
72646b68f0SPietro Cerutti 	if (rc == 0) {
73646b68f0SPietro Cerutti 		errno = EINVAL;
74646b68f0SPietro Cerutti 		return (NULL);
75646b68f0SPietro Cerutti 	}
76646b68f0SPietro Cerutti 
77646b68f0SPietro Cerutti 	/*
78646b68f0SPietro Cerutti 	 * There's no point in requiring an automatically allocated buffer
79646b68f0SPietro Cerutti 	 * in write-only mode.
80646b68f0SPietro Cerutti 	 */
81646b68f0SPietro Cerutti 	if (!(flags & O_RDWR) && buf == NULL) {
82646b68f0SPietro Cerutti 		errno = EINVAL;
83646b68f0SPietro Cerutti 		return (NULL);
84646b68f0SPietro Cerutti 	}
85646b68f0SPietro Cerutti 
86646b68f0SPietro Cerutti 	ck = malloc(sizeof(struct fmemopen_cookie));
8796c95412SPietro Cerutti 	if (ck == NULL) {
8896c95412SPietro Cerutti 		return (NULL);
8996c95412SPietro Cerutti 	}
9096c95412SPietro Cerutti 
9196c95412SPietro Cerutti 	ck->off  = 0;
92646b68f0SPietro Cerutti 	ck->size = size;
9396c95412SPietro Cerutti 
94646b68f0SPietro Cerutti 	/* Check whether we have to allocate the buffer ourselves. */
9596c95412SPietro Cerutti 	ck->own = ((ck->buf = buf) == NULL);
9696c95412SPietro Cerutti 	if (ck->own) {
9796c95412SPietro Cerutti 		ck->buf = malloc(size);
9896c95412SPietro Cerutti 		if (ck->buf == NULL) {
9996c95412SPietro Cerutti 			free(ck);
10096c95412SPietro Cerutti 			return (NULL);
10196c95412SPietro Cerutti 		}
102646b68f0SPietro Cerutti 	}
103646b68f0SPietro Cerutti 
104646b68f0SPietro Cerutti 	/*
105646b68f0SPietro Cerutti 	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
106646b68f0SPietro Cerutti 	 * truncate the buffer.
107646b68f0SPietro Cerutti 	 */
108646b68f0SPietro Cerutti 	if (ck->own || mode[0] == 'w') {
10996c95412SPietro Cerutti 		ck->buf[0] = '\0';
11096c95412SPietro Cerutti 	}
11196c95412SPietro Cerutti 
112646b68f0SPietro Cerutti 	/* Check for binary mode. */
113646b68f0SPietro Cerutti 	ck->bin = strchr(mode, 'b') != NULL;
11496c95412SPietro Cerutti 
115646b68f0SPietro Cerutti 	/*
116646b68f0SPietro Cerutti 	 * The size of the current buffer contents is set depending on the
117646b68f0SPietro Cerutti 	 * mode:
118646b68f0SPietro Cerutti 	 *
119646b68f0SPietro Cerutti 	 * for append (text-mode), the position of the first NULL byte, or the
120646b68f0SPietro Cerutti 	 * size of the buffer if none is found
121646b68f0SPietro Cerutti 	 *
122646b68f0SPietro Cerutti 	 * for append (binary-mode), the size of the buffer
123646b68f0SPietro Cerutti 	 *
124646b68f0SPietro Cerutti 	 * for read, the size of the buffer
125646b68f0SPietro Cerutti 	 *
126646b68f0SPietro Cerutti 	 * for write, 0
127646b68f0SPietro Cerutti 	 */
128646b68f0SPietro Cerutti 	switch (mode[0]) {
129646b68f0SPietro Cerutti 	case 'a':
130646b68f0SPietro Cerutti 		ck->off = ck->len = strnlen(ck->buf, ck->size);
131646b68f0SPietro Cerutti 		break;
132646b68f0SPietro Cerutti 	case 'r':
133646b68f0SPietro Cerutti 		ck->len = size;
134646b68f0SPietro Cerutti 		break;
135646b68f0SPietro Cerutti 	case 'w':
136646b68f0SPietro Cerutti 		ck->len = 0;
137646b68f0SPietro Cerutti 		break;
138646b68f0SPietro Cerutti 	}
139646b68f0SPietro Cerutti 
14071796d33SPietro Cerutti 	f = funopen(ck,
141646b68f0SPietro Cerutti 	    flags & O_WRONLY ? NULL : fmemopen_read,
142646b68f0SPietro 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*f6d1992dSAndrey A. Chernov 	if (mode[0] == 'a')
153*f6d1992dSAndrey A. Chernov 		f->_flags |= __SAPP;
154*f6d1992dSAndrey A. Chernov 
155646b68f0SPietro Cerutti 	/*
156646b68f0SPietro Cerutti 	 * Turn off buffering, so a write past the end of the buffer
157646b68f0SPietro Cerutti 	 * correctly returns a short object count.
158646b68f0SPietro Cerutti 	 */
15971796d33SPietro Cerutti 	setvbuf(f, NULL, _IONBF, 0);
16096c95412SPietro Cerutti 
16196c95412SPietro Cerutti 	return (f);
16296c95412SPietro Cerutti }
16396c95412SPietro Cerutti 
16496c95412SPietro Cerutti static int
16596c95412SPietro Cerutti fmemopen_read(void *cookie, char *buf, int nbytes)
16696c95412SPietro Cerutti {
167646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
16896c95412SPietro Cerutti 
16996c95412SPietro Cerutti 	if (nbytes > ck->len - ck->off)
17096c95412SPietro Cerutti 		nbytes = ck->len - ck->off;
17196c95412SPietro Cerutti 
17296c95412SPietro Cerutti 	if (nbytes == 0)
17396c95412SPietro Cerutti 		return (0);
17496c95412SPietro Cerutti 
17596c95412SPietro Cerutti 	memcpy(buf, ck->buf + ck->off, nbytes);
17696c95412SPietro Cerutti 
17796c95412SPietro Cerutti 	ck->off += nbytes;
17896c95412SPietro Cerutti 
17996c95412SPietro Cerutti 	return (nbytes);
18096c95412SPietro Cerutti }
18196c95412SPietro Cerutti 
18296c95412SPietro Cerutti static int
18396c95412SPietro Cerutti fmemopen_write(void *cookie, const char *buf, int nbytes)
18496c95412SPietro Cerutti {
185646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
18696c95412SPietro Cerutti 
187646b68f0SPietro Cerutti 	if (nbytes > ck->size - ck->off)
188646b68f0SPietro Cerutti 		nbytes = ck->size - ck->off;
18996c95412SPietro Cerutti 
19096c95412SPietro Cerutti 	if (nbytes == 0)
19196c95412SPietro Cerutti 		return (0);
19296c95412SPietro Cerutti 
19396c95412SPietro Cerutti 	memcpy(ck->buf + ck->off, buf, nbytes);
19496c95412SPietro Cerutti 
19596c95412SPietro Cerutti 	ck->off += nbytes;
19696c95412SPietro Cerutti 
197646b68f0SPietro Cerutti 	if (ck->off > ck->len)
198646b68f0SPietro Cerutti 		ck->len = ck->off;
199646b68f0SPietro Cerutti 
200646b68f0SPietro Cerutti 	/*
201646b68f0SPietro Cerutti 	 * We append a NULL byte if all these conditions are met:
202646b68f0SPietro Cerutti 	 * - the buffer is not binary
203646b68f0SPietro Cerutti 	 * - the buffer is not full
204646b68f0SPietro Cerutti 	 * - the data just written doesn't already end with a NULL byte
205646b68f0SPietro Cerutti 	 */
206646b68f0SPietro Cerutti 	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
20796c95412SPietro Cerutti 		ck->buf[ck->off] = '\0';
20896c95412SPietro Cerutti 
20996c95412SPietro Cerutti 	return (nbytes);
21096c95412SPietro Cerutti }
21196c95412SPietro Cerutti 
21296c95412SPietro Cerutti static fpos_t
21396c95412SPietro Cerutti fmemopen_seek(void *cookie, fpos_t offset, int whence)
21496c95412SPietro Cerutti {
215646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
21696c95412SPietro Cerutti 
21796c95412SPietro Cerutti 
21896c95412SPietro Cerutti 	switch (whence) {
21996c95412SPietro Cerutti 	case SEEK_SET:
220646b68f0SPietro Cerutti 		if (offset > ck->size) {
22196c95412SPietro Cerutti 			errno = EINVAL;
22296c95412SPietro Cerutti 			return (-1);
22396c95412SPietro Cerutti 		}
22496c95412SPietro Cerutti 		ck->off = offset;
22596c95412SPietro Cerutti 		break;
22696c95412SPietro Cerutti 
22796c95412SPietro Cerutti 	case SEEK_CUR:
228646b68f0SPietro Cerutti 		if (ck->off + offset > ck->size) {
22996c95412SPietro Cerutti 			errno = EINVAL;
23096c95412SPietro Cerutti 			return (-1);
23196c95412SPietro Cerutti 		}
23296c95412SPietro Cerutti 		ck->off += offset;
23396c95412SPietro Cerutti 		break;
23496c95412SPietro Cerutti 
23596c95412SPietro Cerutti 	case SEEK_END:
23696c95412SPietro Cerutti 		if (offset > 0 || -offset > ck->len) {
23796c95412SPietro Cerutti 			errno = EINVAL;
23896c95412SPietro Cerutti 			return (-1);
23996c95412SPietro Cerutti 		}
24096c95412SPietro Cerutti 		ck->off = ck->len + offset;
24196c95412SPietro Cerutti 		break;
24296c95412SPietro Cerutti 
24396c95412SPietro Cerutti 	default:
24496c95412SPietro Cerutti 		errno = EINVAL;
24596c95412SPietro Cerutti 		return (-1);
24696c95412SPietro Cerutti 	}
24796c95412SPietro Cerutti 
24896c95412SPietro Cerutti 	return (ck->off);
24996c95412SPietro Cerutti }
25096c95412SPietro Cerutti 
25196c95412SPietro Cerutti static int
25296c95412SPietro Cerutti fmemopen_close(void *cookie)
25396c95412SPietro Cerutti {
254646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
25596c95412SPietro Cerutti 
25696c95412SPietro Cerutti 	if (ck->own)
25796c95412SPietro Cerutti 		free(ck->buf);
25896c95412SPietro Cerutti 
25996c95412SPietro Cerutti 	free(ck);
26096c95412SPietro Cerutti 
26196c95412SPietro Cerutti 	return (0);
26296c95412SPietro Cerutti }
263