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(strbuf) \
41 do { \
42 if ((strbuf)->pos < (strbuf)->size / 3) \
43 /* don't free all memory to avoid churn */ \
44 strbuf_resize((strbuf), 2 * (strbuf)->pos); \
45 } while (0)
47 /*
48 * private data structures
49 */
51 struct sdb_strbuf {
52 char *string;
53 size_t size;
54 size_t pos;
55 };
57 /*
58 * private helper functions
59 */
61 static int
62 strbuf_resize(sdb_strbuf_t *strbuf, size_t new_size)
63 {
64 char *tmp;
66 if (new_size <= strbuf->pos)
67 return -1;
69 tmp = realloc(strbuf->string, new_size);
70 if (! tmp)
71 return -1;
73 if (new_size)
74 strbuf->string = tmp;
75 else
76 strbuf->string = NULL;
77 strbuf->size = new_size;
78 return 0;
79 } /* strbuf_resize */
81 /*
82 * public API
83 */
85 sdb_strbuf_t *
86 sdb_strbuf_create(size_t size)
87 {
88 sdb_strbuf_t *strbuf;
90 strbuf = calloc(1, sizeof(*strbuf));
91 if (! strbuf)
92 return NULL;
94 strbuf->string = NULL;
95 if (size) {
96 strbuf->string = malloc(size);
97 if (! strbuf->string) {
98 free(strbuf);
99 return NULL;
100 }
102 strbuf->string[0] = '\0';
103 }
105 strbuf->size = size;
106 strbuf->pos = 0;
108 return strbuf;
109 } /* sdb_strbuf_create */
111 void
112 sdb_strbuf_destroy(sdb_strbuf_t *strbuf)
113 {
114 if (! strbuf)
115 return;
117 if (strbuf->string)
118 free(strbuf->string);
119 free(strbuf);
120 } /* sdb_strbuf_destroy */
122 ssize_t
123 sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
124 {
125 va_list aq;
126 int status;
128 if ((! strbuf) || (! fmt))
129 return -1;
131 assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0'));
133 if (! strbuf->size) {
134 /* use some arbitrary but somewhat reasonable default */
135 if (strbuf_resize(strbuf, 64))
136 return -1;
137 }
138 /* make sure to reserve space for the nul-byte */
139 else if (strbuf->pos >= strbuf->size - 1)
140 if (strbuf_resize(strbuf, 2 * strbuf->size))
141 return -1;
143 assert(strbuf->size && strbuf->string);
144 assert(strbuf->pos < strbuf->size);
146 /* 'ap' is invalid after calling vsnprintf; thus copy before using it */
147 va_copy(aq, ap);
148 status = vsnprintf(strbuf->string + strbuf->pos,
149 strbuf->size - strbuf->pos, fmt, ap);
151 if (status < 0) {
152 va_end(aq);
153 return status;
154 }
156 /* 'status' does not include nul-byte */
157 if ((size_t)status >= strbuf->size - strbuf->pos) {
158 if (strbuf_resize(strbuf, strbuf->pos + (size_t)status + 1)) {
159 va_end(aq);
160 return -1;
161 }
163 /* reset string and try again */
164 strbuf->string[strbuf->pos] = '\0';
165 status = (int)sdb_strbuf_vappend(strbuf, fmt, aq);
166 }
167 else
168 strbuf->pos += (size_t)status;
170 va_end(aq);
172 /* even though this function always appends to the existing buffer, the
173 * size might have previously been reset */
174 CHECK_SHRINK(strbuf);
176 return (ssize_t)status;
177 } /* sdb_strbuf_vappend */
179 ssize_t
180 sdb_strbuf_append(sdb_strbuf_t *strbuf, const char *fmt, ...)
181 {
182 va_list ap;
183 ssize_t status;
185 va_start(ap, fmt);
186 status = sdb_strbuf_vappend(strbuf, fmt, ap);
187 va_end(ap);
189 return status;
190 } /* sdb_strbuf_append */
192 ssize_t
193 sdb_strbuf_vsprintf(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
194 {
195 if (! strbuf)
196 return -1;
198 if (strbuf->size) {
199 strbuf->string[0] = '\0';
200 strbuf->pos = 0;
201 }
203 return sdb_strbuf_vappend(strbuf, fmt, ap);
204 } /* sdb_strbuf_vsprintf */
206 ssize_t
207 sdb_strbuf_sprintf(sdb_strbuf_t *strbuf, const char *fmt, ...)
208 {
209 va_list ap;
210 ssize_t status;
212 va_start(ap, fmt);
213 status = sdb_strbuf_vsprintf(strbuf, fmt, ap);
214 va_end(ap);
216 return status;
217 } /* sdb_strbuf_sprintf */
219 ssize_t
220 sdb_strbuf_memappend(sdb_strbuf_t *strbuf, const void *data, size_t n)
221 {
222 if ((! strbuf) || (! data))
223 return -1;
225 assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0'));
227 if (strbuf->pos + n + 1 >= strbuf->size) {
228 size_t newsize = strbuf->size * 2;
230 if (! newsize)
231 newsize = 64;
232 while (strbuf->pos + n + 1 >= newsize)
233 newsize *= 2;
235 if (strbuf_resize(strbuf, newsize))
236 return -1;
237 }
239 assert(strbuf->size && strbuf->string);
240 assert(strbuf->pos < strbuf->size);
242 memcpy((void *)(strbuf->string + strbuf->pos), data, n);
243 strbuf->pos += n;
244 strbuf->string[strbuf->pos] = '\0';
246 /* even though this function always appends to the existing buffer, the
247 * size might have previously been reset */
248 CHECK_SHRINK(strbuf);
250 return (ssize_t)n;
251 } /* sdb_strbuf_memappend */
253 ssize_t
254 sdb_strbuf_memcpy(sdb_strbuf_t *strbuf, const void *data, size_t n)
255 {
256 if ((! strbuf) || (! data))
257 return -1;
259 if (strbuf->size) {
260 strbuf->string[0] = '\0';
261 strbuf->pos = 0;
262 }
264 return sdb_strbuf_memappend(strbuf, data, n);
265 } /* sdb_strbuf_memcpy */
267 ssize_t
268 sdb_strbuf_read(sdb_strbuf_t *strbuf, int fd, size_t n)
269 {
270 ssize_t ret;
272 if (! strbuf)
273 return -1;
275 if (strbuf->pos + n + 1 >= strbuf->size)
276 if (strbuf_resize(strbuf, strbuf->pos + n + 1))
277 return -1;
279 ret = read(fd, strbuf->string + strbuf->pos, n);
280 if (ret > 0)
281 strbuf->pos += (size_t)ret;
282 return ret;
283 } /* sdb_strbuf_read */
285 ssize_t
286 sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
287 {
288 ssize_t ret = 0;
290 if (! strbuf)
291 return -1;
293 assert((!strbuf->size) || (strbuf->pos < strbuf->size));
294 assert(strbuf->pos <= strbuf->size);
296 while ((strbuf->pos > 0)
297 && (strbuf->string[strbuf->pos - 1] == '\n')) {
298 --strbuf->pos;
299 strbuf->string[strbuf->pos] = '\0';
300 ++ret;
301 }
303 return ret;
304 } /* sdb_strbuf_chomp */
306 void
307 sdb_strbuf_skip(sdb_strbuf_t *strbuf, size_t offset, size_t n)
308 {
309 char *start;
310 size_t len;
312 if ((! strbuf) || (! n))
313 return;
315 if (offset >= strbuf->pos)
316 return;
318 len = strbuf->pos - offset;
320 if (n >= len) {
321 strbuf->string[offset] = '\0';
322 strbuf->pos = offset;
323 return;
324 }
326 assert(offset + n < strbuf->pos);
327 assert(offset < strbuf->pos);
329 start = strbuf->string + offset;
330 memmove(start, start + n, len - n);
331 strbuf->pos -= n;
332 strbuf->string[strbuf->pos] = '\0';
334 /* don't resize now but wait for the next write to avoid churn */
335 } /* sdb_strbuf_skip */
337 void
338 sdb_strbuf_clear(sdb_strbuf_t *strbuf)
339 {
340 if ((! strbuf) || (! strbuf->size))
341 return;
343 strbuf->string[0] = '\0';
344 strbuf->pos = 0;
346 /* don't resize now but wait for the next write to avoid churn */
347 } /* sdb_strbuf_clear */
349 const char *
350 sdb_strbuf_string(sdb_strbuf_t *strbuf)
351 {
352 if (! strbuf)
353 return NULL;
354 if (! strbuf->size)
355 return "";
356 return strbuf->string;
357 } /* sdb_strbuf_string */
359 size_t
360 sdb_strbuf_len(sdb_strbuf_t *strbuf)
361 {
362 if (! strbuf)
363 return 0;
364 return strbuf->pos;
365 } /* sdb_strbuf_string */
367 size_t
368 sdb_strbuf_cap(sdb_strbuf_t *strbuf)
369 {
370 if (! strbuf)
371 return 0;
372 return strbuf->size;
373 } /* sdb_strbuf_cap */
375 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */