Code

0474677296c31f9c1fcce3ded0a0207fde621c8a
[sysdb.git] / src / utils / strbuf.c
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)
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)
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, ...)
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)
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, ...)
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)
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)
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)
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)
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)
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)
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)
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)
362         if (! strbuf)
363                 return 0;
364         return strbuf->pos;
365 } /* sdb_strbuf_string */
367 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */