X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Futils%2Fstrbuf.c;h=7d8bc5527f6138f7a1c8ad6cf6730c6445c3460b;hb=4a307afc0bddeba03e26d2d6d284b3f96f80a931;hp=df3ee4b236d7f41b716cec4ca6edb1f090086286;hpb=9ff971eae5d311fd8066e13903589c7deb476934;p=sysdb.git diff --git a/src/utils/strbuf.c b/src/utils/strbuf.c index df3ee4b..7d8bc55 100644 --- a/src/utils/strbuf.c +++ b/src/utils/strbuf.c @@ -32,6 +32,17 @@ #include #include #include +#include + +#include + +/* 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,39 +114,65 @@ sdb_strbuf_destroy(sdb_strbuf_t *strbuf) if (! strbuf) return; - free(strbuf->string); + if (strbuf->string) + free(strbuf->string); free(strbuf); } /* sdb_strbuf_destroy */ ssize_t sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap) { + va_list aq; int status; 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, strbuf->size - strbuf->pos, fmt, ap); - if (status < 0) + if (status < 0) { + va_end(aq); 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'; - return sdb_strbuf_vappend(strbuf, fmt, ap); + status = (int)sdb_strbuf_vappend(strbuf, fmt, aq); } + else + 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); - strbuf->pos += (size_t)status; return (ssize_t)status; } /* sdb_strbuf_vappend */ @@ -153,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 */ @@ -172,11 +216,143 @@ 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) +{ + ssize_t ret = 0; + + 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; + strbuf->string[strbuf->pos] = '\0'; + ++ret; + } + + 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 */ @@ -188,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 : */