]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
strbuf: Library that implements printing to a dynamically allocated buffer.
authorFlorian Forster <octo@collectd.org>
Mon, 18 Sep 2017 14:35:51 +0000 (16:35 +0200)
committerFlorian Forster <octo@google.com>
Tue, 14 Jul 2020 17:18:50 +0000 (19:18 +0200)
Makefile.am
src/testing.h
src/utils/strbuf/strbuf.c [new file with mode: 0644]
src/utils/strbuf/strbuf.h [new file with mode: 0644]
src/utils/strbuf/strbuf_test.c [new file with mode: 0644]

index cb8205fccb001184da9ce71b179ba1e144450810..21273f7e551d45edb4129e0b27acf74cd64cc71c 100644 (file)
@@ -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 \
index fd7e6c6623516a4fba991cdf4c727451448c7323..8299d8907bf8bc30297258edab66a336e63e2e4a 100644 (file)
 #define TESTING_H 1
 
 #include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
 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 (file)
index 0000000..f61f534
--- /dev/null
@@ -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 <octo at collectd.org>
+ */
+
+#include "collectd.h"
+
+#include "utils/strbuf/strbuf.h"
+
+#include <stdarg.h>
+
+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 (file)
index 0000000..8fa2a87
--- /dev/null
@@ -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 <octo at collectd.org>
+ */
+
+#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 (file)
index 0000000..b35717b
--- /dev/null
@@ -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 <octo at collectd.org>
+ */
+
+#include "testing.h"
+#include "utils/strbuf/strbuf.h"
+
+#include <errno.h>
+
+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 */