From: Martin Wilck <mwilck@xxxxxxxx> 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@xxxxxxxx> --- 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..8422a50 --- /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 + 1 - (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; + + buf->offs = (char *)mempcpy(buf->buf + buf->offs, str, slen) + - buf->buf; + 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@xxxxxxxxxx https://listman.redhat.com/mailman/listinfo/dm-devel