Code

plugin: Make sdb_plugin_info_t public.
[sysdb.git] / src / utils / strbuf.c
index bc24fef5e54c19781c5cd4bef7c3a1036e897945..7d8bc5527f6138f7a1c8ad6cf6730c6445c3460b 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <string.h>
+
+#include <unistd.h>
+
+/* free memory if most of the buffer is unused */
+#define CHECK_SHRINK(strbuf) \
+       do { \
+               if ((strbuf)->pos < (strbuf)->size / 3) \
+                       /* don't free all memory to avoid churn */ \
+                       strbuf_resize((strbuf), 2 * (strbuf)->pos); \
+       } while (0)
 
 /*
  * private data structures
@@ -48,18 +59,22 @@ struct sdb_strbuf {
  */
 
 static int
-strbuf_resize(sdb_strbuf_t *strbuf)
+strbuf_resize(sdb_strbuf_t *strbuf, size_t new_size)
 {
        char *tmp;
 
-       assert(strbuf->size);
+       if (new_size <= strbuf->pos)
+               return -1;
 
-       tmp = realloc(strbuf->string, 2 * strbuf->size);
+       tmp = realloc(strbuf->string, new_size);
        if (! tmp)
                return -1;
 
-       strbuf->string = tmp;
-       strbuf->size *= 2;
+       if (new_size)
+               strbuf->string = tmp;
+       else
+               strbuf->string = NULL;
+       strbuf->size = new_size;
        return 0;
 } /* strbuf_resize */
 
@@ -72,20 +87,21 @@ sdb_strbuf_create(size_t size)
 {
        sdb_strbuf_t *strbuf;
 
-       if (! size)
-               return NULL;
-
        strbuf = calloc(1, sizeof(*strbuf));
        if (! strbuf)
                return NULL;
 
-       strbuf->string = malloc(size);
-       if (! strbuf->string) {
-               free(strbuf);
-               return NULL;
+       strbuf->string = NULL;
+       if (size) {
+               strbuf->string = malloc(size);
+               if (! strbuf->string) {
+                       free(strbuf);
+                       return NULL;
+               }
+
+               strbuf->string[0] = '\0';
        }
 
-       strbuf->string[0] = '\0';
        strbuf->size = size;
        strbuf->pos  = 0;
 
@@ -98,7 +114,8 @@ sdb_strbuf_destroy(sdb_strbuf_t *strbuf)
        if (! strbuf)
                return;
 
-       free(strbuf->string);
+       if (strbuf->string)
+               free(strbuf->string);
        free(strbuf);
 } /* sdb_strbuf_destroy */
 
@@ -111,12 +128,21 @@ sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
        if ((! strbuf) || (! fmt))
                return -1;
 
-       assert(strbuf->string[strbuf->pos] == '\0');
+       assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0'));
 
-       if (strbuf->pos >= strbuf->size)
-               if (strbuf_resize(strbuf))
+       if (! strbuf->size) {
+               /* use some arbitrary but somewhat reasonable default */
+               if (strbuf_resize(strbuf, 64))
+                       return -1;
+       }
+       /* make sure to reserve space for the nul-byte */
+       else if (strbuf->pos >= strbuf->size - 1)
+               if (strbuf_resize(strbuf, 2 * strbuf->size))
                        return -1;
 
+       assert(strbuf->size && strbuf->string);
+       assert(strbuf->pos < strbuf->size);
+
        /* 'ap' is invalid after calling vsnprintf; thus copy before using it */
        va_copy(aq, ap);
        status = vsnprintf(strbuf->string + strbuf->pos,
@@ -127,8 +153,12 @@ sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
                return status;
        }
 
+       /* 'status' does not include nul-byte */
        if ((size_t)status >= strbuf->size - strbuf->pos) {
-               strbuf_resize(strbuf);
+               if (strbuf_resize(strbuf, strbuf->pos + (size_t)status + 1)) {
+                       va_end(aq);
+                       return -1;
+               }
 
                /* reset string and try again */
                strbuf->string[strbuf->pos] = '\0';
@@ -138,6 +168,11 @@ sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
                strbuf->pos += (size_t)status;
 
        va_end(aq);
+
+       /* even though this function always appends to the existing buffer, the
+        * size might have previously been reset */
+       CHECK_SHRINK(strbuf);
+
        return (ssize_t)status;
 } /* sdb_strbuf_vappend */
 
@@ -160,8 +195,10 @@ sdb_strbuf_vsprintf(sdb_strbuf_t *strbuf, const char *fmt, va_list ap)
        if (! strbuf)
                return -1;
 
-       strbuf->string[0] = '\0';
-       strbuf->pos = 0;
+       if (strbuf->size) {
+               strbuf->string[0] = '\0';
+               strbuf->pos = 0;
+       }
 
        return sdb_strbuf_vappend(strbuf, fmt, ap);
 } /* sdb_strbuf_vsprintf */
@@ -179,6 +216,72 @@ sdb_strbuf_sprintf(sdb_strbuf_t *strbuf, const char *fmt, ...)
        return status;
 } /* sdb_strbuf_sprintf */
 
+ssize_t
+sdb_strbuf_memappend(sdb_strbuf_t *strbuf, const void *data, size_t n)
+{
+       if ((! strbuf) || (! data))
+               return -1;
+
+       assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0'));
+
+       if (strbuf->pos + n + 1 >= strbuf->size) {
+               size_t newsize = strbuf->size * 2;
+
+               if (! newsize)
+                       newsize = 64;
+               while (strbuf->pos + n + 1 >= newsize)
+                       newsize *= 2;
+
+               if (strbuf_resize(strbuf, newsize))
+                       return -1;
+       }
+
+       assert(strbuf->size && strbuf->string);
+       assert(strbuf->pos < strbuf->size);
+
+       memcpy((void *)(strbuf->string + strbuf->pos), data, n);
+       strbuf->pos += n;
+       strbuf->string[strbuf->pos] = '\0';
+
+       /* even though this function always appends to the existing buffer, the
+        * size might have previously been reset */
+       CHECK_SHRINK(strbuf);
+
+       return (ssize_t)n;
+} /* sdb_strbuf_memappend */
+
+ssize_t
+sdb_strbuf_memcpy(sdb_strbuf_t *strbuf, const void *data, size_t n)
+{
+       if ((! strbuf) || (! data))
+               return -1;
+
+       if (strbuf->size) {
+               strbuf->string[0] = '\0';
+               strbuf->pos = 0;
+       }
+
+       return sdb_strbuf_memappend(strbuf, data, n);
+} /* sdb_strbuf_memcpy */
+
+ssize_t
+sdb_strbuf_read(sdb_strbuf_t *strbuf, int fd, size_t n)
+{
+       ssize_t ret;
+
+       if (! strbuf)
+               return -1;
+
+       if (strbuf->pos + n + 1 >= strbuf->size)
+               if (strbuf_resize(strbuf, strbuf->pos + n + 1))
+                       return -1;
+
+       ret = read(fd, strbuf->string + strbuf->pos, n);
+       if (ret > 0)
+               strbuf->pos += (size_t)ret;
+       return ret;
+} /* sdb_strbuf_read */
+
 ssize_t
 sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
 {
@@ -187,6 +290,9 @@ sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
        if (! strbuf)
                return -1;
 
+       assert((!strbuf->size) || (strbuf->pos < strbuf->size));
+       assert(strbuf->pos <= strbuf->size);
+
        while ((strbuf->pos > 0)
                        && (strbuf->string[strbuf->pos - 1] == '\n')) {
                --strbuf->pos;
@@ -197,11 +303,56 @@ sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
        return ret;
 } /* sdb_strbuf_chomp */
 
+void
+sdb_strbuf_skip(sdb_strbuf_t *strbuf, size_t offset, size_t n)
+{
+       char *start;
+       size_t len;
+
+       if ((! strbuf) || (! n))
+               return;
+
+       if (offset >= strbuf->pos)
+               return;
+
+       len = strbuf->pos - offset;
+
+       if (n >= len) {
+               strbuf->string[offset] = '\0';
+               strbuf->pos = offset;
+               return;
+       }
+
+       assert(offset + n < strbuf->pos);
+       assert(offset < strbuf->pos);
+
+       start = strbuf->string + offset;
+       memmove(start, start + n, len - n);
+       strbuf->pos -= n;
+       strbuf->string[strbuf->pos] = '\0';
+
+       /* don't resize now but wait for the next write to avoid churn */
+} /* sdb_strbuf_skip */
+
+void
+sdb_strbuf_clear(sdb_strbuf_t *strbuf)
+{
+       if ((! strbuf) || (! strbuf->size))
+               return;
+
+       strbuf->string[0] = '\0';
+       strbuf->pos = 0;
+
+       /* don't resize now but wait for the next write to avoid churn */
+} /* sdb_strbuf_clear */
+
 const char *
 sdb_strbuf_string(sdb_strbuf_t *strbuf)
 {
        if (! strbuf)
                return NULL;
+       if (! strbuf->size)
+               return "";
        return strbuf->string;
 } /* sdb_strbuf_string */
 
@@ -213,5 +364,13 @@ sdb_strbuf_len(sdb_strbuf_t *strbuf)
        return strbuf->pos;
 } /* sdb_strbuf_string */
 
+size_t
+sdb_strbuf_cap(sdb_strbuf_t *strbuf)
+{
+       if (! strbuf)
+               return 0;
+       return strbuf->size;
+} /* sdb_strbuf_cap */
+
 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */