[dm-devel] [PATCH 2/9] libmultipath: strbuf: simple api for growing string buffers

Benjamin Marzinski bmarzins at redhat.com
Tue Jul 27 04:54:03 UTC 2021


On Thu, Jul 15, 2021 at 12:52:16PM +0200, mwilck at suse.com wrote:
> From: Martin Wilck <mwilck at 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.
> 

This looks good. I have a couple of nitpicks, but none of them are
actually bugs, so if you prefer what you have, you can ignore them.

> Signed-off-by: Martin Wilck <mwilck at 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..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;
> +

With this add calculation, if you need exactly (x) BUF_CHUNKS worth of
space, you will add (x + 1) BUF_CHUNKS worth of space.  If you instead
use

add = ((addsz - (buf->size - buf->offs)) / BUF_CHUNK + 1)

you will avoid allocating the extra BUF_CHUNK.

> +	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;
> +

This is correct, but I think it would be clearer if you calculated
buf->offs like you do in fill_strbufi():

	memcpy(buf->buf + buf->offs, str, slen);
	buf->offs += slen;

> +	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++) {

"qlen - 2 - (q - quoted)" is likely more space then you need here,
although it doesn't matter, since you already know that you will find a
quote before the end of the string. I get that it matches the
calculation after the loop. but even that one is confusing. I would
prefer that you either did something like

	size_t len = strlen(ptr);

...

		char *q1 = memccpy(q, p, '"', len - (p - ptr));

or even more obviously correct

	const char *p_end = ptr + strlen(ptr);

...

		char *q1 = memccpy(q, p, '"', p_end - p);


-Ben

> +		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




More information about the dm-devel mailing list