xref: /freebsd/lib/libc/stdio/open_memstream.c (revision 559a218c9b257775fb249b67945fe4a05b7a6b9f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2013 Hudson River Trading 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 "namespace.h"
31 #include <assert.h>
32 #include <errno.h>
33 #include <limits.h>
34 #ifdef DEBUG
35 #include <stdint.h>
36 #endif
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <wchar.h>
41 #include "un-namespace.h"
42 
43 /* XXX: There is no FPOS_MAX.  This assumes fpos_t is an off_t. */
44 #define	FPOS_MAX	OFF_MAX
45 
46 struct memstream {
47 	char **bufp;
48 	size_t *sizep;
49 	ssize_t len;
50 	fpos_t offset;
51 };
52 
53 static int
memstream_grow(struct memstream * ms,fpos_t newoff)54 memstream_grow(struct memstream *ms, fpos_t newoff)
55 {
56 	char *buf;
57 	ssize_t newsize;
58 
59 	if (newoff < 0 || newoff >= SSIZE_MAX)
60 		newsize = SSIZE_MAX - 1;
61 	else
62 		newsize = newoff;
63 	if (newsize > ms->len) {
64 		buf = realloc(*ms->bufp, newsize + 1);
65 		if (buf != NULL) {
66 #ifdef DEBUG
67 			fprintf(stderr, "MS: %p growing from %zd to %zd\n",
68 			    ms, ms->len, newsize);
69 #endif
70 			memset(buf + ms->len + 1, 0, newsize - ms->len);
71 			*ms->bufp = buf;
72 			ms->len = newsize;
73 			return (1);
74 		}
75 		return (0);
76 	}
77 	return (1);
78 }
79 
80 static void
memstream_update(struct memstream * ms)81 memstream_update(struct memstream *ms)
82 {
83 
84 	assert(ms->len >= 0 && ms->offset >= 0);
85 	*ms->sizep = ms->len < ms->offset ? ms->len : ms->offset;
86 }
87 
88 static int
memstream_write(void * cookie,const char * buf,int len)89 memstream_write(void *cookie, const char *buf, int len)
90 {
91 	struct memstream *ms;
92 	ssize_t tocopy;
93 
94 	ms = cookie;
95 	if (!memstream_grow(ms, ms->offset + len))
96 		return (-1);
97 	tocopy = ms->len - ms->offset;
98 	if (len < tocopy)
99 		tocopy = len;
100 	memcpy(*ms->bufp + ms->offset, buf, tocopy);
101 	ms->offset += tocopy;
102 	memstream_update(ms);
103 #ifdef DEBUG
104 	fprintf(stderr, "MS: write(%p, %d) = %zd\n", ms, len, tocopy);
105 #endif
106 	return (tocopy);
107 }
108 
109 static fpos_t
memstream_seek(void * cookie,fpos_t pos,int whence)110 memstream_seek(void *cookie, fpos_t pos, int whence)
111 {
112 	struct memstream *ms;
113 #ifdef DEBUG
114 	fpos_t old;
115 #endif
116 
117 	ms = cookie;
118 #ifdef DEBUG
119 	old = ms->offset;
120 #endif
121 	switch (whence) {
122 	case SEEK_SET:
123 		/* _fseeko() checks for negative offsets. */
124 		assert(pos >= 0);
125 		ms->offset = pos;
126 		break;
127 	case SEEK_CUR:
128 		/* This is only called by _ftello(). */
129 		assert(pos == 0);
130 		break;
131 	case SEEK_END:
132 		if (pos < 0) {
133 			if (pos + ms->len < 0) {
134 #ifdef DEBUG
135 				fprintf(stderr,
136 				    "MS: bad SEEK_END: pos %jd, len %zd\n",
137 				    (intmax_t)pos, ms->len);
138 #endif
139 				errno = EINVAL;
140 				return (-1);
141 			}
142 		} else {
143 			if (FPOS_MAX - ms->len < pos) {
144 #ifdef DEBUG
145 				fprintf(stderr,
146 				    "MS: bad SEEK_END: pos %jd, len %zd\n",
147 				    (intmax_t)pos, ms->len);
148 #endif
149 				errno = EOVERFLOW;
150 				return (-1);
151 			}
152 		}
153 		ms->offset = ms->len + pos;
154 		break;
155 	}
156 	memstream_update(ms);
157 #ifdef DEBUG
158 	fprintf(stderr, "MS: seek(%p, %jd, %d) %jd -> %jd\n", ms, (intmax_t)pos,
159 	    whence, (intmax_t)old, (intmax_t)ms->offset);
160 #endif
161 	return (ms->offset);
162 }
163 
164 static int
memstream_close(void * cookie)165 memstream_close(void *cookie)
166 {
167 
168 	free(cookie);
169 	return (0);
170 }
171 
172 FILE *
open_memstream(char ** bufp,size_t * sizep)173 open_memstream(char **bufp, size_t *sizep)
174 {
175 	struct memstream *ms;
176 	int save_errno;
177 	FILE *fp;
178 
179 	if (bufp == NULL || sizep == NULL) {
180 		errno = EINVAL;
181 		return (NULL);
182 	}
183 	*bufp = calloc(1, 1);
184 	if (*bufp == NULL)
185 		return (NULL);
186 	ms = malloc(sizeof(*ms));
187 	if (ms == NULL) {
188 		save_errno = errno;
189 		free(*bufp);
190 		*bufp = NULL;
191 		errno = save_errno;
192 		return (NULL);
193 	}
194 	ms->bufp = bufp;
195 	ms->sizep = sizep;
196 	ms->len = 0;
197 	ms->offset = 0;
198 	memstream_update(ms);
199 	fp = funopen(ms, NULL, memstream_write, memstream_seek,
200 	    memstream_close);
201 	if (fp == NULL) {
202 		save_errno = errno;
203 		free(ms);
204 		free(*bufp);
205 		*bufp = NULL;
206 		errno = save_errno;
207 		return (NULL);
208 	}
209 	fwide(fp, -1);
210 	return (fp);
211 }
212