exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / string-buffer.c
blob6ea589d67dfee1852871f3a09bf49c9a34190246
1 /* A buffer that accumulates a string by piecewise concatenation.
2 Copyright (C) 2021-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation, either version 3 of the
7 License, or (at your option) any later version.
9 This file 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 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible <bruno@clisp.org>, 2021. */
19 #include <config.h>
21 /* Specification. */
22 #include "string-buffer.h"
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
29 void
30 sb_init (struct string_buffer *buffer)
32 buffer->data = buffer->space;
33 buffer->length = 0;
34 buffer->allocated = sizeof (buffer->space);
35 buffer->error = false;
38 /* Ensures that INCREMENT bytes are available beyond the current used length
39 of BUFFER.
40 Returns 0, or -1 in case of out-of-memory error. */
41 static int
42 sb_ensure_more_bytes (struct string_buffer *buffer, size_t increment)
44 size_t incremented_length = buffer->length + increment;
45 if (incremented_length < increment)
46 /* Overflow. */
47 return -1;
49 if (buffer->allocated < incremented_length)
51 size_t new_allocated = 2 * buffer->allocated;
52 if (new_allocated < buffer->allocated)
53 /* Overflow. */
54 return -1;
55 if (new_allocated < incremented_length)
56 new_allocated = incremented_length;
58 char *new_data;
59 if (buffer->data == buffer->space)
61 new_data = (char *) malloc (new_allocated);
62 if (new_data == NULL)
63 /* Out-of-memory. */
64 return -1;
65 memcpy (new_data, buffer->data, buffer->length);
67 else
69 new_data = (char *) realloc (buffer->data, new_allocated);
70 if (new_data == NULL)
71 /* Out-of-memory. */
72 return -1;
74 buffer->data = new_data;
75 buffer->allocated = new_allocated;
77 return 0;
80 int
81 sb_append (struct string_buffer *buffer, const char *str)
83 size_t len = strlen (str);
84 if (sb_ensure_more_bytes (buffer, len) < 0)
86 buffer->error = true;
87 return -1;
89 memcpy (buffer->data + buffer->length, str, len);
90 buffer->length += len;
91 return 0;
94 int
95 sb_appendvf (struct string_buffer *buffer, const char *formatstring,
96 va_list list)
98 va_list list_copy;
100 /* Make a bit of room, so that the probability that the first vsnprintf() call
101 succeeds is high. */
102 size_t room = buffer->allocated - buffer->length;
103 if (room < 64)
105 if (sb_ensure_more_bytes (buffer, 64) < 0)
107 buffer->error = true;
108 return -1;
110 room = buffer->allocated - buffer->length;
113 va_copy (list_copy, list);
115 /* First vsnprintf() call. */
116 int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, list);
117 if (ret < 0)
119 /* Failed. */
120 buffer->error = true;
121 ret = -1;
123 else
125 if ((size_t) ret <= room)
127 /* The result has fit into room bytes. */
128 buffer->length += (size_t) ret;
129 ret = 0;
131 else
133 /* The result was truncated. Make more room, for a second vsnprintf()
134 call. */
135 if (sb_ensure_more_bytes (buffer, (size_t) ret) < 0)
137 buffer->error = true;
138 ret = -1;
140 else
142 /* Second vsnprintf() call. */
143 room = buffer->allocated - buffer->length;
144 ret = vsnprintf (buffer->data + buffer->length, room,
145 formatstring, list_copy);
146 if (ret < 0)
148 /* Failed. */
149 buffer->error = true;
150 ret = -1;
152 else
154 if ((size_t) ret <= room)
156 /* The result has fit into room bytes. */
157 buffer->length += (size_t) ret;
158 ret = 0;
160 else
161 /* The return values of the vsnprintf() calls are not
162 consistent. */
163 abort ();
169 va_end (list_copy);
170 return ret;
174 sb_appendf (struct string_buffer *buffer, const char *formatstring, ...)
176 va_list args;
178 /* Make a bit of room, so that the probability that the first vsnprintf() call
179 succeeds is high. */
180 size_t room = buffer->allocated - buffer->length;
181 if (room < 64)
183 if (sb_ensure_more_bytes (buffer, 64) < 0)
185 buffer->error = true;
186 return -1;
188 room = buffer->allocated - buffer->length;
191 va_start (args, formatstring);
193 /* First vsnprintf() call. */
194 int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, args);
195 if (ret < 0)
197 /* Failed. */
198 buffer->error = true;
199 ret = -1;
201 else
203 if ((size_t) ret <= room)
205 /* The result has fit into room bytes. */
206 buffer->length += (size_t) ret;
207 ret = 0;
209 else
211 /* The result was truncated. Make more room, for a second vsnprintf()
212 call. */
213 if (sb_ensure_more_bytes (buffer, (size_t) ret) < 0)
215 buffer->error = true;
216 ret = -1;
218 else
220 /* Second vsnprintf() call. */
221 room = buffer->allocated - buffer->length;
222 va_end (args);
223 va_start (args, formatstring);
224 ret = vsnprintf (buffer->data + buffer->length, room,
225 formatstring, args);
226 if (ret < 0)
228 /* Failed. */
229 buffer->error = true;
230 ret = -1;
232 else
234 if ((size_t) ret <= room)
236 /* The result has fit into room bytes. */
237 buffer->length += (size_t) ret;
238 ret = 0;
240 else
241 /* The return values of the vsnprintf() calls are not
242 consistent. */
243 abort ();
249 va_end (args);
250 return ret;
253 void
254 sb_free (struct string_buffer *buffer)
256 if (buffer->data != buffer->space)
257 free (buffer->data);
260 /* Returns the contents of BUFFER, and frees all other memory held
261 by BUFFER. Returns NULL upon failure or if there was an error earlier. */
262 char *
263 sb_dupfree (struct string_buffer *buffer)
265 if (buffer->error)
266 goto fail;
268 if (sb_ensure_more_bytes (buffer, 1) < 0)
269 goto fail;
270 buffer->data[buffer->length] = '\0';
271 buffer->length++;
273 if (buffer->data == buffer->space)
275 char *copy = (char *) malloc (buffer->length);
276 if (copy == NULL)
277 goto fail;
278 memcpy (copy, buffer->data, buffer->length);
279 return copy;
281 else
283 /* Shrink the string before returning it. */
284 char *contents = buffer->data;
285 if (buffer->length < buffer->allocated)
287 contents = realloc (contents, buffer->length);
288 if (contents == NULL)
289 goto fail;
291 return contents;
294 fail:
295 sb_free (buffer);
296 return NULL;