diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 5d0502f..a5e4b65 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -5,6 +5,7 @@ cc_library( name = "lib", srcs = [ "crc32.c", + "strbuf.c", "toolbox.c", "util.c", ], @@ -13,6 +14,7 @@ cc_library( "defs.h", "endian.h", "error.h", + "strbuf.h", "util.h", ], copts = COPTS, @@ -59,3 +61,15 @@ cc_test( ":lib", ], ) + +cc_test( + name = "strbuf_test", + size = "small", + srcs = [ + "strbuf_test.c", + ], + copts = COPTS, + deps = [ + ":lib", + ], +) diff --git a/lib/strbuf.c b/lib/strbuf.c new file mode 100644 index 0000000..98db8d0 --- /dev/null +++ b/lib/strbuf.c @@ -0,0 +1,41 @@ +// Copyright 2022 Dietrich Epp. +// This file is part of SyncFiles. SyncFiles is licensed under the terms of the +// Mozilla Public License, version 2.0. See LICENSE.txt for details. +#include "lib/strbuf.h" + +#include +#include + +bool StrbufAlloc(struct Strbuf *restrict b, size_t n) +{ + size_t req = n + 1; + if (req > b->alloc) { + size_t nalloc = (b->alloc + 16) * 3 / 2; + if (req > nalloc) { + nalloc = req; + } + char *nbuf = realloc(b->buf, nalloc); + if (nbuf == NULL) { + return false; + } + b->buf = nbuf; + b->alloc = nalloc; + } + return true; +} + +bool StrbufReserve(struct Strbuf *restrict b, size_t n) +{ + return StrbufAlloc(b, b->len + n); +} + +bool StrbufAppendMem(struct Strbuf *restrict b, const char *s, size_t n) +{ + if (!StrbufReserve(b, n)) { + return false; + } + memcpy(b->buf + b->len, s, n); + b->buf[b->len + n] = '\0'; + b->len += n; + return true; +} diff --git a/lib/strbuf.h b/lib/strbuf.h new file mode 100644 index 0000000..78bfc14 --- /dev/null +++ b/lib/strbuf.h @@ -0,0 +1,29 @@ +// Copyright 2022 Dietrich Epp. +// This file is part of SyncFiles. SyncFiles is licensed under the terms of the +// Mozilla Public License, version 2.0. See LICENSE.txt for details. +#ifndef LIB_STRBUF_H +#define LIB_STRBUF_H +#include + +#include "lib/defs.h" + +// A strbuf is a buffer for building a string. The structure can be +// zero-initialized. +struct Strbuf { + char *buf; // String data. May be NULL if strbuf is empty. + size_t len; // String length, not including nul terminator. + size_t alloc; // Data reserved for string, not including terminator. +}; + +// Reserve enough space in the strbuf to store a string which is n bytes long. +// Also reserve enough space for the nul byte after. Return true on success. +bool StrbufAlloc(struct Strbuf *restrict b, size_t n); + +// Reserve enough space in the strbuf to append n bytes to the buffer. Also +// reserve enough space for the nul byte after. Return true on success. +bool StrbufReserve(struct Strbuf *restrict b, size_t n); + +// Append the given data to the buffer. Return true on success. +bool StrbufAppendMem(struct Strbuf *restrict b, const char *s, size_t n); + +#endif diff --git a/lib/strbuf_test.c b/lib/strbuf_test.c new file mode 100644 index 0000000..d6a0f2d --- /dev/null +++ b/lib/strbuf_test.c @@ -0,0 +1,45 @@ +// Copyright 2022 Dietrich Epp. +// This file is part of SyncFiles. SyncFiles is licensed under the terms of the +// Mozilla Public License, version 2.0. See LICENSE.txt for details. +#include "lib/strbuf.h" + +#include +#include +#include + +struct Segment { + const char *buf; + size_t size; +}; + +#define S "0123456789" +#define S8 S S S S S S S S + +static const struct Segment kSegments[] = { + {"abc", 3}, + {S8, 80}, + {"def", 3}, + {0, 0}, +}; + +static const char kOut[] = "abc" S8 "def"; + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + struct Strbuf s = {0, 0, 0}; + for (int i = 0; kSegments[i].buf != NULL; i++) { + bool r = StrbufAppendMem(&s, kSegments[i].buf, kSegments[i].size); + if (!r) { + fputs("StrbufAppendMem returned false\n", stderr); + exit(1); + } + } + if (s.len != sizeof(kOut) - 1 || memcmp(s.buf, kOut, sizeof(kOut)) != 0) { + fputs("Invalid result\n", stderr); + exit(1); + } + free(s.buf); + return 0; +}