--- /dev/null
+/*
+ * 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;
+}
#include <valgrind/valgrind.h>
+#include <fcntl.h>
+#include <unistd.h>
+
#include "util-list.h"
#include "util-strings.h"
#include "util-time.h"
#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"
}
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)
{
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);