From: Florian Forster Date: Mon, 18 Sep 2017 14:35:51 +0000 (+0200) Subject: strbuf: Library that implements printing to a dynamically allocated buffer. X-Git-Tag: 6.0.0-rc0~148^2~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6320dceaa51ba811fd64e8b1404aa424e8ba2fd1;p=thirdparty%2Fcollectd.git strbuf: Library that implements printing to a dynamically allocated buffer. --- diff --git a/Makefile.am b/Makefile.am index cb8205fcc..21273f7e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -140,7 +140,8 @@ noinst_LTLIBRARIES = \ liblookup.la \ libmetadata.la \ libmount.la \ - liboconfig.la + liboconfig.la \ + libstrbuf.la check_LTLIBRARIES = \ @@ -157,6 +158,7 @@ check_PROGRAMS = \ test_utils_latency \ test_utils_message_parser \ test_utils_mount \ + test_utils_strbuf \ test_utils_subst \ test_utils_time \ test_utils_vl_lookup \ @@ -545,6 +547,14 @@ if BUILD_WITH_LIBKSTAT test_utils_mount_LDADD += -lkstat endif +libstrbuf_la_SOURCES = \ + src/utils/strbuf/strbuf.c \ + src/utils/strbuf/strbuf.h + +test_utils_strbuf_SOURCES = \ + src/utils/strbuf/strbuf_test.c \ + src/testing.h +test_utils_strbuf_LDADD = libstrbuf.la libcollectdclient_la_SOURCES = \ src/libcollectdclient/client.c \ diff --git a/src/testing.h b/src/testing.h index fd7e6c662..8299d8907 100644 --- a/src/testing.h +++ b/src/testing.h @@ -28,6 +28,10 @@ #define TESTING_H 1 #include +#include +#include +#include +#include static int fail_count__; static int check_count__; diff --git a/src/utils/strbuf/strbuf.c b/src/utils/strbuf/strbuf.c new file mode 100644 index 000000000..f61f534ee --- /dev/null +++ b/src/utils/strbuf/strbuf.c @@ -0,0 +1,198 @@ +/** + * collectd - src/utils_strbuf.h + * Copyright (C) 2017 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + */ + +#include "collectd.h" + +#include "utils/strbuf/strbuf.h" + +#include + +static size_t strbuf_avail(strbuf_t *buf) { + if ((buf == NULL) || (buf->size == 0)) { + return 0; + } + + assert(buf->pos < buf->size); + return buf->size - (buf->pos + 1); +} + +static size_t strbuf_pagesize() { + static size_t cached_pagesize; + + if (!cached_pagesize) { + long tmp = sysconf(_SC_PAGESIZE); + if (tmp >= 1) + cached_pagesize = (size_t)tmp; + else + cached_pagesize = 1024; + } + + return cached_pagesize; +} + +/* strbuf_resize resizes an dynamic buffer to ensure that "need" bytes can be + * stored in it. When called with an empty buffer, i.e. buf->size == 0, it will + * allocate just enough memory to store need+1 bytes. Subsequent calls will + * only allocate memory when needed, doubling the allocated memory size each + * time until the page size is reached, then allocating. */ +static int strbuf_resize(strbuf_t *buf, size_t need) { + if (buf->fixed) + return 0; + + if (strbuf_avail(buf) > need) + return 0; + + size_t new_size; + if (buf->size == 0) { + /* New buffers: try to use a reasonable default. */ + new_size = 512; + } else if (buf->size < strbuf_pagesize()) { + /* Small buffers: double the size. */ + new_size = 2 * buf->size; + } else { + /* Large buffers: allocate an additional page. */ + size_t pages = (buf->size + strbuf_pagesize() - 1) / strbuf_pagesize(); + new_size = (pages + 1) * strbuf_pagesize(); + } + + /* Check that the new size is large enough. If not, calculate the exact number + * of bytes needed. */ + if (new_size < (buf->pos + need + 1)) + new_size = buf->pos + need + 1; + + char *new_ptr = realloc(buf->ptr, new_size); + if (new_ptr == NULL) + return ENOMEM; + + buf->ptr = new_ptr; + buf->size = new_size; + + return 0; +} + +strbuf_t *strbuf_create(void) { return calloc(1, sizeof(strbuf_t)); } + +strbuf_t *strbuf_create_static(void *buffer, size_t buffer_size) { + strbuf_t *buf = calloc(1, sizeof(*buf)); + if (buf == NULL) + return NULL; + + *buf = (strbuf_t){ + .ptr = buffer, + .size = buffer_size, + .fixed = 1, + }; + return buf; +} + +void strbuf_destroy(strbuf_t *buf) { + if (buf == NULL) { + return; + } + + if (!buf->fixed) { + free(buf->ptr); + } + free(buf); +} + +void strbuf_reset(strbuf_t *buf) { + if (buf == NULL) { + return; + } + + buf->pos = 0; + if (buf->ptr != NULL) + buf->ptr[buf->pos] = 0; + + if (buf->fixed) { + return; + } + + /* Truncate the buffer to the page size. This is deemed a good compromise + * between freeing memory (after a large buffer has been constructed) and + * performance (avoid unnecessary allocations). */ + if (buf->size > strbuf_pagesize()) { + char *new_ptr = realloc(buf->ptr, strbuf_pagesize()); + if (new_ptr != NULL) + buf->ptr = new_ptr; + } +} + +int strbuf_print(strbuf_t *buf, char const *s) { + if ((buf == NULL) || (s == NULL)) + return EINVAL; + + size_t s_len = strlen(s); + int status = strbuf_resize(buf, s_len); + if (status != 0) + return status; + + size_t bytes = strbuf_avail(buf); + if (bytes == 0) + return ENOSPC; + + if (bytes > s_len) + bytes = s_len; + + memmove(buf->ptr + buf->pos, s, bytes); + buf->pos += bytes; + buf->ptr[buf->pos] = 0; + + return 0; +} + +int strbuf_printf(strbuf_t *buf, char const *format, ...) { + va_list ap; + + va_start(ap, format); + int status = vsnprintf(NULL, 0, format, ap); + va_end(ap); + if (status <= 0) + return status; + + size_t s_len = (size_t)status; + + status = strbuf_resize(buf, s_len); + if (status != 0) + return status; + + size_t bytes = strbuf_avail(buf); + if (bytes == 0) + return ENOSPC; + + if (bytes > s_len) + bytes = s_len; + + va_start(ap, format); + (void)vsnprintf(buf->ptr + buf->pos, bytes + 1, format, ap); + va_end(ap); + + buf->pos += bytes; + buf->ptr[buf->pos] = 0; + + return 0; +} diff --git a/src/utils/strbuf/strbuf.h b/src/utils/strbuf/strbuf.h new file mode 100644 index 000000000..8fa2a8736 --- /dev/null +++ b/src/utils/strbuf/strbuf.h @@ -0,0 +1,66 @@ +/** + * collectd - src/utils/strbuf/strbuf.h + * Copyright (C) 2017 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + */ + +#ifndef UTILS_STRBUF_H +#define UTILS_STRBUF_H 1 + +typedef struct { + char *ptr; + size_t pos; + size_t size; + _Bool fixed; +} strbuf_t; + +#define STRBUF_NEW \ + &(strbuf_t) { .ptr = NULL } +#define STRBUF_NEW_STATIC(b) \ + &(strbuf_t) { .ptr = b, .size = sizeof(b), .fixed = 1 } +#define STRBUF_DELETE(buf) \ + do { \ + if (!buf->fixed) { \ + free(buf->ptr); \ + } \ + *buf = (strbuf_t){.ptr = NULL}; \ + } + +strbuf_t *strbuf_create(void); +strbuf_t *strbuf_create_static(void *buffer, size_t buffer_size); +void strbuf_destroy(strbuf_t *buf); + +/* strbuf_reset empties the buffer. If the buffer is dynamically allocated, it + * will *not* release (all) the allocated memory. */ +void strbuf_reset(strbuf_t *buf); + +/* strbuf_print adds "s" to the buffer. If the size of the buffer is static and + * there is no space available in the buffer, ENOSPC is returned. */ +int strbuf_print(strbuf_t *buf, char const *s); + +/* strbuf_printf adds a string to the buffer using formatting. If the size of + * the buffer is static and there is no space available in the buffer, ENOSPC + * is returned. */ +int strbuf_printf(strbuf_t *buf, char const *format, ...); + +#endif diff --git a/src/utils/strbuf/strbuf_test.c b/src/utils/strbuf/strbuf_test.c new file mode 100644 index 000000000..b35717b33 --- /dev/null +++ b/src/utils/strbuf/strbuf_test.c @@ -0,0 +1,88 @@ +/** + * collectd - src/utils/strbuf/strbuf_test.c + * Copyright (C) 2020 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Florian octo Forster + */ + +#include "testing.h" +#include "utils/strbuf/strbuf.h" + +#include + +DEF_TEST(dynamic) { + strbuf_t *buf; + CHECK_NOT_NULL(buf = strbuf_create()); + + CHECK_ZERO(strbuf_print(buf, "foo")); + EXPECT_EQ_STR("foo", buf->ptr); + + CHECK_ZERO(strbuf_print(buf, "bar")); + EXPECT_EQ_STR("foobar", buf->ptr); + + CHECK_ZERO(strbuf_printf(buf, "%d\n", 9000)); + EXPECT_EQ_STR("foobar9000\n", buf->ptr); + + strbuf_reset(buf); + CHECK_ZERO(strlen(buf->ptr)); + + CHECK_ZERO(strbuf_print(buf, "new content")); + EXPECT_EQ_STR("new content", buf->ptr); + + strbuf_destroy(buf); + return 0; +} + +DEF_TEST(static) { + char mem[8]; + strbuf_t *buf; + CHECK_NOT_NULL(buf = strbuf_create_static(mem, sizeof(mem))); + + CHECK_ZERO(strbuf_print(buf, "foo")); + EXPECT_EQ_STR("foo", buf->ptr); + + CHECK_ZERO(strbuf_print(buf, "bar")); + EXPECT_EQ_STR("foobar", buf->ptr); + + CHECK_ZERO(strbuf_printf(buf, "%d\n", 9000)); + EXPECT_EQ_STR("foobar9", buf->ptr); + + EXPECT_EQ_INT(ENOSPC, strbuf_print(buf, "buffer already filled")); + EXPECT_EQ_STR("foobar9", buf->ptr); + + strbuf_reset(buf); + CHECK_ZERO(strlen(buf->ptr)); + + CHECK_ZERO(strbuf_print(buf, "new content")); + EXPECT_EQ_STR("new con", buf->ptr); + + strbuf_destroy(buf); + return 0; +} + +int main(int argc, char **argv) /* {{{ */ +{ + RUN_TEST(dynamic); + RUN_TEST(static); + + END_TEST; +} /* }}} int main */