All of lore.kernel.org
 help / color / mirror / Atom feed
From: Benjamin Marzinski <bmarzins@redhat.com>
To: mwilck@suse.com
Cc: dm-devel@redhat.com
Subject: Re: [dm-devel] [PATCH 2/9] libmultipath: strbuf: simple api for growing string buffers
Date: Mon, 26 Jul 2021 23:54:03 -0500	[thread overview]
Message-ID: <20210727045403.GJ3087@octiron.msp.redhat.com> (raw)
In-Reply-To: <20210715105223.30463-3-mwilck@suse.com>

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

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

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel


  reply	other threads:[~2021-07-27  5:02 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-15 10:52 [dm-devel] [PATCH 0/9] multipath-tools: use variable-size string buffers mwilck
2021-07-15 10:52 ` [dm-devel] [PATCH 1/9] libmultipath: variable-size parameters in dm_get_map() mwilck
2021-07-26 22:17   ` Benjamin Marzinski
2021-08-11 14:18     ` Martin Wilck
2021-07-15 10:52 ` [dm-devel] [PATCH 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
2021-07-27  4:54   ` Benjamin Marzinski [this message]
2021-08-11 15:03     ` Martin Wilck
2021-07-15 10:52 ` [dm-devel] [PATCH 3/9] libmultipath: variable-size parameters in assemble_map() mwilck
2021-07-28 15:54   ` Benjamin Marzinski
2021-08-11 15:15     ` Martin Wilck
2021-07-15 10:52 ` [dm-devel] [PATCH 4/9] libmultipath: use strbuf in dict.c mwilck
2021-07-28 19:03   ` Benjamin Marzinski
2021-08-11 15:27     ` Martin Wilck
2021-08-30 18:23       ` Benjamin Marzinski
2021-07-15 10:52 ` [dm-devel] [PATCH 5/9] libmultipath: use strbuf in print.c mwilck
2021-07-29  0:00   ` Benjamin Marzinski
2021-07-15 10:52 ` [dm-devel] [PATCH 6/9] libmultipath: print.c: fail hard if keywords are not found mwilck
2021-07-29  2:36   ` Benjamin Marzinski
2021-07-15 10:52 ` [dm-devel] [PATCH 7/9] libmultipath: print.h: move macros to print.c mwilck
2021-07-29  2:36   ` Benjamin Marzinski
2021-07-15 10:52 ` [dm-devel] [PATCH 8/9] libmultipath: use strbuf in alias.c mwilck
2021-07-29 15:22   ` Benjamin Marzinski
2021-07-15 10:52 ` [dm-devel] [PATCH 9/9] multipathd: use strbuf in cli.c mwilck
2021-07-29 15:46   ` Benjamin Marzinski

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=20210727045403.GJ3087@octiron.msp.redhat.com \
    --to=bmarzins@redhat.com \
    --cc=dm-devel@redhat.com \
    --cc=mwilck@suse.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.