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