1 /* A buffer that accumulates a string by piecewise concatenation.
2    Copyright (C) 2021 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, or (at your option)
7    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 <https://www.gnu.org/licenses/>.  */
16 
17 /* Written by Bruno Haible <bruno@clisp.org>, 2021.  */
18 
19 #include <config.h>
20 
21 /* Specification.  */
22 #include "string-buffer.h"
23 
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 void
sb_init(struct string_buffer * buffer)30 sb_init (struct string_buffer *buffer)
31 {
32   buffer->data = buffer->space;
33   buffer->length = 0;
34   buffer->allocated = sizeof (buffer->space);
35   buffer->error = false;
36 }
37 
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
sb_ensure_more_bytes(struct string_buffer * buffer,size_t increment)42 sb_ensure_more_bytes (struct string_buffer *buffer, size_t increment)
43 {
44   size_t incremented_length = buffer->length + increment;
45   if (incremented_length < increment)
46     /* Overflow.  */
47     return -1;
48 
49   if (buffer->allocated < incremented_length)
50     {
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;
57 
58       char *new_data;
59       if (buffer->data == buffer->space)
60         {
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);
66         }
67       else
68         {
69           new_data = (char *) realloc (buffer->data, new_allocated);
70           if (new_data == NULL)
71             /* Out-of-memory.  */
72             return -1;
73         }
74       buffer->data = new_data;
75       buffer->allocated = new_allocated;
76     }
77   return 0;
78 }
79 
80 int
sb_append(struct string_buffer * buffer,const char * str)81 sb_append (struct string_buffer *buffer, const char *str)
82 {
83   size_t len = strlen (str);
84   if (sb_ensure_more_bytes (buffer, len) < 0)
85     {
86       buffer->error = true;
87       return -1;
88     }
89   memcpy (buffer->data + buffer->length, str, len);
90   buffer->length += len;
91   return 0;
92 }
93 
94 int
sb_appendvf(struct string_buffer * buffer,const char * formatstring,va_list list)95 sb_appendvf (struct string_buffer *buffer, const char *formatstring,
96              va_list list)
97 {
98   va_list list_copy;
99 
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)
104     {
105       if (sb_ensure_more_bytes (buffer, 64) < 0)
106         {
107           buffer->error = true;
108           return -1;
109         }
110       room = buffer->allocated - buffer->length;
111     }
112 
113   va_copy (list_copy, list);
114 
115   /* First vsnprintf() call.  */
116   int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, list);
117   if (ret < 0)
118     {
119       /* Failed.  */
120       buffer->error = true;
121       ret = -1;
122     }
123   else
124     {
125       if ((size_t) ret <= room)
126         {
127           /* The result has fit into room bytes.  */
128           buffer->length += (size_t) ret;
129           ret = 0;
130         }
131       else
132         {
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)
136             {
137               buffer->error = true;
138               ret = -1;
139             }
140           else
141             {
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)
147                 {
148                   /* Failed.  */
149                   buffer->error = true;
150                   ret = -1;
151                 }
152               else
153                 {
154                   if ((size_t) ret <= room)
155                     {
156                       /* The result has fit into room bytes.  */
157                       buffer->length += (size_t) ret;
158                       ret = 0;
159                     }
160                   else
161                     /* The return values of the vsnprintf() calls are not
162                        consistent.  */
163                     abort ();
164                 }
165             }
166         }
167     }
168 
169   va_end (list_copy);
170   return ret;
171 }
172 
173 int
sb_appendf(struct string_buffer * buffer,const char * formatstring,...)174 sb_appendf (struct string_buffer *buffer, const char *formatstring, ...)
175 {
176   va_list args;
177 
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)
182     {
183       if (sb_ensure_more_bytes (buffer, 64) < 0)
184         {
185           buffer->error = true;
186           return -1;
187         }
188       room = buffer->allocated - buffer->length;
189     }
190 
191   va_start (args, formatstring);
192 
193   /* First vsnprintf() call.  */
194   int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, args);
195   if (ret < 0)
196     {
197       /* Failed.  */
198       buffer->error = true;
199       ret = -1;
200     }
201   else
202     {
203       if ((size_t) ret <= room)
204         {
205           /* The result has fit into room bytes.  */
206           buffer->length += (size_t) ret;
207           ret = 0;
208         }
209       else
210         {
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)
214             {
215               buffer->error = true;
216               ret = -1;
217             }
218           else
219             {
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)
227                 {
228                   /* Failed.  */
229                   buffer->error = true;
230                   ret = -1;
231                 }
232               else
233                 {
234                   if ((size_t) ret <= room)
235                     {
236                       /* The result has fit into room bytes.  */
237                       buffer->length += (size_t) ret;
238                       ret = 0;
239                     }
240                   else
241                     /* The return values of the vsnprintf() calls are not
242                        consistent.  */
243                     abort ();
244                 }
245             }
246         }
247     }
248 
249   va_end (args);
250   return ret;
251 }
252 
253 void
sb_free(struct string_buffer * buffer)254 sb_free (struct string_buffer *buffer)
255 {
256   if (buffer->data != buffer->space)
257     free (buffer->data);
258 }
259 
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 *
sb_dupfree(struct string_buffer * buffer)263 sb_dupfree (struct string_buffer *buffer)
264 {
265   if (buffer->error)
266     goto fail;
267 
268   if (sb_ensure_more_bytes (buffer, 1) < 0)
269     goto fail;
270   buffer->data[buffer->length] = '\0';
271   buffer->length++;
272 
273   if (buffer->data == buffer->space)
274     {
275       char *copy = (char *) malloc (buffer->length);
276       if (copy == NULL)
277         goto fail;
278       memcpy (copy, buffer->data, buffer->length);
279       return copy;
280     }
281   else
282     {
283       /* Shrink the string before returning it.  */
284       char *contents = buffer->data;
285       if (buffer->length < buffer->allocated)
286         {
287           contents = realloc (contents, buffer->length);
288           if (contents == NULL)
289             goto fail;
290         }
291       return contents;
292     }
293 
294  fail:
295   sb_free (buffer);
296   return NULL;
297 }
298