util: add a generic stringbuffer with a few helper functions
authorPeter Hutterer <peter.hutterer@who-t.net>
Fri, 11 Oct 2024 05:52:04 +0000 (15:52 +1000)
committerMarge Bot <emma+marge@anholt.net>
Wed, 30 Oct 2024 23:20:42 +0000 (23:20 +0000)
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1067>

meson.build
src/util-stringbuf.h [new file with mode: 0644]
test/test-utils.c

index aac0ea9bef7f4811b5807fa12ff867767b5c7836..65da680639bd987c834a5200be2ef31c28dec7c6 100644 (file)
@@ -264,6 +264,7 @@ util_headers = [
                'util-matrix.h',
                'util-prop-parsers.h',
                'util-ratelimit.h',
+               'util-stringbuf.h',
                'util-strings.h',
                'util-time.h',
 ]
diff --git a/src/util-stringbuf.h b/src/util-stringbuf.h
new file mode 100644 (file)
index 0000000..a11d7f2
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright © 2024 Red Hat, Inc.
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util-macros.h"
+
+struct stringbuf {
+       char *data;
+       size_t len; /* len of log */
+       size_t sz;  /* allocated size */
+};
+
+static inline void
+stringbuf_init(struct stringbuf *b)
+{
+       b->len = 0;
+       b->sz = 64;
+       b->data = calloc(1, b->sz);
+}
+
+static inline bool
+stringbuf_is_empty(struct stringbuf *b)
+{
+       return b->len == 0;
+}
+
+static inline void
+stringbuf_reset(struct stringbuf *b)
+{
+       free(b->data);
+       b->data = NULL;
+       b->sz = 0;
+       b->len = 0;
+
+}
+
+static inline struct stringbuf *
+stringbuf_new(void)
+{
+       struct stringbuf *b = calloc(1, sizeof(*b));
+       stringbuf_init(b);
+       return b;
+}
+
+static inline void
+stringbuf_destroy(struct stringbuf *b)
+{
+       stringbuf_reset(b);
+       free(b);
+}
+
+static inline char *
+stringbuf_steal_destroy(struct stringbuf *b)
+{
+       char *str = b->data;
+       b->data = NULL;
+       stringbuf_destroy(b);
+       return str;
+}
+
+static inline char *
+stringbuf_steal(struct stringbuf *b)
+{
+       char *str = b->data;
+       b->data = NULL;
+       stringbuf_reset(b);
+       return str;
+}
+
+/**
+ * Ensure the stringbuf has space for at least sz bytes
+ */
+static inline int
+stringbuf_ensure_size(struct stringbuf *b, size_t sz)
+{
+       if (b->sz < sz) {
+               char *tmp = realloc(b->data, sz);
+               if (!tmp)
+                       return -errno;
+               b->data = tmp;
+               memset(&b->data[b->len], 0, sz - b->len);
+               b->sz = sz;
+       }
+       return 0;
+}
+
+/**
+ * Ensure the stringbuf has space for at least sz *more* bytes
+ */
+static inline int
+stringbuf_ensure_space(struct stringbuf *b, size_t sz)
+{
+       return stringbuf_ensure_size(b, b->len + sz);
+}
+
+/**
+ * Append the the data from the fd to the string buffer.
+ */
+static inline int
+stringbuf_append_from_fd(struct stringbuf *b, int fd, size_t maxlen)
+{
+       while (1) {
+               size_t inc = maxlen ? maxlen : 1024;
+               do {
+                       int r = stringbuf_ensure_space(b, inc);
+                       if (r < 0)
+                               return r;
+
+                       r = read(fd, &b->data[b->len], min(b->sz - b->len, inc));
+                       if (r <= 0) {
+                               if (r < 0 && errno != EAGAIN)
+                                       return -errno;
+                               return 0;
+                       }
+                       b->len += r;
+               } while (maxlen > 0);
+       }
+
+       return 0;
+}
+
+static inline int
+stringbuf_append_string(struct stringbuf *b, const char *msg)
+{
+       int r = stringbuf_ensure_space(b, strlen(msg) + 1);
+       if (r < 0)
+               return r;
+
+       size_t slen = strlen(msg);
+
+       if (!b->data) /* cannot happen, but let's make scan-build happy */
+               abort();
+
+       memcpy(b->data + b->len, msg, slen);
+       b->len += slen;
+
+       return 0;
+}
index 73a82208396050036f74c8823b6aa244165bafc3..10751fed660d371346bae3b85caf4dbcf1807b53 100644 (file)
@@ -27,6 +27,9 @@
 
 #include <valgrind/valgrind.h>
 
+#include <fcntl.h>
+#include <unistd.h>
+
 #include "util-list.h"
 #include "util-strings.h"
 #include "util-time.h"
@@ -35,6 +38,7 @@
 #include "util-bits.h"
 #include "util-range.h"
 #include "util-ratelimit.h"
+#include "util-stringbuf.h"
 #include "util-matrix.h"
 #include "util-input-event.h"
 
@@ -1721,6 +1725,107 @@ START_TEST(range_test)
 }
 END_TEST
 
+START_TEST(stringbuf_test)
+{
+       struct stringbuf buf;
+       struct stringbuf *b = &buf;
+       int rc;
+
+       stringbuf_init(b);
+       ck_assert_int_eq(b->len, 0u);
+
+       rc = stringbuf_append_string(b, "foo");
+       ck_assert_int_ne(rc, -1);
+       rc = stringbuf_append_string(b, "bar");
+       ck_assert_int_ne(rc, -1);
+       rc = stringbuf_append_string(b, "baz");
+       ck_assert_int_ne(rc, -1);
+       ck_assert_str_eq(b->data, "foobarbaz");
+       ck_assert_int_eq(b->len, strlen("foobarbaz"));
+
+       rc = stringbuf_ensure_space(b, 500);
+       ck_assert_int_ne(rc, -1);
+       ck_assert_int_ge(b->sz, 500u);
+
+       rc = stringbuf_ensure_size(b, 0);
+       ck_assert_int_ne(rc, -1);
+       rc = stringbuf_ensure_size(b, 1024);
+       ck_assert_int_ne(rc, -1);
+       ck_assert_int_ge(b->sz, 1024u);
+
+       char *data = stringbuf_steal(b);
+       ck_assert_str_eq(data, "foobarbaz");
+       ck_assert_int_eq(b->sz, 0u);
+       ck_assert_int_eq(b->len, 0u);
+       ck_assert_ptr_null(b->data);
+       free(data);
+
+       const char *str = "1234567890";
+       rc = stringbuf_append_string(b, str);
+       ck_assert_int_ne(rc, -1);
+       ck_assert_str_eq(b->data, str);
+       ck_assert_int_eq(b->len, 10u);
+       stringbuf_reset(b);
+
+       /* intentional double-reset */
+       stringbuf_reset(b);
+
+       int pipefd[2];
+       rc = pipe2(pipefd, O_CLOEXEC | O_NONBLOCK);
+       ck_assert_int_ne(rc, -1);
+
+       str = "foo bar baz";
+       char *compare = NULL;
+       for (int i = 0; i < 100; i++) {
+               rc = write(pipefd[1], str, strlen(str));
+               ck_assert_int_ne(rc, -1);
+
+               rc = stringbuf_append_from_fd(b, pipefd[0], 64);
+               ck_assert_int_ne(rc, -1);
+
+               char *expected;
+               rc = xasprintf(&expected, "%s%s", compare ? compare : "", str);
+               ck_assert_int_ne(rc, -1);
+               ck_assert_str_eq(b->data, expected);
+
+               free(compare);
+               compare = expected;
+       }
+       free(compare);
+       close(pipefd[0]);
+       close(pipefd[1]);
+       stringbuf_reset(b);
+
+       rc = pipe2(pipefd, O_CLOEXEC | O_NONBLOCK);
+               ck_assert_int_ne(rc, -1);
+       ck_assert_int_ne(rc, -1);
+
+       const size_t stride = 256;
+       const size_t maxsize = 4096;
+
+       for (size_t i = 0; i < maxsize; i += stride) {
+               char buf[stride];
+               memset(buf, i/stride, sizeof(buf));
+               rc = write(pipefd[1], buf, sizeof(buf));
+               ck_assert_int_ne(rc, -1);
+       }
+
+       stringbuf_append_from_fd(b, pipefd[0], 0);
+       ck_assert_int_eq(b->len, maxsize);
+       ck_assert_int_ge(b->sz, maxsize);
+
+       for (size_t i = 0; i < maxsize; i+= stride) {
+               char buf[stride];
+               memset(buf, i/stride, sizeof(buf));
+               ck_assert_int_eq(memcmp(buf, b->data + i, sizeof(buf)), 0);
+       }
+
+       close(pipefd[0]);
+       close(pipefd[1]);
+       stringbuf_reset(b);
+}
+END_TEST
+
 static Suite *
 litest_utils_suite(void)
 {
@@ -1778,6 +1883,7 @@ litest_utils_suite(void)
        tcase_add_test(tc, absinfo_normalize_value_test);
 
        tcase_add_test(tc, range_test);
+       tcase_add_test(tc, stringbuf_test);
 
        suite_add_tcase(s, tc);