From: mwilck@suse.com
To: Benjamin Marzinski <bmarzins@redhat.com>,
Christophe Varoqui <christophe.varoqui@opensvc.com>
Cc: dm-devel@redhat.com, Martin Wilck <mwilck@suse.com>
Subject: [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers
Date: Wed, 11 Aug 2021 17:41:43 +0200 [thread overview]
Message-ID: <20210811154150.24714-3-mwilck@suse.com> (raw)
In-Reply-To: <20210811154150.24714-1-mwilck@suse.com>
From: Martin Wilck <mwilck@suse.com>
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 <mwilck@suse.com>
---
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
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
next prev parent reply other threads:[~2021-08-11 15:43 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
2021-08-11 15:41 ` [dm-devel] [PATCH v2 1/9] libmultipath: variable-size parameters in dm_get_map() mwilck
2021-08-30 20:44 ` Benjamin Marzinski
2021-08-11 15:41 ` mwilck [this message]
2021-08-11 16:08 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers Bart Van Assche
2021-08-12 7:19 ` Martin Wilck
2021-08-30 20:45 ` Benjamin Marzinski
2021-08-11 15:41 ` [dm-devel] [PATCH v2 3/9] libmultipath: variable-size parameters in assemble_map() mwilck
2021-08-30 20:45 ` Benjamin Marzinski
2021-08-11 15:41 ` [dm-devel] [PATCH v2 4/9] libmultipath: use strbuf in dict.c mwilck
2021-08-30 20:46 ` Benjamin Marzinski
2021-08-11 15:41 ` [dm-devel] [PATCH v2 5/9] libmultipath: use strbuf in print.c mwilck
2021-08-30 20:47 ` Benjamin Marzinski
2021-08-11 15:41 ` [dm-devel] [PATCH v2 6/9] libmultipath: print.c: fail hard if keywords are not found mwilck
2021-08-11 15:41 ` [dm-devel] [PATCH v2 7/9] libmultipath: print.h: move macros to print.c mwilck
2021-08-11 15:41 ` [dm-devel] [PATCH v2 8/9] libmultipath: use strbuf in alias.c mwilck
2021-08-11 15:41 ` [dm-devel] [PATCH v2 9/9] multipathd: use strbuf in cli.c mwilck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210811154150.24714-3-mwilck@suse.com \
--to=mwilck@suse.com \
--cc=bmarzins@redhat.com \
--cc=christophe.varoqui@opensvc.com \
--cc=dm-devel@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).