Code

utils strbuf: Added 'min_size' to be considered when shrinking the buffer.
[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(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)
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)
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, ...)
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)
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, ...)
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)
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)
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)
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)
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)
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)
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)
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)
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)
377         if (! buf)
378                 return 0;
379         return buf->size;
380 } /* sdb_strbuf_cap */
382 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */