From: Martin Wilck <[email protected]>

Add an API for string buffers that grow in size as text is added.
This API will be useful in several places of the multipath-tools code
base. Add unit tests for these helpers, too.

Signed-off-by: Martin Wilck <[email protected]>
---
 libmultipath/Makefile |   2 +-
 libmultipath/strbuf.c | 207 +++++++++++++++++++++
 libmultipath/strbuf.h | 168 +++++++++++++++++
 tests/Makefile        |   3 +-
 tests/strbuf.c        | 412 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 790 insertions(+), 2 deletions(-)
 create mode 100644 libmultipath/strbuf.c
 create mode 100644 libmultipath/strbuf.h
 create mode 100644 tests/strbuf.c

diff --git a/libmultipath/Makefile b/libmultipath/Makefile
index e7254f3..7f3921c 100644
--- a/libmultipath/Makefile
+++ b/libmultipath/Makefile
@@ -53,7 +53,7 @@ OBJS = memory.o parser.o vector.o devmapper.o callout.o \
        log.o configure.o structs_vec.o sysfs.o prio.o checkers.o \
        lock.o file.o wwids.o prioritizers/alua_rtpg.o prkey.o \
        io_err_stat.o dm-generic.o generic.o foreign.o nvme-lib.o \
-       libsg.o valid.o
+       libsg.o valid.o strbuf.o
 
 all:   $(DEVLIB)
 
diff --git a/libmultipath/strbuf.c b/libmultipath/strbuf.c
new file mode 100644
index 0000000..a24a57d
--- /dev/null
+++ b/libmultipath/strbuf.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2021 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+#include <inttypes.h>
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <assert.h>
+#include "strbuf.h"
+
+static const char empty_str[] = "";
+
+const char *get_strbuf_str(const struct strbuf *buf)
+{
+       return buf->buf ? buf->buf : empty_str;
+}
+
+char *steal_strbuf_str(struct strbuf *buf)
+{
+       char *p = buf->buf;
+
+       buf->buf = NULL;
+       buf->size = buf->offs = 0;
+       return p;
+}
+
+size_t get_strbuf_len(const struct strbuf *buf)
+{
+       return buf->offs;
+}
+
+static bool strbuf_is_sane(const struct strbuf *buf)
+{
+       return buf && ((!buf->buf && !buf->size && !buf->offs) ||
+                      (buf->buf && buf->size && buf->size > buf->offs));
+}
+
+void reset_strbuf(struct strbuf *buf)
+{
+       free(buf->buf);
+       buf->buf = NULL;
+       buf->size = buf->offs = 0;
+}
+
+void free_strbuf(struct strbuf *buf)
+{
+       if (!buf)
+               return;
+       reset_strbuf(buf);
+       free(buf);
+}
+
+struct strbuf *new_strbuf(void)
+{
+       return calloc(1, sizeof(struct strbuf));
+}
+
+int truncate_strbuf(struct strbuf *buf, size_t offs)
+{
+       if (!buf->buf)
+               return -EFAULT;
+       if (offs > buf->offs)
+               return -ERANGE;
+
+       buf->offs = offs;
+       buf->buf[offs] = '\0';
+       return 0;
+}
+
+#define BUF_CHUNK 64
+
+static int expand_strbuf(struct strbuf *buf, int addsz)
+{
+       size_t add;
+       char *tmp;
+
+       assert(strbuf_is_sane(buf));
+       if (addsz < 0)
+               return -EINVAL;
+       if (buf->size - buf->offs >= (size_t)addsz + 1)
+               return 0;
+
+       add = ((addsz - (buf->size - buf->offs)) / BUF_CHUNK + 1)
+               * BUF_CHUNK;
+
+       if (buf->size >= SIZE_MAX - add) {
+               add = SIZE_MAX - buf->size;
+               if (add < (size_t)addsz + 1)
+                       return -EOVERFLOW;
+       }
+
+       tmp = realloc(buf->buf, buf->size + add);
+       if (!tmp)
+               return -ENOMEM;
+
+       buf->buf = tmp;
+       buf->size += add;
+       buf->buf[buf->offs] = '\0';
+
+       return 0;
+}
+
+int __append_strbuf_str(struct strbuf *buf, const char *str, int slen)
+{
+       int ret;
+
+       if ((ret = expand_strbuf(buf, slen)) < 0)
+               return ret;
+
+       memcpy(buf->buf + buf->offs, str, slen);
+       buf->offs += slen;
+       buf->buf[buf->offs] = '\0';
+
+       return slen;
+}
+
+int append_strbuf_str(struct strbuf *buf, const char *str)
+{
+       size_t slen;
+
+       if (!str)
+               return -EINVAL;
+
+       slen = strlen(str);
+       if (slen > INT_MAX)
+               return -ERANGE;
+
+       return __append_strbuf_str(buf, str, slen);
+}
+
+int fill_strbuf(struct strbuf *buf, char c, int slen)
+{
+       int ret;
+
+       if ((ret = expand_strbuf(buf, slen)) < 0)
+               return ret;
+
+       memset(buf->buf + buf->offs, c, slen);
+       buf->offs += slen;
+       buf->buf[buf->offs] = '\0';
+
+       return slen;
+}
+
+int append_strbuf_quoted(struct strbuf *buff, const char *ptr)
+{
+       char *quoted, *q;
+       const char *p;
+       unsigned n_quotes, i;
+       size_t qlen;
+       int ret;
+
+       if (!ptr)
+               return -EINVAL;
+
+       for (n_quotes = 0, p = strchr(ptr, '"'); p; p = strchr(++p, '"'))
+               n_quotes++;
+
+       /* leading + trailing quote, 1 extra quote for every quote in ptr */
+       qlen = strlen(ptr) + 2 + n_quotes;
+       if (qlen > INT_MAX)
+               return -ERANGE;
+       if ((ret = expand_strbuf(buff, qlen)) < 0)
+               return ret;
+
+       quoted = &(buff->buf[buff->offs]);
+       *quoted++ = '"';
+       for (p = ptr, q = quoted, i = 0; i < n_quotes; i++) {
+               char *q1 = memccpy(q, p, '"', qlen - 2 - (q - quoted));
+
+               assert(q1 != NULL);
+               p += q1 - q;
+               *q1++ = '"';
+               q = q1;
+       }
+       q = mempcpy(q, p, qlen - 2 - (q - quoted));
+       *q++ = '"';
+       *q = '\0';
+       ret = q - &(buff->buf[buff->offs]);
+       buff->offs += ret;
+       return ret;
+}
+
+__attribute__((format(printf, 2, 3)))
+int print_strbuf(struct strbuf *buf, const char *fmt, ...)
+{
+       va_list ap;
+       int ret;
+       char *tail;
+
+       va_start(ap, fmt);
+       ret = vasprintf(&tail, fmt, ap);
+       va_end(ap);
+
+       if (ret < 0)
+               return -ENOMEM;
+
+       ret = __append_strbuf_str(buf, tail, ret);
+
+       free(tail);
+       return ret;
+}
diff --git a/libmultipath/strbuf.h b/libmultipath/strbuf.h
new file mode 100644
index 0000000..5903572
--- /dev/null
+++ b/libmultipath/strbuf.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2021 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+#ifndef _STRBUF_H
+#define _STRBUF_H
+#include <errno.h>
+#include <string.h>
+
+struct strbuf {
+       char *buf;
+       size_t size;
+       size_t offs;
+};
+
+/**
+ * reset_strbuf(): prepare strbuf for new content
+ * @param strbuf: string buffer to reset
+ *
+ * Frees internal buffer and resets size and offset to 0.
+ * Can be used to cleanup a struct strbuf on stack.
+ */
+void reset_strbuf(struct strbuf *buf);
+
+/**
+ * free_strbuf(): free resources
+ * @param strbuf: string buffer to discard
+ *
+ * Frees all memory occupied by a struct strbuf.
+ */
+void free_strbuf(struct strbuf *buf);
+
+/**
+ * macro: STRBUF_INIT
+ *
+ * Use this to initialize a local struct strbuf on the stack,
+ * or in a global/static variable.
+ */
+#define STRBUF_INIT { .buf = NULL, }
+
+/**
+ * macro: STRBUF_ON_STACK
+ *
+ * Define and initialize a local struct @strbuf to be cleaned up when
+ * the current scope is left
+ */
+#define STRBUF_ON_STACK(__x)                                           \
+       struct strbuf __attribute__((cleanup(reset_strbuf))) (__x) = 
STRBUF_INIT;
+
+/**
+ * new_strbuf(): allocate a struct strbuf on the heap
+ *
+ * @returns: pointer to allocated struct, or NULL in case of error.
+ */
+struct strbuf *new_strbuf(void);
+
+/**
+ * get_strbuf_str(): retrieve string from strbuf
+ * @param buf: a struct strbuf
+ * @returns: pointer to the string written to the strbuf so far.
+ *
+ * If @strbuf was never written to, the function returns a zero-
+ * length string. The return value of this function must not be
+ * free()d.
+ */
+const char *get_strbuf_str(const struct strbuf *buf);
+
+/**
+ * steal_strbuf_str(): retrieve string from strbuf and reset
+ * @param buf: a struct strbuf
+ * @returns: pointer to the string written to @strbuf, or NULL
+ *
+ * After calling this function, the @strbuf is empty as if freshly
+ * initialized. The caller is responsible to free() the returned pointer.
+ * If @strbuf was never written to (not even an empty string was appended),
+ * the function returns NULL.
+ */
+char *steal_strbuf_str(struct strbuf *buf);
+
+/**
+ * get_strbuf_len(): retrieve string length from strbuf
+ * @param buf: a struct strbuf
+ * @returns: the length of the string written to @strbuf so far.
+ */
+size_t get_strbuf_len(const struct strbuf *buf);
+
+/**
+ * truncate_strbuf(): shorten the buffer
+ * @param buf: struct strbuf to truncate
+ * @param offs: new buffer position / offset
+ * @returns: 0 on success, negative error code otherwise.
+ *
+ * If @strbuf is freshly allocated/reset (never written to), -EFAULT
+ * is returned. if @offs must be higher than the current offset as returned
+ * by get_strbuf_len(), -ERANGE is returned. The allocated size of the @strbuf
+ * remains unchanged.
+ */
+int truncate_strbuf(struct strbuf *buf, size_t offs);
+
+/**
+ * __append_strbuf_str(): append string of known length
+ * @param buf: the struct strbuf to write to
+ * @param str: the string to append, not necessarily 0-terminated
+ * @param slen: max number of characters to append, must be non-negative
+ * @returns: @slen = number of appended characters if successful (excluding
+ * terminating '\0'); negative error code otherwise.
+ *
+ * Notes: a 0-byte is always appended to the output buffer after @slen 
characters.
+ * 0-bytes possibly contained in the first @slen characters are copied into
+ * the output. If the function returns an error, @strbuf is unchanged.
+ */
+int __append_strbuf_str(struct strbuf *buf, const char *str, int slen);
+
+/**
+ * append_strbuf_str(): append string
+ * @param buf: the struct strbuf to write to
+ * @param str: the string to append, 0-terminated
+ * @returns: number of appended characters if successful (excluding
+ * terminating '\0'); negative error code otherwise
+ *
+ * Appends the given 0-terminated string to @strbuf, expanding @strbuf's size
+ * as necessary. If the function returns an error, @strbuf is unchanged.
+ */
+int append_strbuf_str(struct strbuf *buf, const char *str);
+
+/**
+ * fill_strbuf_str(): pad strbuf with a character
+ * @param buf: the struct strbuf to write to
+ * @param c: the character used for filling
+ * @param slen: max number of characters to append, must be non-negative
+ * @returns: number of appended characters if successful (excluding
+ * terminating '\0'); negative error code otherwise
+ *
+ * Appends the given character @slen times to @strbuf, expanding @strbuf's size
+ * as necessary. If the function returns an error, @strbuf is unchanged.
+ */
+int fill_strbuf(struct strbuf *buf, char c, int slen);
+
+/**
+ * append_strbuf_quoted(): append string in double quotes, escaping quotes in 
string
+ * @param buf: the struct strbuf to write to
+ * @param str: the string to append, 0-terminated
+ * @returns: number of appended characters if successful (excluding
+ * terminating '\0'); negative error code otherwise
+ *
+ * Appends the given string to @strbuf, with leading and trailing double
+ * quotes (") added, expanding @strbuf's size as necessary. Any double quote
+ * characters (") in the string are transformed to double double quotes ("").
+ * If the function returns an error, @strbuf is unchanged.
+ */
+int append_strbuf_quoted(struct strbuf *buf, const char *str);
+
+/**
+ * print_strbuf(): print to strbuf, formatted
+ * @param buf: the struct strbuf to print to
+ * @param fmt: printf()-like format string
+ * @returns: number of appended characters if successful, (excluding
+ * terminating '\0'); negative error code otherwise
+ *
+ * Appends the the arguments following @fmt, formatted as in printf(), to
+ * @strbuf, expanding @strbuf's size as necessary. The function makes sure that
+ * the output @strbuf is always 0-terminated.
+ * If the function returns an error, @strbuf is unchanged.
+ */
+__attribute__((format(printf, 2, 3)))
+int print_strbuf(struct strbuf *buf, const char *fmt, ...);
+
+#endif
diff --git a/tests/Makefile b/tests/Makefile
index e70c8ed..8cbc4b7 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -13,7 +13,7 @@ CFLAGS += $(BIN_CFLAGS) -I$(multipathdir) -I$(mpathcmddir) \
 LIBDEPS += -L. -L$(mpathcmddir) -lmultipath -lmpathcmd -lcmocka
 
 TESTS := uevent parser util dmevents hwtable blacklist unaligned vpd pgpolicy \
-        alias directio valid devt mpathvalid
+        alias directio valid devt mpathvalid strbuf
 HELPERS := test-lib.o test-log.o
 
 .SILENT: $(TESTS:%=%.o)
@@ -63,6 +63,7 @@ mpathvalid-test_OBJDEPS := ../libmpathvalid/mpath_valid.o
 ifneq ($(DIO_TEST_DEV),)
 directio-test_LIBDEPS := -laio
 endif
+strbuf-test_OBJDEPS := ../libmultipath/strbuf.o
 
 %.o: %.c
        $(CC) $(CFLAGS) $($*-test_FLAGS) -c -o $@ $<
diff --git a/tests/strbuf.c b/tests/strbuf.c
new file mode 100644
index 0000000..43a477d
--- /dev/null
+++ b/tests/strbuf.c
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2021 SUSE LLC
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <cmocka.h>
+#include <errno.h>
+#include "strbuf.h"
+#include "debug.h"
+#include "globals.c"
+
+void *__real_realloc(void *ptr, size_t size);
+
+static bool mock_realloc = false;
+void *__wrap_realloc(void *ptr, size_t size)
+{
+       void *p;
+       if (!mock_realloc)
+               return __real_realloc(ptr, size);
+
+       p = mock_ptr_type(void *);
+       condlog(4, "%s: %p, %zu -> %p", __func__, ptr, size, p);
+       return p;
+}
+
+static void test_strbuf_00(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       char *p;
+
+       assert_ptr_equal(buf.buf, NULL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+       p = steal_strbuf_str(&buf);
+       assert_ptr_equal(p, NULL);
+
+       assert_ptr_equal(buf.buf, NULL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       assert_int_equal(append_strbuf_str(&buf, "moin"), 4);
+       assert_int_equal(get_strbuf_len(&buf), 4);
+       assert_in_range(buf.size, 5, SIZE_MAX);
+       assert_string_equal(get_strbuf_str(&buf), "moin");
+       p = steal_strbuf_str(&buf);
+       assert_string_equal(p, "moin");
+       free(p);
+
+       assert_ptr_equal(buf.buf, NULL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       assert_int_equal(append_strbuf_str(&buf, NULL), -EINVAL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       assert_int_equal(append_strbuf_str(&buf, ""), 0);
+       /* appending a 0-length string allocates memory */
+       assert_in_range(buf.size, 1, SIZE_MAX);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+       p = steal_strbuf_str(&buf);
+       assert_string_equal(p, "");
+       free(p);
+
+       assert_int_equal(__append_strbuf_str(&buf, "x", 0), 0);
+       /* appending a 0-length string allocates memory */
+       assert_in_range(buf.size, 1, SIZE_MAX);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+}
+
+static void test_strbuf_alloc_err(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       size_t sz, ofs;
+       int rc;
+
+       mock_realloc = true;
+       will_return(__wrap_realloc, NULL);
+       assert_int_equal(append_strbuf_str(&buf, "moin"), -ENOMEM);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       mock_realloc = false;
+       assert_int_equal(append_strbuf_str(&buf, "moin"), 4);
+       sz = buf.size;
+       assert_in_range(sz, 5, SIZE_MAX);
+       assert_int_equal(buf.offs, 4);
+       assert_int_equal(get_strbuf_len(&buf), 4);
+       assert_string_equal(get_strbuf_str(&buf), "moin");
+
+       mock_realloc = true;
+       will_return(__wrap_realloc, NULL);
+       ofs = get_strbuf_len(&buf);
+       while ((rc = append_strbuf_str(&buf, " hello")) >= 0) {
+               condlog(3, "%s", get_strbuf_str(&buf));
+               assert_int_equal(rc, 6);
+               assert_int_equal(get_strbuf_len(&buf), ofs + 6);
+               assert_memory_equal(get_strbuf_str(&buf), "moin", 4);
+               assert_string_equal(get_strbuf_str(&buf) + ofs, " hello");
+               ofs = get_strbuf_len(&buf);
+       }
+       assert_int_equal(rc, -ENOMEM);
+       assert_int_equal(buf.size, sz);
+       assert_int_equal(get_strbuf_len(&buf), ofs);
+       assert_memory_equal(get_strbuf_str(&buf), "moin", 4);
+       assert_string_equal(get_strbuf_str(&buf) + ofs - 6, " hello");
+
+       reset_strbuf(&buf);
+       assert_ptr_equal(buf.buf, NULL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       mock_realloc = false;
+}
+
+static void test_strbuf_overflow(void **state)
+{
+       STRBUF_ON_STACK(buf);
+
+       assert_int_equal(append_strbuf_str(&buf, "x"), 1);
+       /* fake huge buffer */
+       buf.size = SIZE_MAX - 1;
+       buf.offs = buf.size - 1;
+       assert_int_equal(append_strbuf_str(&buf, "x"), -EOVERFLOW);
+}
+
+static void test_strbuf_big(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       const char big[] = 
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n";
+       char *bbig;
+       int i;
+
+       /* Under valgrind, 30000 iterations need ca. 30s on my laptop */
+       for (i = 0; i < 30000; i++) {
+               if (i % 1000 == 0)
+                       condlog(4, "%d", i);
+               assert_int_equal(append_strbuf_str(&buf, big), sizeof(big) - 1);
+               assert_int_equal(get_strbuf_len(&buf), (sizeof(big) - 1) * (i + 
1));
+               assert_memory_equal(get_strbuf_str(&buf), big, sizeof(big) - 1);
+               assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf)
+                                   - (sizeof(big) - 1), big);
+       };
+       bbig = steal_strbuf_str(&buf);
+
+       assert_ptr_equal(buf.buf, NULL);
+       assert_int_equal(buf.size, 0);
+       assert_int_equal(buf.offs, 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       assert_int_equal(strlen(bbig), i * (sizeof(big) - 1));
+       assert_memory_equal(bbig, big, sizeof(big) - 1);
+       free(bbig);
+}
+
+static void test_strbuf_nul(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       char greet[] = "hello, sir!";
+
+       assert_int_equal(__append_strbuf_str(&buf, greet, 6), 6);
+       assert_string_equal(get_strbuf_str(&buf), "hello,");
+       assert_int_equal(__append_strbuf_str(&buf, greet, 6), 6);
+       assert_string_equal(get_strbuf_str(&buf), "hello,hello,");
+
+       /* overwrite comma with NUL; append_strbuf_str() stops at NUL byte */
+       greet[5] = '\0';
+       reset_strbuf(&buf);
+       assert_int_equal(append_strbuf_str(&buf, greet), 5);
+       assert_int_equal(get_strbuf_len(&buf), 5);
+       assert_string_equal(get_strbuf_str(&buf), "hello");
+       assert_int_equal(append_strbuf_str(&buf, greet), 5);
+       assert_int_equal(get_strbuf_len(&buf), 10);
+       assert_string_equal(get_strbuf_str(&buf), "hellohello");
+
+       /* __append_strbuf_str() appends full memory, including NUL bytes */
+       reset_strbuf(&buf);
+       assert_int_equal(__append_strbuf_str(&buf, greet, sizeof(greet) - 1),
+                        sizeof(greet) - 1);
+       assert_int_equal(get_strbuf_len(&buf), sizeof(greet) - 1);
+       assert_string_equal(get_strbuf_str(&buf), "hello");
+       assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - 5, " 
sir!");
+       assert_int_equal(__append_strbuf_str(&buf, greet, sizeof(greet) - 1),
+                        sizeof(greet) - 1);
+       assert_string_equal(get_strbuf_str(&buf), "hello");
+       assert_int_equal(get_strbuf_len(&buf), 2 * (sizeof(greet) - 1));
+       assert_string_equal(get_strbuf_str(&buf) + get_strbuf_len(&buf) - 5, " 
sir!");
+}
+
+static void test_strbuf_quoted(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       const char said[] = "She said ";
+       const char greet[] = "hi, man!";
+       char *p;
+       size_t n;
+
+       assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1);
+       assert_int_equal(append_strbuf_quoted(&buf, greet), sizeof(greet) + 1);
+       assert_string_equal(get_strbuf_str(&buf), "She said \"hi, man!\"");
+       n = get_strbuf_len(&buf);
+       p = steal_strbuf_str(&buf);
+       assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1);
+       assert_int_equal(append_strbuf_quoted(&buf, p), n + 4);
+       assert_string_equal(get_strbuf_str(&buf),
+                           "She said \"She said \"\"hi, man!\"\"\"");
+       free(p);
+       n = get_strbuf_len(&buf);
+       p = steal_strbuf_str(&buf);
+       assert_int_equal(append_strbuf_str(&buf, said), sizeof(said) - 1);
+       assert_int_equal(append_strbuf_quoted(&buf, p), n + 8);
+       assert_string_equal(get_strbuf_str(&buf),
+                           "She said \"She said \"\"She said \"\"\"\"hi, 
man!\"\"\"\"\"\"\"");
+       free(p);
+}
+
+static void test_strbuf_escaped(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       const char said[] = "She said \"hi, man\"";
+
+       assert_int_equal(append_strbuf_quoted(&buf, said), sizeof(said) + 3);
+       assert_string_equal(get_strbuf_str(&buf),
+                           "\"She said \"\"hi, man\"\"\"");
+
+       reset_strbuf(&buf);
+       assert_int_equal(append_strbuf_quoted(&buf, "\""), 4);
+       assert_string_equal(get_strbuf_str(&buf), "\"\"\"\"");
+
+       reset_strbuf(&buf);
+       assert_int_equal(append_strbuf_quoted(&buf, "\"\""), 6);
+       assert_string_equal(get_strbuf_str(&buf), "\"\"\"\"\"\"");
+
+       reset_strbuf(&buf);
+       assert_int_equal(append_strbuf_quoted(&buf, "\"Hi\""), 8);
+       assert_string_equal(get_strbuf_str(&buf), "\"\"\"Hi\"\"\"");
+}
+
+#define SENTENCE "yields, preceded by itself, falsehood"
+static void test_print_strbuf(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       char sentence[] = SENTENCE;
+
+       assert_int_equal(print_strbuf(&buf, "\"%s\" %s.", sentence, sentence),
+                        2 * (sizeof(sentence) - 1) + 4);
+       assert_string_equal(get_strbuf_str(&buf),
+                           "\"" SENTENCE "\" " SENTENCE ".");
+       condlog(3, "%s", get_strbuf_str(&buf));
+
+       reset_strbuf(&buf);
+       assert_int_equal(print_strbuf(&buf, "0x%08x", 0xdeadbeef), 10);
+       assert_string_equal(get_strbuf_str(&buf), "0xdeadbeef");
+
+       reset_strbuf(&buf);
+       assert_int_equal(print_strbuf(&buf, "%d%% of %d is %0.2f",
+                                     5, 100, 0.05), 17);
+       assert_string_equal(get_strbuf_str(&buf), "5% of 100 is 0.05");
+}
+
+static void test_truncate_strbuf(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       const char str[] = "hello my dear!\n";
+       size_t sz, sz1;
+
+       assert_int_equal(truncate_strbuf(&buf, 1), -EFAULT);
+       assert_int_equal(truncate_strbuf(&buf, 0), -EFAULT);
+
+       assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1);
+       assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1);
+       assert_string_equal(get_strbuf_str(&buf), str);
+
+       assert_int_equal(truncate_strbuf(&buf, sizeof(str)), -ERANGE);
+       assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1);
+       assert_string_equal(get_strbuf_str(&buf), str);
+
+       assert_int_equal(truncate_strbuf(&buf, sizeof(str) - 1), 0);
+       assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 1);
+       assert_string_equal(get_strbuf_str(&buf), str);
+
+       assert_int_equal(truncate_strbuf(&buf, sizeof(str) - 2), 0);
+       assert_int_equal(get_strbuf_len(&buf), sizeof(str) - 2);
+       assert_string_not_equal(get_strbuf_str(&buf), str);
+       assert_memory_equal(get_strbuf_str(&buf), str, sizeof(str) - 2);
+
+       assert_int_equal(truncate_strbuf(&buf, 5), 0);
+       assert_int_equal(get_strbuf_len(&buf), 5);
+       assert_string_not_equal(get_strbuf_str(&buf), str);
+       assert_string_equal(get_strbuf_str(&buf), "hello");
+
+       reset_strbuf(&buf);
+       assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1);
+
+       sz = buf.size;
+       while (buf.size == sz)
+               assert_int_equal(append_strbuf_str(&buf, str), sizeof(str) - 1);
+
+       sz1  = buf.size;
+       assert_in_range(get_strbuf_len(&buf), sz + 1, SIZE_MAX);
+       assert_string_equal(get_strbuf_str(&buf) +
+                           get_strbuf_len(&buf) - (sizeof(str) - 1), str);
+       assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf) + 1),
+                        -ERANGE);
+       assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf)), 0);
+       assert_int_equal(truncate_strbuf(&buf, get_strbuf_len(&buf)
+                                        - (sizeof(str) - 1)), 0);
+       assert_in_range(get_strbuf_len(&buf), 1, sz);
+       assert_string_equal(get_strbuf_str(&buf) +
+                           get_strbuf_len(&buf) - (sizeof(str) - 1), str);
+       assert_int_equal(buf.size, sz1);
+
+       assert_int_equal(truncate_strbuf(&buf, 5), 0);
+       assert_int_equal(get_strbuf_len(&buf), 5);
+       assert_string_equal(get_strbuf_str(&buf), "hello");
+       assert_int_equal(buf.size, sz1);
+
+       assert_int_equal(truncate_strbuf(&buf, 0), 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+       assert_int_equal(buf.size, sz1);
+}
+
+static void test_fill_strbuf(void **state)
+{
+       STRBUF_ON_STACK(buf);
+       int i;
+       char *p;
+
+       assert_int_equal(fill_strbuf(&buf, '+', -5), -EINVAL);
+
+       assert_int_equal(fill_strbuf(&buf, '+', 0), 0);
+       assert_int_equal(get_strbuf_len(&buf), 0);
+       assert_string_equal(get_strbuf_str(&buf), "");
+
+       assert_int_equal(fill_strbuf(&buf, '+', 1), 1);
+       assert_int_equal(get_strbuf_len(&buf), 1);
+       assert_string_equal(get_strbuf_str(&buf), "+");
+
+       assert_int_equal(fill_strbuf(&buf, '-', 3), 3);
+       assert_int_equal(get_strbuf_len(&buf), 4);
+       assert_string_equal(get_strbuf_str(&buf), "+---");
+
+       assert_int_equal(fill_strbuf(&buf, '\0', 3), 3);
+       assert_int_equal(get_strbuf_len(&buf), 7);
+       assert_string_equal(get_strbuf_str(&buf), "+---");
+
+       truncate_strbuf(&buf, 4);
+       assert_int_equal(fill_strbuf(&buf, '+', 4), 4);
+       assert_int_equal(get_strbuf_len(&buf), 8);
+       assert_string_equal(get_strbuf_str(&buf), "+---++++");
+
+       reset_strbuf(&buf);
+       assert_int_equal(fill_strbuf(&buf, 'x', 30000), 30000);
+       assert_int_equal(get_strbuf_len(&buf), 30000);
+       p = steal_strbuf_str(&buf);
+       assert_int_equal(strlen(p), 30000);
+       for (i = 0; i < 30000; i++)
+               assert_int_equal(p[i], 'x');
+       free(p);
+}
+
+static int test_strbuf(void)
+{
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test(test_strbuf_00),
+               cmocka_unit_test(test_strbuf_alloc_err),
+               cmocka_unit_test(test_strbuf_overflow),
+               cmocka_unit_test(test_strbuf_big),
+               cmocka_unit_test(test_strbuf_nul),
+               cmocka_unit_test(test_strbuf_quoted),
+               cmocka_unit_test(test_strbuf_escaped),
+               cmocka_unit_test(test_print_strbuf),
+               cmocka_unit_test(test_truncate_strbuf),
+               cmocka_unit_test(test_fill_strbuf),
+       };
+
+       return cmocka_run_group_tests(tests, NULL, NULL);
+}
+
+int main(void)
+{
+       int ret = 0;
+
+       init_test_verbosity(-1);
+       ret += test_strbuf();
+       return ret;
+}
-- 
2.32.0


--
dm-devel mailing list
[email protected]
https://listman.redhat.com/mailman/listinfo/dm-devel

Reply via email to