1 /*
2 * SysDB - src/utils/strbuf.c
3 * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #include "utils/strbuf.h"
30 #include <assert.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <string.h>
37 #include <unistd.h>
39 /* free memory if most of the buffer is unused */
40 #define CHECK_SHRINK(buf) \
41 do { \
42 if (((buf)->pos < (buf)->size / 3) \
43 && (2 * (buf)->pos > (buf)->min_size)) \
44 /* don't free all memory to avoid churn */ \
45 strbuf_resize((buf), 2 * (buf)->pos); \
46 } while (0)
48 /*
49 * private data structures
50 */
52 struct sdb_strbuf {
53 char *string;
54 size_t size;
55 size_t pos;
57 /* min size to shrink the buffer to */
58 size_t min_size;
59 };
61 /*
62 * private helper functions
63 */
65 static int
66 strbuf_resize(sdb_strbuf_t *buf, size_t new_size)
67 {
68 char *tmp;
70 if (new_size <= buf->pos)
71 return -1;
73 tmp = realloc(buf->string, new_size);
74 if (! tmp)
75 return -1;
77 if (new_size)
78 buf->string = tmp;
79 else
80 buf->string = NULL;
81 buf->size = new_size;
82 return 0;
83 } /* strbuf_resize */
85 /*
86 * public API
87 */
89 sdb_strbuf_t *
90 sdb_strbuf_create(size_t size)
91 {
92 sdb_strbuf_t *buf;
94 buf = calloc(1, sizeof(*buf));
95 if (! buf)
96 return NULL;
98 buf->string = NULL;
99 if (size) {
100 buf->string = malloc(size);
101 if (! buf->string) {
102 free(buf);
103 return NULL;
104 }
106 buf->string[0] = '\0';
107 buf->min_size = size;
108 }
109 else
110 buf->min_size = 64;
112 buf->size = size;
113 buf->pos = 0;
115 return buf;
116 } /* sdb_strbuf_create */
118 void
119 sdb_strbuf_destroy(sdb_strbuf_t *buf)
120 {
121 if (! buf)
122 return;
124 if (buf->string)
125 free(buf->string);
126 free(buf);
127 } /* sdb_strbuf_destroy */
129 ssize_t
130 sdb_strbuf_vappend(sdb_strbuf_t *buf, const char *fmt, va_list ap)
131 {
132 va_list aq;
133 int status;
135 if ((! buf) || (! fmt))
136 return -1;
138 assert((buf->size == 0) || (buf->string[buf->pos] == '\0'));
140 if (! buf->size) {
141 /* use some arbitrary but somewhat reasonable default */
142 if (strbuf_resize(buf, 64))
143 return -1;
144 }
145 /* make sure to reserve space for the nul-byte */
146 else if (buf->pos >= buf->size - 1)
147 if (strbuf_resize(buf, 2 * buf->size))
148 return -1;
150 assert(buf->size && buf->string);
151 assert(buf->pos < buf->size);
153 /* 'ap' is invalid after calling vsnprintf; thus copy before using it */
154 va_copy(aq, ap);
155 status = vsnprintf(buf->string + buf->pos,
156 buf->size - buf->pos, fmt, ap);
158 if (status < 0) {
159 va_end(aq);
160 return status;
161 }
163 /* 'status' does not include nul-byte */
164 if ((size_t)status >= buf->size - buf->pos) {
165 if (strbuf_resize(buf, buf->pos + (size_t)status + 1)) {
166 va_end(aq);
167 return -1;
168 }
170 /* reset string and try again */
171 buf->string[buf->pos] = '\0';
172 status = (int)sdb_strbuf_vappend(buf, fmt, aq);
173 }
174 else
175 buf->pos += (size_t)status;
177 va_end(aq);
179 /* even though this function always appends to the existing buffer, the
180 * size might have previously been reset */
181 CHECK_SHRINK(buf);
183 return (ssize_t)status;
184 } /* sdb_strbuf_vappend */
186 ssize_t
187 sdb_strbuf_append(sdb_strbuf_t *buf, const char *fmt, ...)
188 {
189 va_list ap;
190 ssize_t status;
192 va_start(ap, fmt);
193 status = sdb_strbuf_vappend(buf, fmt, ap);
194 va_end(ap);
196 return status;
197 } /* sdb_strbuf_append */
199 ssize_t
200 sdb_strbuf_vsprintf(sdb_strbuf_t *buf, const char *fmt, va_list ap)
201 {
202 if (! buf)
203 return -1;
205 if (buf->size) {
206 buf->string[0] = '\0';
207 buf->pos = 0;
208 }
210 return sdb_strbuf_vappend(buf, fmt, ap);
211 } /* sdb_strbuf_vsprintf */
213 ssize_t
214 sdb_strbuf_sprintf(sdb_strbuf_t *buf, const char *fmt, ...)
215 {
216 va_list ap;
217 ssize_t status;
219 va_start(ap, fmt);
220 status = sdb_strbuf_vsprintf(buf, fmt, ap);
221 va_end(ap);
223 return status;
224 } /* sdb_strbuf_sprintf */
226 ssize_t
227 sdb_strbuf_memappend(sdb_strbuf_t *buf, const void *data, size_t n)
228 {
229 if ((! buf) || (! data))
230 return -1;
232 assert((buf->size == 0) || (buf->string[buf->pos] == '\0'));
234 if (buf->pos + n + 1 >= buf->size) {
235 size_t newsize = buf->size * 2;
237 if (! newsize)
238 newsize = 64;
239 while (buf->pos + n + 1 >= newsize)
240 newsize *= 2;
242 if (strbuf_resize(buf, newsize))
243 return -1;
244 }
246 assert(buf->size && buf->string);
247 assert(buf->pos < buf->size);
249 memcpy((void *)(buf->string + buf->pos), data, n);
250 buf->pos += n;
251 buf->string[buf->pos] = '\0';
253 /* even though this function always appends to the existing buffer, the
254 * size might have previously been reset */
255 CHECK_SHRINK(buf);
257 return (ssize_t)n;
258 } /* sdb_strbuf_memappend */
260 ssize_t
261 sdb_strbuf_memcpy(sdb_strbuf_t *buf, const void *data, size_t n)
262 {
263 if ((! buf) || (! data))
264 return -1;
266 if (buf->size) {
267 buf->string[0] = '\0';
268 buf->pos = 0;
269 }
271 return sdb_strbuf_memappend(buf, data, n);
272 } /* sdb_strbuf_memcpy */
274 ssize_t
275 sdb_strbuf_read(sdb_strbuf_t *buf, int fd, size_t n)
276 {
277 ssize_t ret;
279 if (! buf)
280 return -1;
282 if (buf->pos + n + 1 >= buf->size)
283 if (strbuf_resize(buf, buf->pos + n + 1))
284 return -1;
286 ret = read(fd, buf->string + buf->pos, n);
287 if (ret > 0)
288 buf->pos += (size_t)ret;
289 return ret;
290 } /* sdb_strbuf_read */
292 ssize_t
293 sdb_strbuf_chomp(sdb_strbuf_t *buf)
294 {
295 ssize_t ret = 0;
297 if (! buf)
298 return -1;
300 assert((!buf->size) || (buf->pos < buf->size));
301 assert(buf->pos <= buf->size);
303 while ((buf->pos > 0)
304 && (buf->string[buf->pos - 1] == '\n')) {
305 --buf->pos;
306 buf->string[buf->pos] = '\0';
307 ++ret;
308 }
310 return ret;
311 } /* sdb_strbuf_chomp */
313 void
314 sdb_strbuf_skip(sdb_strbuf_t *buf, size_t offset, size_t n)
315 {
316 char *start;
317 size_t len;
319 if ((! buf) || (! n))
320 return;
322 if (offset >= buf->pos)
323 return;
325 len = buf->pos - offset;
327 if (n >= len) {
328 buf->string[offset] = '\0';
329 buf->pos = offset;
330 return;
331 }
333 assert(offset + n < buf->pos);
334 assert(offset < buf->pos);
336 start = buf->string + offset;
337 memmove(start, start + n, len - n);
338 buf->pos -= n;
339 buf->string[buf->pos] = '\0';
341 /* don't resize now but wait for the next write to avoid churn */
342 } /* sdb_strbuf_skip */
344 void
345 sdb_strbuf_clear(sdb_strbuf_t *buf)
346 {
347 if ((! buf) || (! buf->size))
348 return;
350 buf->string[0] = '\0';
351 buf->pos = 0;
353 /* don't resize now but wait for the next write to avoid churn */
354 } /* sdb_strbuf_clear */
356 const char *
357 sdb_strbuf_string(sdb_strbuf_t *buf)
358 {
359 if (! buf)
360 return NULL;
361 if (! buf->size)
362 return "";
363 return buf->string;
364 } /* sdb_strbuf_string */
366 size_t
367 sdb_strbuf_len(sdb_strbuf_t *buf)
368 {
369 if (! buf)
370 return 0;
371 return buf->pos;
372 } /* sdb_strbuf_string */
374 size_t
375 sdb_strbuf_cap(sdb_strbuf_t *buf)
376 {
377 if (! buf)
378 return 0;
379 return buf->size;
380 } /* sdb_strbuf_cap */
382 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */