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 "sysdb.h"
29 #include "utils/strbuf.h"
31 #include <assert.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <string.h>
38 #include <unistd.h>
40 /* free memory if most of the buffer is unused */
41 #define CHECK_SHRINK(buf) \
42 do { \
43 if (((buf)->pos < (buf)->size / 3) \
44 && (2 * (buf)->pos > (buf)->min_size)) \
45 /* don't free all memory to avoid churn */ \
46 strbuf_resize((buf), 2 * (buf)->pos); \
47 } while (0)
49 /*
50 * private data structures
51 */
53 struct sdb_strbuf {
54 char *string;
55 size_t size;
56 size_t pos;
58 /* min size to shrink the buffer to */
59 size_t min_size;
60 };
62 /*
63 * private helper functions
64 */
66 static int
67 strbuf_resize(sdb_strbuf_t *buf, size_t new_size)
68 {
69 size_t tmp_size;
70 char *tmp;
72 if (new_size <= buf->pos)
73 return -1;
75 tmp_size = SDB_MAX(buf->size, buf->min_size);
76 if (! tmp_size)
77 tmp_size = 64;
78 while (tmp_size < new_size)
79 tmp_size *= 2;
81 tmp = realloc(buf->string, new_size);
82 if (! tmp)
83 return -1;
85 if (new_size)
86 buf->string = tmp;
87 else
88 buf->string = NULL;
89 buf->size = new_size;
90 return 0;
91 } /* strbuf_resize */
93 /*
94 * public API
95 */
97 sdb_strbuf_t *
98 sdb_strbuf_create(size_t size)
99 {
100 sdb_strbuf_t *buf;
102 buf = calloc(1, sizeof(*buf));
103 if (! buf)
104 return NULL;
106 buf->string = NULL;
107 if (size) {
108 buf->string = malloc(size);
109 if (! buf->string) {
110 free(buf);
111 return NULL;
112 }
114 buf->string[0] = '\0';
115 buf->min_size = size;
116 }
117 else
118 buf->min_size = 64;
120 buf->size = size;
121 buf->pos = 0;
123 return buf;
124 } /* sdb_strbuf_create */
126 void
127 sdb_strbuf_destroy(sdb_strbuf_t *buf)
128 {
129 if (! buf)
130 return;
132 if (buf->string)
133 free(buf->string);
134 free(buf);
135 } /* sdb_strbuf_destroy */
137 ssize_t
138 sdb_strbuf_vappend(sdb_strbuf_t *buf, const char *fmt, va_list ap)
139 {
140 va_list aq;
141 int status;
143 if ((! buf) || (! fmt))
144 return -1;
146 assert((buf->size == 0) || (buf->string[buf->pos] == '\0'));
148 if (! buf->size) {
149 /* use some arbitrary but somewhat reasonable default */
150 if (strbuf_resize(buf, 64))
151 return -1;
152 }
153 /* make sure to reserve space for the nul-byte */
154 else if (buf->pos >= buf->size - 1)
155 if (strbuf_resize(buf, 2 * buf->size))
156 return -1;
158 assert(buf->size && buf->string);
159 assert(buf->pos < buf->size);
161 /* 'ap' is invalid after calling vsnprintf; thus copy before using it */
162 va_copy(aq, ap);
163 status = vsnprintf(buf->string + buf->pos,
164 buf->size - buf->pos, fmt, ap);
166 if (status < 0) {
167 va_end(aq);
168 return status;
169 }
171 /* 'status' does not include nul-byte */
172 if ((size_t)status >= buf->size - buf->pos) {
173 if (strbuf_resize(buf, buf->pos + (size_t)status + 1)) {
174 va_end(aq);
175 return -1;
176 }
178 /* reset string and try again */
179 buf->string[buf->pos] = '\0';
180 status = (int)sdb_strbuf_vappend(buf, fmt, aq);
181 }
182 else
183 buf->pos += (size_t)status;
185 va_end(aq);
187 /* even though this function always appends to the existing buffer, the
188 * size might have previously been reset */
189 CHECK_SHRINK(buf);
191 return (ssize_t)status;
192 } /* sdb_strbuf_vappend */
194 ssize_t
195 sdb_strbuf_append(sdb_strbuf_t *buf, const char *fmt, ...)
196 {
197 va_list ap;
198 ssize_t status;
200 va_start(ap, fmt);
201 status = sdb_strbuf_vappend(buf, fmt, ap);
202 va_end(ap);
204 return status;
205 } /* sdb_strbuf_append */
207 ssize_t
208 sdb_strbuf_vsprintf(sdb_strbuf_t *buf, const char *fmt, va_list ap)
209 {
210 if (! buf)
211 return -1;
213 if (buf->size) {
214 buf->string[0] = '\0';
215 buf->pos = 0;
216 }
218 return sdb_strbuf_vappend(buf, fmt, ap);
219 } /* sdb_strbuf_vsprintf */
221 ssize_t
222 sdb_strbuf_sprintf(sdb_strbuf_t *buf, const char *fmt, ...)
223 {
224 va_list ap;
225 ssize_t status;
227 va_start(ap, fmt);
228 status = sdb_strbuf_vsprintf(buf, fmt, ap);
229 va_end(ap);
231 return status;
232 } /* sdb_strbuf_sprintf */
234 ssize_t
235 sdb_strbuf_memappend(sdb_strbuf_t *buf, const void *data, size_t n)
236 {
237 if ((! buf) || (! data))
238 return -1;
240 assert((buf->size == 0) || (buf->string[buf->pos] == '\0'));
242 if (buf->pos + n + 1 > buf->size) {
243 if (strbuf_resize(buf, buf->pos + n + 1))
244 return -1;
245 }
247 assert(buf->size && buf->string);
248 assert(buf->pos < buf->size);
250 memcpy((void *)(buf->string + buf->pos), data, n);
251 buf->pos += n;
252 buf->string[buf->pos] = '\0';
254 /* even though this function always appends to the existing buffer, the
255 * size might have previously been reset */
256 CHECK_SHRINK(buf);
258 return (ssize_t)n;
259 } /* sdb_strbuf_memappend */
261 ssize_t
262 sdb_strbuf_memcpy(sdb_strbuf_t *buf, const void *data, size_t n)
263 {
264 if ((! buf) || (! data))
265 return -1;
267 if (buf->size) {
268 buf->string[0] = '\0';
269 buf->pos = 0;
270 }
272 return sdb_strbuf_memappend(buf, data, n);
273 } /* sdb_strbuf_memcpy */
275 ssize_t
276 sdb_strbuf_read(sdb_strbuf_t *buf, int fd, size_t n)
277 {
278 ssize_t ret;
280 if (! buf)
281 return -1;
283 if (buf->pos + n + 1 >= buf->size)
284 if (strbuf_resize(buf, buf->pos + n + 1))
285 return -1;
287 ret = read(fd, buf->string + buf->pos, n);
288 if (ret > 0)
289 buf->pos += (size_t)ret;
290 return ret;
291 } /* sdb_strbuf_read */
293 ssize_t
294 sdb_strbuf_chomp(sdb_strbuf_t *buf)
295 {
296 ssize_t ret = 0;
298 if (! buf)
299 return -1;
301 assert((!buf->size) || (buf->pos < buf->size));
302 assert(buf->pos <= buf->size);
304 while ((buf->pos > 0)
305 && (buf->string[buf->pos - 1] == '\n')) {
306 --buf->pos;
307 buf->string[buf->pos] = '\0';
308 ++ret;
309 }
311 return ret;
312 } /* sdb_strbuf_chomp */
314 void
315 sdb_strbuf_skip(sdb_strbuf_t *buf, size_t offset, size_t n)
316 {
317 char *start;
318 size_t len;
320 if ((! buf) || (! n))
321 return;
323 if (offset >= buf->pos)
324 return;
326 len = buf->pos - offset;
328 if (n >= len) {
329 buf->string[offset] = '\0';
330 buf->pos = offset;
331 return;
332 }
334 assert(offset + n < buf->pos);
335 assert(offset < buf->pos);
337 start = buf->string + offset;
338 memmove(start, start + n, len - n);
339 buf->pos -= n;
340 buf->string[buf->pos] = '\0';
342 /* don't resize now but wait for the next write to avoid churn */
343 } /* sdb_strbuf_skip */
345 void
346 sdb_strbuf_clear(sdb_strbuf_t *buf)
347 {
348 if ((! buf) || (! buf->size))
349 return;
351 buf->string[0] = '\0';
352 buf->pos = 0;
354 /* don't resize now but wait for the next write to avoid churn */
355 } /* sdb_strbuf_clear */
357 const char *
358 sdb_strbuf_string(sdb_strbuf_t *buf)
359 {
360 if (! buf)
361 return NULL;
362 if (! buf->size)
363 return "";
364 return buf->string;
365 } /* sdb_strbuf_string */
367 size_t
368 sdb_strbuf_len(sdb_strbuf_t *buf)
369 {
370 if (! buf)
371 return 0;
372 return buf->pos;
373 } /* sdb_strbuf_string */
375 size_t
376 sdb_strbuf_cap(sdb_strbuf_t *buf)
377 {
378 if (! buf)
379 return 0;
380 return buf->size;
381 } /* sdb_strbuf_cap */
383 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */