From 93531803a8dd0d2e357ad1b8fd90e07162d0e660 Mon Sep 17 00:00:00 2001 From: John Marshall Date: Wed, 12 Mar 2014 14:33:12 +0000 Subject: [PATCH 1/2] Add ks_release() to kstring.h Using this function is a more explicit way of transferring ownership than just "foo = str.s"; the latter leaves room for readers to wonder whether a subsequent "free(str.s)" has been forgotten. --- kstring.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/kstring.h b/kstring.h index 0e654cb..2567efc 100644 --- a/kstring.h +++ b/kstring.h @@ -49,9 +49,8 @@ * kstring_t str = { 0, 0, NULL }; * kstring_t str; ...; str.l = str.m = 0; str.s = NULL; * and either ownership of the underlying buffer should be given away before - * the object disappears (i.e., the str.s pointer copied and something else - * responsible for freeing it), or the kstring_t should be destroyed with - * free(str.s); */ + * the object disappears (see ks_release() below) or the kstring_t should be + * destroyed with free(str.s); */ #ifndef KSTRING_T #define KSTRING_T kstring_t typedef struct __kstring_t { @@ -111,6 +110,18 @@ static inline size_t ks_len(kstring_t *s) return s->l; } +// Give ownership of the underlying buffer away to something else (making +// that something else responsible for freeing it), leaving the kstring_t +// empty and ready to be used again, or ready to go out of scope without +// needing free(str.s) to prevent a memory leak. +static inline char *ks_release(kstring_t *s) +{ + char *ss = s->s; + s->l = s->m = 0; + s->s = NULL; + return ss; +} + static inline int kputsn(const char *p, int l, kstring_t *s) { if (s->l + l + 1 >= s->m) { From cbcfcabc8f9b7f0ca14c6dce2ee5644e4e80e5e4 Mon Sep 17 00:00:00 2001 From: John Marshall Date: Tue, 9 Jun 2015 03:29:54 +0100 Subject: [PATCH 2/2] Add kgetline() to kstring.c/.h Similar to BSD's getline() but omits the \n terminator and manages the memory as a kstring. Call with "(kgets_func *) fgets" to read from stdio, or implement an fgets()-style function to read from other streams, e.g., a wrapper around gzgets() that reorders its parameters as per fgets(). --- kstring.c | 20 ++++++++++++++++++ kstring.h | 7 +++++++ test/kstring_test.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/kstring.c b/kstring.c index f029317..253d281 100644 --- a/kstring.c +++ b/kstring.c @@ -105,6 +105,26 @@ int ksplit_core(char *s, int delimiter, int *_max, int **_offsets) return n; } +int kgetline(kstring_t *s, kgets_func *fgets_fn, void *fp) +{ + size_t l0 = s->l; + + while (s->l == l0 || s->s[s->l-1] != '\n') { + if (s->m - s->l < 200) ks_resize(s, s->m + 200); + if (fgets_fn(s->s + s->l, s->m - s->l, fp) == NULL) break; + s->l += strlen(s->s + s->l); + } + + if (s->l == l0) return EOF; + + if (s->l > l0 && s->s[s->l-1] == '\n') { + s->l--; + if (s->l > l0 && s->s[s->l-1] == '\r') s->l--; + } + s->s[s->l] = '\0'; + return 0; +} + /********************** * Boyer-Moore search * **********************/ diff --git a/kstring.h b/kstring.h index 2567efc..f13fcd9 100644 --- a/kstring.h +++ b/kstring.h @@ -82,6 +82,13 @@ extern "C" { * if sep is not changed. */ char *kstrtok(const char *str, const char *sep, ks_tokaux_t *aux); + /* kgetline() uses the supplied fgets()-like function to read a "\n"- + * or "\r\n"-terminated line from fp. The line read is appended to the + * kstring without its terminator and 0 is returned; EOF is returned at + * EOF or on error (determined by querying fp, as per fgets()). */ + typedef char *kgets_func(char *, int, void *); + int kgetline(kstring_t *s, kgets_func *fgets, void *fp); + #ifdef __cplusplus } #endif diff --git a/test/kstring_test.c b/test/kstring_test.c index 76f9532..9f9b6e6 100644 --- a/test/kstring_test.c +++ b/test/kstring_test.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -37,7 +38,40 @@ void test_kputl(kstring_t *ks, long n) check("kputl()", ks, buf); } -int main() +static char *mem_gets(char *buf, int buflen, void *vtextp) +{ + const char **textp = (const char **) vtextp; + + const char *nl = strchr(*textp, '\n'); + size_t n = nl? nl - *textp + 1 : strlen(*textp); + + if (n == 0) return NULL; + + if (n > buflen-1) n = buflen-1; + memcpy(buf, *textp, n); + buf[n] = '\0'; + *textp += n; + return buf; +} + +void test_kgetline(kstring_t *ks, const char *text, ...) +{ + const char *exp; + va_list arg; + + va_start(arg, text); + while ((exp = va_arg(arg, const char *)) != NULL) { + ks->l = 0; + if (kgetline(ks, mem_gets, &text) != 0) kputs("EOF", ks); + check("kgetline()", ks, exp); + } + va_end(arg); + + ks->l = 0; + if (kgetline(ks, mem_gets, &text) == 0) check("kgetline()", ks, "EOF"); +} + +int main(int argc, char **argv) { kstring_t ks; @@ -65,6 +99,28 @@ int main() test_kputl(&ks, -LONG_MAX); test_kputl(&ks, LONG_MIN); + test_kgetline(&ks, "", NULL); + test_kgetline(&ks, "apple", "apple", NULL); + test_kgetline(&ks, "banana\n", "banana", NULL); + test_kgetline(&ks, "carrot\r\n", "carrot", NULL); + test_kgetline(&ks, "\n", "", NULL); + test_kgetline(&ks, "\n\n", "", "", NULL); + test_kgetline(&ks, "foo\nbar", "foo", "bar", NULL); + test_kgetline(&ks, "foo\nbar\n", "foo", "bar", NULL); + test_kgetline(&ks, + "abcdefghijklmnopqrstuvwxyz0123456789\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n", + "abcdefghijklmnopqrstuvwxyz0123456789", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", NULL); + + if (argc > 1) { + FILE *f = fopen(argv[1], "r"); + if (f) { + for (ks.l = 0; kgetline(&ks, (kgets_func *)fgets, f) == 0; ks.l = 0) + puts(ks.s); + fclose(f); + } + } + free(ks.s); if (nfail > 0) {