xref: /netbsd-src/lib/libc/stdio/open_memstream.c (revision 89f106fbc2bd696766675c676fb46391b59a2da3)
1*89f106fbSchristos /*	$NetBSD: open_memstream.c,v 1.2 2024/01/23 15:32:54 christos Exp $	*/
2dfe69df4Schristos 
3dfe69df4Schristos /*-
4dfe69df4Schristos  * Copyright (c) 2013 Advanced Computing Technologies LLC
5dfe69df4Schristos  * Written by: John H. Baldwin <jhb@FreeBSD.org>
6dfe69df4Schristos  * All rights reserved.
7dfe69df4Schristos  *
8dfe69df4Schristos  * Redistribution and use in source and binary forms, with or without
9dfe69df4Schristos  * modification, are permitted provided that the following conditions
10dfe69df4Schristos  * are met:
11dfe69df4Schristos  * 1. Redistributions of source code must retain the above copyright
12dfe69df4Schristos  *    notice, this list of conditions and the following disclaimer.
13dfe69df4Schristos  * 2. Redistributions in binary form must reproduce the above copyright
14dfe69df4Schristos  *    notice, this list of conditions and the following disclaimer in the
15dfe69df4Schristos  *    documentation and/or other materials provided with the distribution.
16dfe69df4Schristos  *
17dfe69df4Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18dfe69df4Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19dfe69df4Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20dfe69df4Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21dfe69df4Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22dfe69df4Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23dfe69df4Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24dfe69df4Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25dfe69df4Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26dfe69df4Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27dfe69df4Schristos  * SUCH DAMAGE.
28dfe69df4Schristos  */
29dfe69df4Schristos 
30dfe69df4Schristos #include <sys/cdefs.h>
31dfe69df4Schristos #if 0
32dfe69df4Schristos __FBSDID("$FreeBSD: head/lib/libc/stdio/open_memstream.c 247411 2013-02-27 19:50:46Z jhb $");
33dfe69df4Schristos #endif
34*89f106fbSchristos __RCSID("$NetBSD: open_memstream.c,v 1.2 2024/01/23 15:32:54 christos Exp $");
35dfe69df4Schristos 
36dfe69df4Schristos #include "namespace.h"
37dfe69df4Schristos #include <assert.h>
38dfe69df4Schristos #include <errno.h>
39dfe69df4Schristos #include <limits.h>
40dfe69df4Schristos #include <stdio.h>
41dfe69df4Schristos #include <stdlib.h>
42dfe69df4Schristos #include <string.h>
43dfe69df4Schristos #include <wchar.h>
44dfe69df4Schristos 
45dfe69df4Schristos #define	OFF_MAX	LLONG_MAX
46dfe69df4Schristos 
47dfe69df4Schristos struct memstream {
48dfe69df4Schristos 	char **bufp;
49dfe69df4Schristos 	size_t *sizep;
50dfe69df4Schristos 	size_t len;
51dfe69df4Schristos 	size_t offset;
52dfe69df4Schristos };
53dfe69df4Schristos 
54*89f106fbSchristos static __inline size_t
off_t_to_size_t(off_t off)55*89f106fbSchristos off_t_to_size_t(off_t off)
56*89f106fbSchristos {
57*89f106fbSchristos 	if (off < 0 || off >= SSIZE_MAX)
58*89f106fbSchristos 		return SSIZE_MAX - 1;
59*89f106fbSchristos 	return (size_t)off;
60*89f106fbSchristos }
61*89f106fbSchristos 
62dfe69df4Schristos static int
memstream_grow(struct memstream * ms,off_t newoff)63dfe69df4Schristos memstream_grow(struct memstream *ms, off_t newoff)
64dfe69df4Schristos {
65dfe69df4Schristos 	char *buf;
66dfe69df4Schristos 	size_t newsize;
67dfe69df4Schristos 
68*89f106fbSchristos 	newsize = off_t_to_size_t(newoff);
69dfe69df4Schristos 	if (newsize > ms->len) {
70dfe69df4Schristos 		buf = realloc(*ms->bufp, newsize + 1);
71dfe69df4Schristos 		if (buf != NULL) {
72dfe69df4Schristos #ifdef DEBUG
73dfe69df4Schristos 			fprintf(stderr, "MS: %p growing from %zd to %zd\n",
74dfe69df4Schristos 			    ms, ms->len, newsize);
75dfe69df4Schristos #endif
76dfe69df4Schristos 			memset(buf + ms->len + 1, 0, newsize - ms->len);
77dfe69df4Schristos 			*ms->bufp = buf;
78dfe69df4Schristos 			ms->len = newsize;
79dfe69df4Schristos 			return (1);
80dfe69df4Schristos 		}
81dfe69df4Schristos 		return (0);
82dfe69df4Schristos 	}
83dfe69df4Schristos 	return (1);
84dfe69df4Schristos }
85dfe69df4Schristos 
86dfe69df4Schristos static void
memstream_update(struct memstream * ms)87dfe69df4Schristos memstream_update(struct memstream *ms)
88dfe69df4Schristos {
89dfe69df4Schristos 
90dfe69df4Schristos 	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
91dfe69df4Schristos }
92dfe69df4Schristos 
93dfe69df4Schristos static ssize_t
memstream_write(void * cookie,const void * buf,size_t len)94dfe69df4Schristos memstream_write(void *cookie, const void *buf, size_t len)
95dfe69df4Schristos {
96dfe69df4Schristos 	struct memstream *ms;
97dfe69df4Schristos 	size_t tocopy;
98dfe69df4Schristos 	off_t more;
99dfe69df4Schristos 
100dfe69df4Schristos 	ms = cookie;
101dfe69df4Schristos 	more = ms->offset;
102dfe69df4Schristos 	more += len;
103dfe69df4Schristos 	if (!memstream_grow(ms, more))
104dfe69df4Schristos 		return (-1);
105dfe69df4Schristos 	tocopy = ms->len - ms->offset;
106dfe69df4Schristos 	if (len < tocopy)
107dfe69df4Schristos 		tocopy = len;
108dfe69df4Schristos 	memcpy(*ms->bufp + ms->offset, buf, tocopy);
109dfe69df4Schristos 	ms->offset += tocopy;
110dfe69df4Schristos 	memstream_update(ms);
111dfe69df4Schristos #ifdef DEBUG
112dfe69df4Schristos 	fprintf(stderr, "MS: write(%p, %zu) = %zu\n", ms, len, tocopy);
113dfe69df4Schristos #endif
114dfe69df4Schristos 	return (ssize_t)tocopy;
115dfe69df4Schristos }
116dfe69df4Schristos 
117dfe69df4Schristos static off_t
memstream_seek(void * cookie,off_t pos,int whence)118dfe69df4Schristos memstream_seek(void *cookie, off_t pos, int whence)
119dfe69df4Schristos {
120dfe69df4Schristos 	struct memstream *ms;
121dfe69df4Schristos #ifdef DEBUG
122dfe69df4Schristos 	size_t old;
123dfe69df4Schristos #endif
124dfe69df4Schristos 
125dfe69df4Schristos 	ms = cookie;
126dfe69df4Schristos #ifdef DEBUG
127dfe69df4Schristos 	old = ms->offset;
128dfe69df4Schristos #endif
129dfe69df4Schristos 	switch (whence) {
130dfe69df4Schristos 	case SEEK_SET:
131dfe69df4Schristos 		/* _fseeko() checks for negative offsets. */
132dfe69df4Schristos 		assert(pos >= 0);
133*89f106fbSchristos 		ms->offset = off_t_to_size_t(pos);
134dfe69df4Schristos 		break;
135dfe69df4Schristos 	case SEEK_CUR:
136dfe69df4Schristos 		/* This is only called by _ftello(). */
137dfe69df4Schristos 		assert(pos == 0);
138dfe69df4Schristos 		break;
139dfe69df4Schristos 	case SEEK_END:
140dfe69df4Schristos 		if (pos < 0) {
141dfe69df4Schristos 			if (pos + (ssize_t)ms->len < 0) {
142dfe69df4Schristos #ifdef DEBUG
143dfe69df4Schristos 				fprintf(stderr,
144dfe69df4Schristos 				    "MS: bad SEEK_END: pos %jd, len %zu\n",
145dfe69df4Schristos 				    (intmax_t)pos, ms->len);
146dfe69df4Schristos #endif
147dfe69df4Schristos 				errno = EINVAL;
148dfe69df4Schristos 				return (-1);
149dfe69df4Schristos 			}
150dfe69df4Schristos 		} else {
151dfe69df4Schristos 			if (OFF_MAX - (ssize_t)ms->len < pos) {
152dfe69df4Schristos #ifdef DEBUG
153dfe69df4Schristos 				fprintf(stderr,
154dfe69df4Schristos 				    "MS: bad SEEK_END: pos %jd, len %zu\n",
155dfe69df4Schristos 				    (intmax_t)pos, ms->len);
156dfe69df4Schristos #endif
157dfe69df4Schristos 				errno = EOVERFLOW;
158dfe69df4Schristos 				return (-1);
159dfe69df4Schristos 			}
160dfe69df4Schristos 		}
161*89f106fbSchristos 		ms->offset = off_t_to_size_t(ms->len + pos);
162dfe69df4Schristos 		break;
163dfe69df4Schristos 	}
164dfe69df4Schristos 	memstream_update(ms);
165dfe69df4Schristos #ifdef DEBUG
166dfe69df4Schristos 	fprintf(stderr, "MS: seek(%p, %jd, %d) %zu -> %zu\n", ms,
167dfe69df4Schristos 	    (intmax_t)pos, whence, old, ms->offset);
168dfe69df4Schristos #endif
169dfe69df4Schristos 	return (ms->offset);
170dfe69df4Schristos }
171dfe69df4Schristos 
172dfe69df4Schristos static int
memstream_close(void * cookie)173dfe69df4Schristos memstream_close(void *cookie)
174dfe69df4Schristos {
175dfe69df4Schristos 
176dfe69df4Schristos 	free(cookie);
177dfe69df4Schristos 	return (0);
178dfe69df4Schristos }
179dfe69df4Schristos 
180dfe69df4Schristos FILE *
open_memstream(char ** bufp,size_t * sizep)181dfe69df4Schristos open_memstream(char **bufp, size_t *sizep)
182dfe69df4Schristos {
183dfe69df4Schristos 	struct memstream *ms;
184dfe69df4Schristos 	int save_errno;
185dfe69df4Schristos 	FILE *fp;
186dfe69df4Schristos 
187dfe69df4Schristos 	if (bufp == NULL || sizep == NULL) {
188dfe69df4Schristos 		errno = EINVAL;
189dfe69df4Schristos 		return (NULL);
190dfe69df4Schristos 	}
191dfe69df4Schristos 	*bufp = calloc(1, 1);
192dfe69df4Schristos 	if (*bufp == NULL)
193dfe69df4Schristos 		return (NULL);
194dfe69df4Schristos 	ms = malloc(sizeof(*ms));
195dfe69df4Schristos 	if (ms == NULL) {
196dfe69df4Schristos 		save_errno = errno;
197dfe69df4Schristos 		free(*bufp);
198dfe69df4Schristos 		*bufp = NULL;
199dfe69df4Schristos 		errno = save_errno;
200dfe69df4Schristos 		return (NULL);
201dfe69df4Schristos 	}
202dfe69df4Schristos 	ms->bufp = bufp;
203dfe69df4Schristos 	ms->sizep = sizep;
204dfe69df4Schristos 	ms->len = 0;
205dfe69df4Schristos 	ms->offset = 0;
206dfe69df4Schristos 	memstream_update(ms);
207dfe69df4Schristos 	fp = funopen2(ms, NULL, memstream_write, memstream_seek,
208dfe69df4Schristos 	    NULL, memstream_close);
209dfe69df4Schristos 	if (fp == NULL) {
210dfe69df4Schristos 		save_errno = errno;
211dfe69df4Schristos 		free(ms);
212dfe69df4Schristos 		free(*bufp);
213dfe69df4Schristos 		*bufp = NULL;
214dfe69df4Schristos 		errno = save_errno;
215dfe69df4Schristos 		return (NULL);
216dfe69df4Schristos 	}
217dfe69df4Schristos 	fwide(fp, -1);
218dfe69df4Schristos 	return (fp);
219dfe69df4Schristos }
220