xref: /freebsd/lib/libc/stdio/fmemopen.c (revision 0953460ce149e6f384aafbcb1e6213dfbf8f6a16)
196c95412SPietro Cerutti /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3d915a14eSPedro F. Giffuni  *
471796d33SPietro Cerutti  * Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
571796d33SPietro Cerutti  *
671796d33SPietro Cerutti  * Redistribution and use in source and binary forms, with or without
771796d33SPietro Cerutti  * modification, are permitted provided that the following conditions
871796d33SPietro Cerutti  * are met:
971796d33SPietro Cerutti  * 1. Redistributions of source code must retain the above copyright
1071796d33SPietro Cerutti  *    notice, this list of conditions and the following disclaimer.
1171796d33SPietro Cerutti  * 2. Redistributions in binary form must reproduce the above copyright
1271796d33SPietro Cerutti  *    notice, this list of conditions and the following disclaimer in the
1371796d33SPietro Cerutti  *    documentation and/or other materials provided with the distribution.
1471796d33SPietro Cerutti  *
1571796d33SPietro Cerutti  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1671796d33SPietro Cerutti  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1771796d33SPietro Cerutti  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1871796d33SPietro Cerutti  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
1971796d33SPietro Cerutti  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2071796d33SPietro Cerutti  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2171796d33SPietro Cerutti  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2271796d33SPietro Cerutti  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2371796d33SPietro Cerutti  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2471796d33SPietro Cerutti  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2571796d33SPietro Cerutti  * SUCH DAMAGE.
2696c95412SPietro Cerutti  */
2796c95412SPietro Cerutti 
28646b68f0SPietro Cerutti #include <fcntl.h>
2971796d33SPietro Cerutti #include <stdbool.h>
3096c95412SPietro Cerutti #include <stdio.h>
3196c95412SPietro Cerutti #include <stdlib.h>
3296c95412SPietro Cerutti #include <string.h>
3396c95412SPietro Cerutti #include <errno.h>
34646b68f0SPietro Cerutti #include "local.h"
3596c95412SPietro Cerutti 
36646b68f0SPietro Cerutti struct fmemopen_cookie
3796c95412SPietro Cerutti {
3896c95412SPietro Cerutti 	char	*buf;	/* pointer to the memory region */
3971796d33SPietro Cerutti 	bool	 own;	/* did we allocate the buffer ourselves? */
40646b68f0SPietro Cerutti 	char     bin;   /* is this a binary buffer? */
41646b68f0SPietro Cerutti 	size_t	 size;	/* buffer length in bytes */
42646b68f0SPietro Cerutti 	size_t	 len;	/* data length in bytes */
43646b68f0SPietro 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 *
fmemopen(void * __restrict buf,size_t size,const char * __restrict mode)5296c95412SPietro Cerutti fmemopen(void * __restrict buf, size_t size, const char * __restrict mode)
5396c95412SPietro Cerutti {
54646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck;
55646b68f0SPietro Cerutti 	FILE *f;
56646b68f0SPietro Cerutti 	int flags, rc;
57646b68f0SPietro Cerutti 
58646b68f0SPietro Cerutti 	/*
594c524a42SPietro Cerutti 	 * POSIX says we shall return EINVAL if size is 0.
604c524a42SPietro Cerutti 	 */
614c524a42SPietro Cerutti 	if (size == 0) {
624c524a42SPietro Cerutti 		errno = EINVAL;
634c524a42SPietro Cerutti 		return (NULL);
644c524a42SPietro Cerutti 	}
654c524a42SPietro Cerutti 
664c524a42SPietro Cerutti 	/*
67646b68f0SPietro Cerutti 	 * Retrieve the flags as used by open(2) from the mode argument, and
68646b68f0SPietro Cerutti 	 * validate them.
6971796d33SPietro Cerutti 	 */
70646b68f0SPietro Cerutti 	rc = __sflags(mode, &flags);
71646b68f0SPietro Cerutti 	if (rc == 0) {
72646b68f0SPietro Cerutti 		errno = EINVAL;
73646b68f0SPietro Cerutti 		return (NULL);
74646b68f0SPietro Cerutti 	}
75646b68f0SPietro Cerutti 
76646b68f0SPietro Cerutti 	/*
77*0953460cSEd Maste 	 * An automatically allocated buffer is only allowed in read-write mode.
78646b68f0SPietro Cerutti 	 */
79*0953460cSEd Maste 	if ((flags & O_ACCMODE) != O_RDWR && buf == NULL) {
80646b68f0SPietro Cerutti 		errno = EINVAL;
81646b68f0SPietro Cerutti 		return (NULL);
82646b68f0SPietro Cerutti 	}
83646b68f0SPietro Cerutti 
84646b68f0SPietro Cerutti 	ck = malloc(sizeof(struct fmemopen_cookie));
8596c95412SPietro Cerutti 	if (ck == NULL) {
8696c95412SPietro Cerutti 		return (NULL);
8796c95412SPietro Cerutti 	}
8896c95412SPietro Cerutti 
8996c95412SPietro Cerutti 	ck->off  = 0;
90646b68f0SPietro Cerutti 	ck->size = size;
9196c95412SPietro Cerutti 
92646b68f0SPietro Cerutti 	/* Check whether we have to allocate the buffer ourselves. */
9396c95412SPietro Cerutti 	ck->own = ((ck->buf = buf) == NULL);
9496c95412SPietro Cerutti 	if (ck->own) {
9596c95412SPietro Cerutti 		ck->buf = malloc(size);
9696c95412SPietro Cerutti 		if (ck->buf == NULL) {
9796c95412SPietro Cerutti 			free(ck);
9896c95412SPietro Cerutti 			return (NULL);
9996c95412SPietro Cerutti 		}
100646b68f0SPietro Cerutti 	}
101646b68f0SPietro Cerutti 
102646b68f0SPietro Cerutti 	/*
103646b68f0SPietro Cerutti 	 * POSIX distinguishes between w+ and r+, in that w+ is supposed to
104646b68f0SPietro Cerutti 	 * truncate the buffer.
105646b68f0SPietro Cerutti 	 */
106646b68f0SPietro Cerutti 	if (ck->own || mode[0] == 'w') {
10796c95412SPietro Cerutti 		ck->buf[0] = '\0';
10896c95412SPietro Cerutti 	}
10996c95412SPietro Cerutti 
110646b68f0SPietro Cerutti 	/* Check for binary mode. */
111646b68f0SPietro Cerutti 	ck->bin = strchr(mode, 'b') != NULL;
11296c95412SPietro Cerutti 
113646b68f0SPietro Cerutti 	/*
114646b68f0SPietro Cerutti 	 * The size of the current buffer contents is set depending on the
115646b68f0SPietro Cerutti 	 * mode:
116646b68f0SPietro Cerutti 	 *
117646b68f0SPietro Cerutti 	 * for append (text-mode), the position of the first NULL byte, or the
118646b68f0SPietro Cerutti 	 * size of the buffer if none is found
119646b68f0SPietro Cerutti 	 *
120646b68f0SPietro Cerutti 	 * for append (binary-mode), the size of the buffer
121646b68f0SPietro Cerutti 	 *
122646b68f0SPietro Cerutti 	 * for read, the size of the buffer
123646b68f0SPietro Cerutti 	 *
124646b68f0SPietro Cerutti 	 * for write, 0
125646b68f0SPietro Cerutti 	 */
126646b68f0SPietro Cerutti 	switch (mode[0]) {
127646b68f0SPietro Cerutti 	case 'a':
128646b68f0SPietro Cerutti 		ck->off = ck->len = strnlen(ck->buf, ck->size);
129646b68f0SPietro Cerutti 		break;
130646b68f0SPietro Cerutti 	case 'r':
131646b68f0SPietro Cerutti 		ck->len = size;
132646b68f0SPietro Cerutti 		break;
133646b68f0SPietro Cerutti 	case 'w':
134646b68f0SPietro Cerutti 		ck->len = 0;
135646b68f0SPietro Cerutti 		break;
136646b68f0SPietro Cerutti 	}
137646b68f0SPietro Cerutti 
138*0953460cSEd Maste 	/* Disable read in O_WRONLY mode, and write in O_RDONLY mode. */
13971796d33SPietro Cerutti 	f = funopen(ck,
140*0953460cSEd Maste 	    (flags & O_ACCMODE) == O_WRONLY ? NULL : fmemopen_read,
141*0953460cSEd Maste 	    (flags & O_ACCMODE) == O_RDONLY ? NULL : fmemopen_write,
14296c95412SPietro Cerutti 	    fmemopen_seek, fmemopen_close);
14396c95412SPietro Cerutti 
14496c95412SPietro Cerutti 	if (f == NULL) {
14596c95412SPietro Cerutti 		if (ck->own)
14696c95412SPietro Cerutti 			free(ck->buf);
14796c95412SPietro Cerutti 		free(ck);
14896c95412SPietro Cerutti 		return (NULL);
14996c95412SPietro Cerutti 	}
15096c95412SPietro Cerutti 
151f6d1992dSAndrey A. Chernov 	if (mode[0] == 'a')
152f6d1992dSAndrey A. Chernov 		f->_flags |= __SAPP;
153f6d1992dSAndrey A. Chernov 
154646b68f0SPietro Cerutti 	/*
155646b68f0SPietro Cerutti 	 * Turn off buffering, so a write past the end of the buffer
156646b68f0SPietro Cerutti 	 * correctly returns a short object count.
157646b68f0SPietro Cerutti 	 */
15871796d33SPietro Cerutti 	setvbuf(f, NULL, _IONBF, 0);
15996c95412SPietro Cerutti 
16096c95412SPietro Cerutti 	return (f);
16196c95412SPietro Cerutti }
16296c95412SPietro Cerutti 
16396c95412SPietro Cerutti static int
fmemopen_read(void * cookie,char * buf,int nbytes)16496c95412SPietro Cerutti fmemopen_read(void *cookie, char *buf, int nbytes)
16596c95412SPietro Cerutti {
166646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
16796c95412SPietro Cerutti 
16896c95412SPietro Cerutti 	if (nbytes > ck->len - ck->off)
16996c95412SPietro Cerutti 		nbytes = ck->len - ck->off;
17096c95412SPietro Cerutti 
17196c95412SPietro Cerutti 	if (nbytes == 0)
17296c95412SPietro Cerutti 		return (0);
17396c95412SPietro Cerutti 
17496c95412SPietro Cerutti 	memcpy(buf, ck->buf + ck->off, nbytes);
17596c95412SPietro Cerutti 
17696c95412SPietro Cerutti 	ck->off += nbytes;
17796c95412SPietro Cerutti 
17896c95412SPietro Cerutti 	return (nbytes);
17996c95412SPietro Cerutti }
18096c95412SPietro Cerutti 
18196c95412SPietro Cerutti static int
fmemopen_write(void * cookie,const char * buf,int nbytes)18296c95412SPietro Cerutti fmemopen_write(void *cookie, const char *buf, int nbytes)
18396c95412SPietro Cerutti {
184646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
18596c95412SPietro Cerutti 
186646b68f0SPietro Cerutti 	if (nbytes > ck->size - ck->off)
187646b68f0SPietro Cerutti 		nbytes = ck->size - ck->off;
18896c95412SPietro Cerutti 
18996c95412SPietro Cerutti 	if (nbytes == 0)
19096c95412SPietro Cerutti 		return (0);
19196c95412SPietro Cerutti 
19296c95412SPietro Cerutti 	memcpy(ck->buf + ck->off, buf, nbytes);
19396c95412SPietro Cerutti 
19496c95412SPietro Cerutti 	ck->off += nbytes;
19596c95412SPietro Cerutti 
196646b68f0SPietro Cerutti 	if (ck->off > ck->len)
197646b68f0SPietro Cerutti 		ck->len = ck->off;
198646b68f0SPietro Cerutti 
199646b68f0SPietro Cerutti 	/*
200646b68f0SPietro Cerutti 	 * We append a NULL byte if all these conditions are met:
201646b68f0SPietro Cerutti 	 * - the buffer is not binary
202646b68f0SPietro Cerutti 	 * - the buffer is not full
203646b68f0SPietro Cerutti 	 * - the data just written doesn't already end with a NULL byte
204646b68f0SPietro Cerutti 	 */
205646b68f0SPietro Cerutti 	if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0')
20696c95412SPietro Cerutti 		ck->buf[ck->off] = '\0';
20796c95412SPietro Cerutti 
20896c95412SPietro Cerutti 	return (nbytes);
20996c95412SPietro Cerutti }
21096c95412SPietro Cerutti 
21196c95412SPietro Cerutti static fpos_t
fmemopen_seek(void * cookie,fpos_t offset,int whence)21296c95412SPietro Cerutti fmemopen_seek(void *cookie, fpos_t offset, int whence)
21396c95412SPietro Cerutti {
214646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
21596c95412SPietro Cerutti 
21696c95412SPietro Cerutti 
21796c95412SPietro Cerutti 	switch (whence) {
21896c95412SPietro Cerutti 	case SEEK_SET:
219646b68f0SPietro Cerutti 		if (offset > ck->size) {
22096c95412SPietro Cerutti 			errno = EINVAL;
22196c95412SPietro Cerutti 			return (-1);
22296c95412SPietro Cerutti 		}
22396c95412SPietro Cerutti 		ck->off = offset;
22496c95412SPietro Cerutti 		break;
22596c95412SPietro Cerutti 
22696c95412SPietro Cerutti 	case SEEK_CUR:
227646b68f0SPietro Cerutti 		if (ck->off + offset > ck->size) {
22896c95412SPietro Cerutti 			errno = EINVAL;
22996c95412SPietro Cerutti 			return (-1);
23096c95412SPietro Cerutti 		}
23196c95412SPietro Cerutti 		ck->off += offset;
23296c95412SPietro Cerutti 		break;
23396c95412SPietro Cerutti 
23496c95412SPietro Cerutti 	case SEEK_END:
23596c95412SPietro Cerutti 		if (offset > 0 || -offset > ck->len) {
23696c95412SPietro Cerutti 			errno = EINVAL;
23796c95412SPietro Cerutti 			return (-1);
23896c95412SPietro Cerutti 		}
23996c95412SPietro Cerutti 		ck->off = ck->len + offset;
24096c95412SPietro Cerutti 		break;
24196c95412SPietro Cerutti 
24296c95412SPietro Cerutti 	default:
24396c95412SPietro Cerutti 		errno = EINVAL;
24496c95412SPietro Cerutti 		return (-1);
24596c95412SPietro Cerutti 	}
24696c95412SPietro Cerutti 
24796c95412SPietro Cerutti 	return (ck->off);
24896c95412SPietro Cerutti }
24996c95412SPietro Cerutti 
25096c95412SPietro Cerutti static int
fmemopen_close(void * cookie)25196c95412SPietro Cerutti fmemopen_close(void *cookie)
25296c95412SPietro Cerutti {
253646b68f0SPietro Cerutti 	struct fmemopen_cookie *ck = cookie;
25496c95412SPietro Cerutti 
25596c95412SPietro Cerutti 	if (ck->own)
25696c95412SPietro Cerutti 		free(ck->buf);
25796c95412SPietro Cerutti 
25896c95412SPietro Cerutti 	free(ck);
25996c95412SPietro Cerutti 
26096c95412SPietro Cerutti 	return (0);
26196c95412SPietro Cerutti }
262