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