YAP 7.1.0
open_memstream.c
1/* Open a write stream around a malloc'd string.
2 Copyright (C) 2010 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17/* Written by Eric Blake <e...@byu.net>, 2010. */
18
19#include "YapIOConfig.h"
20
21/* Specification. */
22#include <stdio.h>
23
24#include <assert.h>
25#include <errno.h>
26#include <stdlib.h>
27#include <string.h>
28
29// #include "verify.h"
30
31#if !HAVE_OPEN_MEMSTREAM && !_WIN32
32
33#if !HAVE_FUNOPEN
34#error Sorry, not ported to your platform yet
35#else
36
37FILE *open_memstream(char **buf, size_t *len);
38
39#define INITIAL_ALLOC 64
40
41struct data {
42 char **buf; /* User's argument. */
43 size_t *len; /* User's argument. Smaller of pos or eof. */
44 size_t pos; /* Current position. */
45 size_t eof; /* End-of-file position. */
46 size_t allocated; /* Allocated size of *buf, always > eof. */
47 char c; /* Temporary storage for byte overwritten by NUL, if pos < eof. */
48};
49typedef struct data data;
50
51/* Stupid BSD interface uses int/int instead of ssize_t/size_t. */
52// verify (sizeof (int) <= sizeof (size_t));
53// verify (sizeof (int) <= sizeof (ssize_t));
54
55static int mem_write(void *c, const char *buf, int n) {
56 data *cookie = c;
57 char *cbuf = *cookie->buf;
58
59 /* Be sure we don't overflow. */
60 if ((ssize_t)(cookie->pos + n) < 0) {
61 errno = EFBIG;
62 return EOF;
63 }
64 /* Grow the buffer, if necessary. Use geometric growth to avoid
65 quadratic realloc behavior. Overallocate, to accomodate the
66 requirement to always place a trailing NUL not counted by length.
67 Thus, we want max(prev_size*1.5, cookie->posn1). */
68 if (cookie->allocated <= cookie->pos + n) {
69 size_t newsize = cookie->allocated * 3 / 2;
70 if (newsize < cookie->pos + n + 1)
71 newsize = cookie->pos + n + 1;
72 cbuf = realloc(cbuf, newsize);
73 if (!cbuf)
74 return EOF;
75 *cookie->buf = cbuf;
76 cookie->allocated = newsize;
77 }
78 /* If we have previously done a seek beyond eof, ensure all
79 intermediate bytges are NUL. */
80 if (cookie->eof < cookie->pos)
81 memset(cbuf + cookie->eof, '\0', cookie->pos - cookie->eof);
82 memmove(cbuf + cookie->pos, buf, n);
83 cookie->pos += n;
84 /* If the user has previously written beyond the current position,
85 remember what the trailing NUL is overwriting. Otherwise,
86 extend the stream. */
87 if (cookie->eof < cookie->pos)
88 cookie->eof = cookie->pos;
89 else
90 cookie->c = cbuf[cookie->pos];
91 cbuf[cookie->pos] = '\0';
92 *cookie->len = cookie->pos;
93 return n;
94}
95
96static fpos_t mem_seek(void *c, fpos_t pos, int whence) {
97 data *cookie = c;
98 off_t offset = pos;
99
100 if (whence == SEEK_CUR)
101 offset = cookie->pos;
102 else if (whence == SEEK_END)
103 offset = cookie->eof;
104 if (offset < 0) {
105 errno = EINVAL;
106 offset = -1;
107 } else if ((size_t)offset != offset) {
108 errno = ENOSPC;
109 offset = -1;
110 } else {
111 if (cookie->pos < cookie->eof) {
112 (*cookie->buf)[cookie->pos] = cookie->c;
113 cookie->c = '\0';
114 }
115 cookie->pos = offset;
116 if (cookie->pos < cookie->eof) {
117 cookie->c = (*cookie->buf)[cookie->pos];
118 (*cookie->buf)[cookie->pos] = '\0';
119 *cookie->len = cookie->pos;
120 } else
121 *cookie->len = cookie->eof;
122 }
123 return offset;
124}
125
126static int mem_close(void *c) {
127 data *cookie = c;
128 char *buf;
129
130 /* Be nice and try to reduce excess memory. */
131 buf = realloc(*cookie->buf, *cookie->len + 1);
132 if (buf)
133 *cookie->buf = buf;
134 free(cookie);
135 return 0;
136}
137
138FILE *open_memstream(char **buf, size_t *len) {
139 FILE *f;
140 data *cookie;
141
142 if (!buf || !len) {
143 errno = EINVAL;
144 return NULL;
145 }
146 if (!(cookie = malloc(sizeof *cookie)))
147 return NULL;
148 if (!(*buf = malloc(INITIAL_ALLOC))) {
149 free(cookie);
150 errno = ENOMEM;
151 return NULL;
152 }
153 **buf = '\0';
154 *len = 0;
155
156 f = funopen(cookie, NULL, mem_write, mem_seek, mem_close);
157 if (!f) {
158 int saved_errno = errno;
159 free(cookie);
160 errno = saved_errno;
161 } else {
162 cookie->buf = buf;
163 cookie->len = len;
164 cookie->pos = 0;
165 cookie->eof = 0;
166 cookie->c = '\0';
167 cookie->allocated = INITIAL_ALLOC;
168 }
169 return f;
170}
171#endif /* HAVE_FUNOPEN */
172
173#endif /* HAVE_OPEN_MEMSTREAM*/