dm-devel.redhat.com archive mirror
 help / color / mirror / Atom feed
* [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers
@ 2021-08-11 15:41 mwilck
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 1/9] libmultipath: variable-size parameters in dm_get_map() mwilck
                   ` (8 more replies)
  0 siblings, 9 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Current libmultipath contains a lot of code writing text to pre-allocated
string buffers of fixed size while trying to avoid buffer overflows
or truncation of the output string. This code is, at least in part,
hard to verify and hard to read, as buffer sizes and positions need to
be checked after every additon of text. It requires either allocating large
buffers in advance, or risking to loose data, or re-allocating buffers
and starting over again.

This patch set changes this by switching almost entirely to text buffers
that are dynamically allocated and grow automatically as text is added.

The first patch, which was the initial motivation of the set, doesn't
need growing buffers, it just overcomes the PARAMS_SIZE limitation by
copying data from libdm. However if the PARAMS_SIZE limitation was dropped
when reading from the kernel, it made sense to drop it for constructing
PARAMS, too. This motivated the implementation of the strbuf API (patch 2),
and using it in assemble_map (patch 3). The API was then consequently used
elswhere in libmultipath too, where strings are built up in pieces (patch 4,
5, 8, 9). These additional changes are huge because of large number of
affected "snprint_xyz" functions and respective function calls, but most
of the changes are mechanical and follow always the same overall idea.

Finally, patch 6 and 7 are cleanups which I found necessary while working
on print.c.

Regression testing: added a test suite for strbuf itself, made sure
all tests cases (which involve quite a bit of printf_xyz() functions) still
pass, and compared output of lots of interactive multipathd and multipath
commands before and after the change.

Note that "multipathd show config" / "multipath -t" will show some minor
differences in the output, because the current code omits quotes for some
output value fields. With this patch set, all string values are quoted.

The patch set is available also on my "tip" branch:
https://github.com/openSUSE/multipath-tools/tree/tip.

Changes wrt v1 (thanks to Ben Marzinski for all):

 - 1/9: added missing pointer dereference
 - 2/9: fixed buffer size calculation, simplified code in __append_strbuf_str()
 - 3/9: use STRBUF_ON_STACK() rather than hand-coded __attribute__
        moved free() statement before goto in ev_add_path().
 - 4/9: Fixed return value of snprint_uid_attrs()
 - 5/9: Removed extra '-'

Martin Wilck (9):
  libmultipath: variable-size parameters in dm_get_map()
  libmultipath: strbuf: simple api for growing string buffers
  libmultipath: variable-size parameters in assemble_map()
  libmultipath: use strbuf in dict.c
  libmultipath: use strbuf in print.c
  libmultipath: print.c: fail hard if keywords are not found
  libmultipath: print.h: move macros to print.c
  libmultipath: use strbuf in alias.c.
  multipathd: use strbuf in cli.c

 libmultipath/Makefile                    |    2 +-
 libmultipath/alias.c                     |   84 +-
 libmultipath/blacklist.c                 |   13 +-
 libmultipath/configure.c                 |   18 +-
 libmultipath/configure.h                 |    3 +-
 libmultipath/devmapper.c                 |   44 +-
 libmultipath/devmapper.h                 |    4 +-
 libmultipath/dict.c                      |  314 ++--
 libmultipath/dict.h                      |   19 +-
 libmultipath/discovery.c                 |   13 +-
 libmultipath/dmparser.c                  |   47 +-
 libmultipath/dmparser.h                  |    2 +-
 libmultipath/foreign.c                   |   78 +-
 libmultipath/foreign.h                   |   13 +-
 libmultipath/foreign/nvme.c              |  100 +-
 libmultipath/generic.c                   |   26 +-
 libmultipath/generic.h                   |   17 +-
 libmultipath/libmultipath.version        |   21 +-
 libmultipath/parser.c                    |   50 +-
 libmultipath/parser.h                    |   17 +-
 libmultipath/print.c                     | 1839 ++++++++++------------
 libmultipath/print.h                     |  131 +-
 libmultipath/prioritizers/weightedpath.c |   71 +-
 libmultipath/propsel.c                   |  147 +-
 libmultipath/strbuf.c                    |  207 +++
 libmultipath/strbuf.h                    |  168 ++
 libmultipath/structs.h                   |    1 -
 libmultipath/structs_vec.c               |   11 +-
 libmultipath/util.c                      |    5 +
 libmultipath/util.h                      |    1 +
 multipathd/cli.c                         |   94 +-
 multipathd/cli_handlers.c                |  349 ++--
 multipathd/main.c                        |   20 +-
 tests/Makefile                           |    3 +-
 tests/alias.c                            |   41 +-
 tests/strbuf.c                           |  412 +++++
 36 files changed, 2313 insertions(+), 2072 deletions(-)
 create mode 100644 libmultipath/strbuf.c
 create mode 100644 libmultipath/strbuf.h
 create mode 100644 tests/strbuf.c

-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 1/9] libmultipath: variable-size parameters in dm_get_map()
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
@ 2021-08-11 15:41 ` mwilck
  2021-08-30 20:44   ` Benjamin Marzinski
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

We've seen a crash of multipath in disassemble_map because of a params
string exceeding PARAMS_SIZE. While the crash could have been fixed by
a simple error check, I believe multipath should be able to work with
arbitrary long parameter strings passed from the kernel.

The parameter list of dm_get_map() has changed. Bumped ABI version and
removed dm_get_map() and some functions from the ABI, which are only
called from libmultipath itself.

Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/devmapper.c          | 44 ++++++++++++++++++++-----------
 libmultipath/devmapper.h          |  4 +--
 libmultipath/libmultipath.version |  6 +----
 libmultipath/structs_vec.c        | 11 +++++---
 4 files changed, 38 insertions(+), 27 deletions(-)

diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c
index 945e625..c05dc20 100644
--- a/libmultipath/devmapper.c
+++ b/libmultipath/devmapper.c
@@ -648,7 +648,7 @@ int dm_map_present(const char * str)
 	return (do_get_info(str, &info) == 0);
 }
 
-int dm_get_map(const char *name, unsigned long long *size, char *outparams)
+int dm_get_map(const char *name, unsigned long long *size, char **outparams)
 {
 	int r = DMP_ERR;
 	struct dm_task *dmt;
@@ -682,12 +682,13 @@ int dm_get_map(const char *name, unsigned long long *size, char *outparams)
 	if (size)
 		*size = length;
 
-	if (!outparams) {
+	if (!outparams)
 		r = DMP_OK;
-		goto out;
+	else {
+		*outparams = strdup(params);
+		r = *outparams ? DMP_OK : DMP_ERR;
 	}
-	if (snprintf(outparams, PARAMS_SIZE, "%s", params) <= PARAMS_SIZE)
-		r = DMP_OK;
+
 out:
 	dm_task_destroy(dmt);
 	return r;
@@ -761,7 +762,7 @@ is_mpath_part(const char *part_name, const char *map_name)
 	return 0;
 }
 
-int dm_get_status(const char *name, char *outstatus)
+int dm_get_status(const char *name, char **outstatus)
 {
 	int r = DMP_ERR;
 	struct dm_task *dmt;
@@ -799,8 +800,12 @@ int dm_get_status(const char *name, char *outstatus)
 		goto out;
 	}
 
-	if (snprintf(outstatus, PARAMS_SIZE, "%s", status) <= PARAMS_SIZE)
+	if (!outstatus)
 		r = DMP_OK;
+	else {
+		*outstatus = strdup(status);
+		r = *outstatus ? DMP_OK : DMP_ERR;
+	}
 out:
 	if (r != DMP_OK)
 		condlog(0, "%s: error getting map status string", name);
@@ -1049,7 +1054,7 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
 	int queue_if_no_path = 0;
 	int udev_flags = 0;
 	unsigned long long mapsize;
-	char params[PARAMS_SIZE] = {0};
+	char *params = NULL;
 
 	if (dm_is_mpath(mapname) != 1)
 		return 0; /* nothing to do */
@@ -1065,7 +1070,7 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
 			return 1;
 
 	if (need_suspend &&
-	    dm_get_map(mapname, &mapsize, params) == DMP_OK &&
+	    dm_get_map(mapname, &mapsize, &params) == DMP_OK &&
 	    strstr(params, "queue_if_no_path")) {
 		if (!dm_queue_if_no_path(mapname, 0))
 			queue_if_no_path = 1;
@@ -1073,6 +1078,8 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
 			/* Leave queue_if_no_path alone if unset failed */
 			queue_if_no_path = -1;
 	}
+	free(params);
+	params = NULL;
 
 	if (dm_remove_partmaps(mapname, need_sync, deferred_remove))
 		return 1;
@@ -1431,7 +1438,7 @@ do_foreach_partmaps (const char * mapname,
 	struct dm_task *dmt;
 	struct dm_names *names;
 	unsigned next = 0;
-	char params[PARAMS_SIZE];
+	char *params = NULL;
 	unsigned long long size;
 	char dev_t[32];
 	int r = 1;
@@ -1474,7 +1481,7 @@ do_foreach_partmaps (const char * mapname,
 		    /*
 		     * and we can fetch the map table from the kernel
 		     */
-		    dm_get_map(names->name, &size, &params[0]) == DMP_OK &&
+		    dm_get_map(names->name, &size, &params) == DMP_OK &&
 
 		    /*
 		     * and the table maps over the multipath map
@@ -1486,12 +1493,15 @@ do_foreach_partmaps (const char * mapname,
 				goto out;
 		}
 
+		free(params);
+		params = NULL;
 		next = names->next;
 		names = (void *) names + next;
 	} while (next);
 
 	r = 0;
 out:
+	free(params);
 	dm_task_destroy (dmt);
 	return r;
 }
@@ -1620,17 +1630,19 @@ struct rename_data {
 static int
 rename_partmap (const char *name, void *data)
 {
-	char buff[PARAMS_SIZE];
+	char *buff = NULL;
 	int offset;
 	struct rename_data *rd = (struct rename_data *)data;
 
 	if (strncmp(name, rd->old, strlen(rd->old)) != 0)
 		return 0;
 	for (offset = strlen(rd->old); name[offset] && !(isdigit(name[offset])); offset++); /* do nothing */
-	snprintf(buff, PARAMS_SIZE, "%s%s%s", rd->new, rd->delim,
-		 name + offset);
-	dm_rename(name, buff, rd->delim, SKIP_KPARTX_OFF);
-	condlog(4, "partition map %s renamed", name);
+	if (asprintf(&buff, "%s%s%s", rd->new, rd->delim, name + offset) >= 0) {
+		dm_rename(name, buff, rd->delim, SKIP_KPARTX_OFF);
+		free(buff);
+		condlog(4, "partition map %s renamed", name);
+	} else
+		condlog(1, "failed to rename partition map %s", name);
 	return 0;
 }
 
diff --git a/libmultipath/devmapper.h b/libmultipath/devmapper.h
index e29b4d4..45a676d 100644
--- a/libmultipath/devmapper.h
+++ b/libmultipath/devmapper.h
@@ -44,8 +44,8 @@ int dm_addmap_create (struct multipath *mpp, char *params);
 int dm_addmap_reload (struct multipath *mpp, char *params, int flush);
 int dm_map_present (const char *);
 int dm_map_present_by_uuid(const char *uuid);
-int dm_get_map(const char *, unsigned long long *, char *);
-int dm_get_status(const char *, char *);
+int dm_get_map(const char *, unsigned long long *, char **);
+int dm_get_status(const char *, char **);
 int dm_type(const char *, char *);
 int dm_is_mpath(const char *);
 int _dm_flush_map (const char *, int, int, int, int);
diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
index 0cff311..7567837 100644
--- a/libmultipath/libmultipath.version
+++ b/libmultipath/libmultipath.version
@@ -31,7 +31,7 @@
  *   The new version inherits the previous ones.
  */
 
-LIBMULTIPATH_5.0.0 {
+LIBMULTIPATH_6.0.0 {
 global:
 	/* symbols referenced by multipath and multipathd */
 	add_foreign;
@@ -58,8 +58,6 @@ global:
 	count_active_paths;
 	delete_all_foreign;
 	delete_foreign;
-	disassemble_map;
-	disassemble_status;
 	dlog;
 	dm_cancel_deferred_remove;
 	dm_enablegroup;
@@ -70,10 +68,8 @@ global:
 	dm_geteventnr;
 	dm_get_info;
 	dm_get_major_minor;
-	dm_get_map;
 	dm_get_maps;
 	dm_get_multipath;
-	dm_get_status;
 	dm_get_uuid;
 	dm_is_mpath;
 	dm_mapname;
diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c
index 7539019..24d6fd2 100644
--- a/libmultipath/structs_vec.c
+++ b/libmultipath/structs_vec.c
@@ -416,12 +416,12 @@ int
 update_multipath_table (struct multipath *mpp, vector pathvec, int flags)
 {
 	int r = DMP_ERR;
-	char params[PARAMS_SIZE] = {0};
+	char *params = NULL;
 
 	if (!mpp)
 		return r;
 
-	r = dm_get_map(mpp->alias, &mpp->size, params);
+	r = dm_get_map(mpp->alias, &mpp->size, &params);
 	if (r != DMP_OK) {
 		condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting table" : "map not present");
 		return r;
@@ -429,14 +429,17 @@ update_multipath_table (struct multipath *mpp, vector pathvec, int flags)
 
 	if (disassemble_map(pathvec, params, mpp)) {
 		condlog(2, "%s: cannot disassemble map", mpp->alias);
+		free(params);
 		return DMP_ERR;
 	}
 
-	*params = '\0';
-	if (dm_get_status(mpp->alias, params) != DMP_OK)
+	free(params);
+	params = NULL;
+	if (dm_get_status(mpp->alias, &params) != DMP_OK)
 		condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting status" : "map not present");
 	else if (disassemble_status(params, mpp))
 		condlog(2, "%s: cannot disassemble status", mpp->alias);
+	free(params);
 
 	/* FIXME: we should deal with the return value here */
 	update_pathvec_from_dm(pathvec, mpp, flags);
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers
  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-11 15:41 ` mwilck
  2021-08-11 16:08   ` Bart Van Assche
  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
                   ` (6 subsequent siblings)
  8 siblings, 2 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 3/9] libmultipath: variable-size parameters in assemble_map()
  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-11 15:41 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
@ 2021-08-11 15:41 ` 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
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Instead of using fixed PARAMS_SIZE-sized arrays for parameters, use
dynamically allocated memory.

The library version needs to be bumped, because setup_map() argument
list has changed.

Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/configure.c          | 18 ++++++------
 libmultipath/configure.h          |  3 +-
 libmultipath/dmparser.c           | 47 ++++++++++---------------------
 libmultipath/dmparser.h           |  2 +-
 libmultipath/libmultipath.version |  5 +++-
 libmultipath/structs.h            |  1 -
 libmultipath/util.c               |  5 ++++
 libmultipath/util.h               |  1 +
 multipathd/cli_handlers.c         |  4 +--
 multipathd/main.c                 | 20 +++++++------
 10 files changed, 50 insertions(+), 56 deletions(-)

diff --git a/libmultipath/configure.c b/libmultipath/configure.c
index a6ae335..1227864 100644
--- a/libmultipath/configure.c
+++ b/libmultipath/configure.c
@@ -292,8 +292,7 @@ static int wait_for_pending_paths(struct multipath *mpp,
 	return n_pending;
 }
 
-int setup_map(struct multipath *mpp, char *params, int params_size,
-	      struct vectors *vecs)
+int setup_map(struct multipath *mpp, char **params, struct vectors *vecs)
 {
 	struct pathgroup * pgp;
 	struct config *conf;
@@ -462,7 +461,7 @@ int setup_map(struct multipath *mpp, char *params, int params_size,
 	 * transform the mp->pg vector of vectors of paths
 	 * into a mp->params strings to feed the device-mapper
 	 */
-	if (assemble_map(mpp, params, params_size)) {
+	if (assemble_map(mpp, params)) {
 		condlog(0, "%s: problem assembing map", mpp->alias);
 		return 1;
 	}
@@ -811,7 +810,7 @@ void select_action (struct multipath *mpp, const struct _vector *curmp,
 		remove_feature(&mpp_feat, "retain_attached_hw_handler");
 		remove_feature(&cmpp_feat, "queue_if_no_path");
 		remove_feature(&cmpp_feat, "retain_attached_hw_handler");
-		if (strncmp(mpp_feat, cmpp_feat, PARAMS_SIZE)) {
+		if (strcmp(mpp_feat, cmpp_feat)) {
 			select_reload_action(mpp, "features change");
 			FREE(cmpp_feat);
 			FREE(mpp_feat);
@@ -1128,14 +1127,14 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
 	int ret = CP_FAIL;
 	int k, i, r;
 	int is_daemon = (cmd == CMD_NONE) ? 1 : 0;
-	char params[PARAMS_SIZE];
+	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
 	struct multipath * mpp;
-	struct path * pp1;
+	struct path * pp1 = NULL;
 	struct path * pp2;
 	vector curmp = vecs->mpvec;
 	vector pathvec = vecs->pathvec;
 	vector newmp;
-	struct config *conf;
+	struct config *conf = NULL;
 	int allow_queueing;
 	struct bitfield *size_mismatch_seen;
 
@@ -1247,8 +1246,7 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
 		}
 		verify_paths(mpp);
 
-		params[0] = '\0';
-		if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+		if (setup_map(mpp, &params, vecs)) {
 			remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC);
 			continue;
 		}
@@ -1260,6 +1258,8 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
 				      force_reload == FORCE_RELOAD_YES ? 1 : 0);
 
 		r = domap(mpp, params, is_daemon);
+		free(params);
+		params = NULL;
 
 		if (r == DOMAP_FAIL || r == DOMAP_RETRY) {
 			condlog(3, "%s: domap (%u) failure "
diff --git a/libmultipath/configure.h b/libmultipath/configure.h
index 70cf77a..92a5aba 100644
--- a/libmultipath/configure.h
+++ b/libmultipath/configure.h
@@ -47,8 +47,7 @@ enum {
 
 struct vectors;
 
-int setup_map (struct multipath * mpp, char * params, int params_size,
-	       struct vectors *vecs );
+int setup_map (struct multipath * mpp, char **params, struct vectors *vecs);
 void select_action (struct multipath *mpp, const struct _vector *curmp,
 		    int force_reload);
 int domap (struct multipath * mpp, char * params, int is_daemon);
diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c
index b306c46..4ba7f33 100644
--- a/libmultipath/dmparser.c
+++ b/libmultipath/dmparser.c
@@ -15,6 +15,7 @@
 #include "util.h"
 #include "debug.h"
 #include "dmparser.h"
+#include "strbuf.h"
 
 #define WORD_SIZE 64
 
@@ -41,40 +42,21 @@ merge_words(char **dst, const char *word)
 	return 0;
 }
 
-#define APPEND(p, end, args...)						\
-({									\
-	int ret;							\
-									\
-	ret = snprintf(p, end - p, ##args);				\
-	if (ret < 0) {							\
-		condlog(0, "%s: conversion error", mp->alias);		\
-		goto err;						\
-	}								\
-	p += ret;							\
-	if (p >= end) {							\
-		condlog(0, "%s: params too small", mp->alias);		\
-		goto err;						\
-	}								\
-})
-
 /*
  * Transforms the path group vector into a proper device map string
  */
-int
-assemble_map (struct multipath * mp, char * params, int len)
+int assemble_map(struct multipath *mp, char **params)
 {
+	static const char no_path_retry[] = "queue_if_no_path";
+	static const char retain_hwhandler[] = "retain_attached_hw_handler";
 	int i, j;
 	int minio;
 	int nr_priority_groups, initial_pg_nr;
-	char * p;
-	const char *const end = params + len;
-	char no_path_retry[] = "queue_if_no_path";
-	char retain_hwhandler[] = "retain_attached_hw_handler";
+	STRBUF_ON_STACK(buff);
 	struct pathgroup * pgp;
 	struct path * pp;
 
 	minio = mp->minio;
-	p = params;
 
 	nr_priority_groups = VECTOR_SIZE(mp->pg);
 	initial_pg_nr = (nr_priority_groups ? mp->bestpg : 0);
@@ -87,14 +69,15 @@ assemble_map (struct multipath * mp, char * params, int len)
 	    get_linux_version_code() < KERNEL_VERSION(4, 3, 0))
 		add_feature(&mp->features, retain_hwhandler);
 
-	/* mp->features must not be NULL */
-	APPEND(p, end, "%s %s %i %i", mp->features, mp->hwhandler,
-		nr_priority_groups, initial_pg_nr);
+	if (print_strbuf(&buff, "%s %s %i %i", mp->features, mp->hwhandler,
+			 nr_priority_groups, initial_pg_nr) < 0)
+		goto err;
 
 	vector_foreach_slot (mp->pg, pgp, i) {
 		pgp = VECTOR_SLOT(mp->pg, i);
-		APPEND(p, end, " %s %i 1", mp->selector,
-		       VECTOR_SIZE(pgp->paths));
+		if (print_strbuf(&buff, " %s %i 1", mp->selector,
+				 VECTOR_SIZE(pgp->paths)) < 0)
+			goto err;
 
 		vector_foreach_slot (pgp->paths, pp, j) {
 			int tmp_minio = minio;
@@ -106,19 +89,19 @@ assemble_map (struct multipath * mp, char * params, int len)
 				condlog(0, "dev_t not set for '%s'", pp->dev);
 				goto err;
 			}
-			APPEND(p, end, " %s %d", pp->dev_t, tmp_minio);
+			if (print_strbuf(&buff, " %s %d", pp->dev_t, tmp_minio) < 0)
+				goto err;
 		}
 	}
 
-	condlog(4, "%s: assembled map [%s]", mp->alias, params);
+	*params = steal_strbuf_str(&buff);
+	condlog(4, "%s: assembled map [%s]", mp->alias, *params);
 	return 0;
 
 err:
 	return 1;
 }
 
-#undef APPEND
-
 /*
  * Caution callers: If this function encounters yet unkown path devices, it
  * adds them uninitialized to the mpp.
diff --git a/libmultipath/dmparser.h b/libmultipath/dmparser.h
index 212fee5..666ae74 100644
--- a/libmultipath/dmparser.h
+++ b/libmultipath/dmparser.h
@@ -1,3 +1,3 @@
-int assemble_map (struct multipath *, char *, int);
+int assemble_map (struct multipath *, char **);
 int disassemble_map (const struct _vector *, const char *, struct multipath *);
 int disassemble_status (const char *, struct multipath *);
diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
index 7567837..6dd52c2 100644
--- a/libmultipath/libmultipath.version
+++ b/libmultipath/libmultipath.version
@@ -31,7 +31,7 @@
  *   The new version inherits the previous ones.
  */
 
-LIBMULTIPATH_6.0.0 {
+LIBMULTIPATH_7.0.0 {
 global:
 	/* symbols referenced by multipath and multipathd */
 	add_foreign;
@@ -267,6 +267,9 @@ global:
 	/* added in 4.5.0 */
 	get_vpd_sgio;
 	trigger_partitions_udev_change;
+
+	/* added in 7.0.0 */
+	cleanup_charp;
 local:
 	*;
 };
diff --git a/libmultipath/structs.h b/libmultipath/structs.h
index c52bcee..399540e 100644
--- a/libmultipath/structs.h
+++ b/libmultipath/structs.h
@@ -13,7 +13,6 @@
 #define SERIAL_SIZE		128
 #define NODE_NAME_SIZE		224
 #define PATH_STR_SIZE		16
-#define PARAMS_SIZE		4096
 #define FILE_NAME_SIZE		256
 #define CALLOUT_MAX_SIZE	256
 #define BLK_DEV_SIZE		33
diff --git a/libmultipath/util.c b/libmultipath/util.c
index 0e37f3f..e2fafe8 100644
--- a/libmultipath/util.c
+++ b/libmultipath/util.c
@@ -455,3 +455,8 @@ int should_exit(void)
 {
 	return 0;
 }
+
+void cleanup_charp(char **p)
+{
+	free(*p);
+}
diff --git a/libmultipath/util.h b/libmultipath/util.h
index e9b48f9..89027f8 100644
--- a/libmultipath/util.h
+++ b/libmultipath/util.h
@@ -123,4 +123,5 @@ static inline void clear_bit_in_bitfield(unsigned int bit, struct bitfield *bf)
 		___p;		       \
 	})
 
+void cleanup_charp(char **p);
 #endif /* _UTIL_H */
diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c
index d70e1db..bce40b1 100644
--- a/multipathd/cli_handlers.c
+++ b/multipathd/cli_handlers.c
@@ -972,12 +972,12 @@ cli_reload(void *v, char **reply, int *len, void *data)
 int resize_map(struct multipath *mpp, unsigned long long size,
 	       struct vectors * vecs)
 {
-	char params[PARAMS_SIZE] = {0};
+	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
 	unsigned long long orig_size = mpp->size;
 
 	mpp->size = size;
 	update_mpp_paths(mpp, vecs->pathvec);
-	if (setup_map(mpp, params, PARAMS_SIZE, vecs) != 0) {
+	if (setup_map(mpp, &params, vecs) != 0) {
 		condlog(0, "%s: failed to setup map for resize : %s",
 			mpp->alias, strerror(errno));
 		mpp->size = orig_size;
diff --git a/multipathd/main.c b/multipathd/main.c
index bdd629e..ae8272e 100644
--- a/multipathd/main.c
+++ b/multipathd/main.c
@@ -489,7 +489,7 @@ static int
 update_map (struct multipath *mpp, struct vectors *vecs, int new_map)
 {
 	int retries = 3;
-	char params[PARAMS_SIZE] = {0};
+	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
 
 retry:
 	condlog(4, "%s: updating new map", mpp->alias);
@@ -502,13 +502,15 @@ retry:
 	verify_paths(mpp);
 	mpp->action = ACT_RELOAD;
 
-	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+	if (setup_map(mpp, &params, vecs)) {
 		condlog(0, "%s: failed to setup new map in update", mpp->alias);
 		retries = -1;
 		goto fail;
 	}
 	if (domap(mpp, params, 1) == DOMAP_FAIL && retries-- > 0) {
 		condlog(0, "%s: map_udate sleep", mpp->alias);
+		free(params);
+		params = NULL;
 		sleep(1);
 		goto retry;
 	}
@@ -1028,7 +1030,7 @@ int
 ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map)
 {
 	struct multipath * mpp;
-	char params[PARAMS_SIZE] = {0};
+	char *params __attribute((cleanup(cleanup_charp))) = NULL;
 	int retries = 3;
 	int start_waiter = 0;
 	int ret;
@@ -1104,7 +1106,7 @@ rescan:
 	/*
 	 * push the map to the device-mapper
 	 */
-	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+	if (setup_map(mpp, &params, vecs)) {
 		condlog(0, "%s: failed to setup map for addition of new "
 			"path %s", mpp->alias, pp->dev);
 		goto fail_map;
@@ -1129,6 +1131,8 @@ rescan:
 			condlog(0, "%s: ev_add_path sleep", mpp->alias);
 			sleep(1);
 			update_mpp_paths(mpp, vecs->pathvec);
+			free(params);
+			params = NULL;
 			goto rescan;
 		}
 		else if (mpp->action == ACT_RELOAD)
@@ -1189,7 +1193,7 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map)
 {
 	struct multipath * mpp;
 	int i, retval = REMOVE_PATH_SUCCESS;
-	char params[PARAMS_SIZE] = {0};
+	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
 
 	/*
 	 * avoid referring to the map of an orphaned path
@@ -1250,7 +1254,7 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map)
 			 */
 		}
 
-		if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+		if (setup_map(mpp, &params, vecs)) {
 			condlog(0, "%s: failed to setup map for"
 				" removal of path %s", mpp->alias, pp->dev);
 			goto fail;
@@ -1940,7 +1944,7 @@ int update_prio(struct path *pp, int refresh_all)
 static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh,
 		      int is_daemon)
 {
-	char params[PARAMS_SIZE] = {0};
+	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
 	struct path *pp;
 	int i, r;
 
@@ -1958,7 +1962,7 @@ static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh,
 			}
 		}
 	}
-	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
+	if (setup_map(mpp, &params, vecs)) {
 		condlog(0, "%s: failed to setup map", mpp->alias);
 		return 1;
 	}
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 4/9] libmultipath: use strbuf in dict.c
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (2 preceding siblings ...)
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 3/9] libmultipath: variable-size parameters in assemble_map() mwilck
@ 2021-08-11 15:41 ` 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
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Temporary solution for snprint_keyword(), as print.c hasn't been migrated yet.

Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/dict.c    | 314 ++++++++++++++++++-----------------------
 libmultipath/dict.h    |  19 +--
 libmultipath/parser.c  |  47 +++---
 libmultipath/parser.h  |  15 +-
 libmultipath/propsel.c | 147 +++++++++----------
 5 files changed, 254 insertions(+), 288 deletions(-)

diff --git a/libmultipath/dict.c b/libmultipath/dict.c
index dd08abf..7a72738 100644
--- a/libmultipath/dict.c
+++ b/libmultipath/dict.c
@@ -26,6 +26,7 @@
 #include <mpath_persist.h>
 #include "mpath_cmd.h"
 #include "dict.h"
+#include "strbuf.h"
 
 static int
 set_int(vector strvec, void *ptr)
@@ -143,84 +144,45 @@ set_yes_no_undef(vector strvec, void *ptr)
 	return 0;
 }
 
-static int
-print_int (char *buff, int len, long v)
+static int print_int(struct strbuf *buff, long v)
 {
-	return snprintf(buff, len, "%li", v);
+	return print_strbuf(buff, "%li", v);
 }
 
-static int
-print_nonzero (char *buff, int len, long v)
+static int print_nonzero(struct strbuf *buff, long v)
 {
 	if (!v)
 		return 0;
-	return snprintf(buff, len, "%li", v);
+	return print_strbuf(buff, "%li", v);
 }
 
-static int
-print_str (char *buff, int len, const char *ptr)
+static int print_str(struct strbuf *buff, const char *ptr)
 {
-	char *p;
-	char *last;
-	const char *q;
+	int ret = append_strbuf_quoted(buff, ptr);
 
-	if (!ptr || len <= 0)
-		return 0;
-
-	q = strchr(ptr, '"');
-	if (q == NULL)
-		return snprintf(buff, len, "\"%s\"", ptr);
-
-	last = buff + len - 1;
-	p = buff;
-	if (p >= last)
-		goto out;
-	*p++ = '"';
-	if (p >= last)
-		goto out;
-	for (; q; q = strchr(ptr, '"')) {
-		if (q + 1 - ptr < last - p)
-			p = mempcpy(p, ptr, q + 1 - ptr);
-		else {
-			p = mempcpy(p, ptr, last - p);
-			goto out;
-		}
-		*p++ = '"';
-		if (p >= last)
-			goto out;
-		ptr = q + 1;
-	}
-	p += strlcpy(p, ptr, last - p);
-	if (p >= last)
-		goto out;
-	*p++ = '"';
-	*p = '\0';
-	return p - buff;
-out:
-	*p = '\0';
-	return len;
+	/*
+	 * -EINVAL aka (ptr == NULL) means "not set".
+	 * Returning an error here breaks unit tests
+	 * (logic in snprint_keyword()).
+	 */
+	return ret == -EINVAL ? 0 : ret;
 }
 
-static int
-print_ignored (char *buff, int len)
+static int print_ignored(struct strbuf *buff)
 {
-	return snprintf(buff, len, "ignored");
+	return append_strbuf_quoted(buff, "ignored");
 }
 
-static int
-print_yes_no (char *buff, int len, long v)
+static int print_yes_no(struct strbuf *buff, long v)
 {
-	return snprintf(buff, len, "\"%s\"",
-			(v == YN_NO)? "no" : "yes");
+	return append_strbuf_quoted(buff, v == YN_NO ? "no" : "yes");
 }
 
-static int
-print_yes_no_undef (char *buff, int len, long v)
+static int print_yes_no_undef(struct strbuf *buff, long v)
 {
 	if (!v)
 		return 0;
-	return snprintf(buff, len, "\"%s\"",
-			(v == YNU_NO)? "no" : "yes");
+	return append_strbuf_quoted(buff, v == YNU_NO? "no" : "yes");
 }
 
 #define declare_def_handler(option, function)				\
@@ -232,32 +194,32 @@ def_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_def_snprint(option, function)				\
 static int								\
-snprint_def_ ## option (struct config *conf, char * buff, int len,	\
-			const void * data)				\
+snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
+			const void *data)				\
 {									\
-	return function (buff, len, conf->option);			\
+	return function(buff, conf->option);				\
 }
 
 #define declare_def_snprint_defint(option, function, value)		\
 static int								\
-snprint_def_ ## option (struct config *conf, char * buff, int len,	\
-			const void * data)				\
+snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
+			const void *data)				\
 {									\
 	int i = value;							\
 	if (!conf->option)						\
-		return function (buff, len, i);				\
-	return function (buff, len, conf->option);			\
+		return function(buff, i);				\
+	return function (buff, conf->option);				\
 }
 
 #define declare_def_snprint_defstr(option, function, value)		\
 static int								\
-snprint_def_ ## option (struct config *conf, char * buff, int len,	\
-			const void * data)				\
+snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
+			const void *data)				\
 {									\
 	static const char *s = value;					\
 	if (!conf->option)						\
-		return function (buff, len, s);				\
-	return function (buff, len, conf->option);			\
+		return function(buff, s);				\
+	return function(buff, conf->option);				\
 }
 
 #define declare_hw_handler(option, function)				\
@@ -272,11 +234,11 @@ hw_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_hw_snprint(option, function)				\
 static int								\
-snprint_hw_ ## option (struct config *conf, char * buff, int len,	\
-		       const void * data)				\
+snprint_hw_ ## option (struct config *conf, struct strbuf *buff,	\
+		       const void *data)				\
 {									\
 	const struct hwentry * hwe = (const struct hwentry *)data;	\
-	return function (buff, len, hwe->option);			\
+	return function(buff, hwe->option);				\
 }
 
 #define declare_ovr_handler(option, function)				\
@@ -290,10 +252,10 @@ ovr_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_ovr_snprint(option, function)				\
 static int								\
-snprint_ovr_ ## option (struct config *conf, char * buff, int len,	\
-			const void * data)				\
+snprint_ovr_ ## option (struct config *conf, struct strbuf *buff,	\
+			const void *data)				\
 {									\
-	return function (buff, len, conf->overrides->option);		\
+	return function (buff, conf->overrides->option);		\
 }
 
 #define declare_mp_handler(option, function)				\
@@ -308,11 +270,11 @@ mp_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_mp_snprint(option, function)				\
 static int								\
-snprint_mp_ ## option (struct config *conf, char * buff, int len,	\
-		       const void * data)				\
+snprint_mp_ ## option (struct config *conf, struct strbuf *buff,	\
+		       const void *data)				\
 {									\
 	const struct mpentry * mpe = (const struct mpentry *)data;	\
-	return function (buff, len, mpe->option);			\
+	return function(buff, mpe->option);				\
 }
 
 static int checkint_handler(struct config *conf, vector strvec)
@@ -354,13 +316,13 @@ static int def_partition_delim_handler(struct config *conf, vector strvec)
 	return 0;
 }
 
-static int snprint_def_partition_delim(struct config *conf, char *buff,
-				       int len, const void *data)
+static int snprint_def_partition_delim(struct config *conf, struct strbuf *buff,
+				       const void *data)
 {
 	if (default_partition_delim == NULL || conf->partition_delim != NULL)
-		return print_str(buff, len, conf->partition_delim);
+		return print_str(buff, conf->partition_delim);
 	else
-		return print_str(buff, len, UNSET_PARTITION_DELIM);
+		return print_str(buff, UNSET_PARTITION_DELIM);
 }
 
 static const char * const find_multipaths_optvals[] = {
@@ -403,10 +365,10 @@ def_find_multipaths_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_def_find_multipaths(struct config *conf, char *buff, int len,
+snprint_def_find_multipaths(struct config *conf, struct strbuf *buff,
 			    const void *data)
 {
-	return print_str(buff, len,
+	return append_strbuf_quoted(buff,
 			 find_multipaths_optvals[conf->find_multipaths]);
 }
 
@@ -419,21 +381,19 @@ declare_ovr_snprint(selector, print_str)
 declare_mp_handler(selector, set_str)
 declare_mp_snprint(selector, print_str)
 
-static int snprint_uid_attrs(struct config *conf, char *buff, int len,
+static int snprint_uid_attrs(struct config *conf, struct strbuf *buff,
 			     const void *dummy)
 {
-	char *p = buff;
-	int n, j;
+	int j, ret, total = 0;
 	const char *att;
 
 	vector_foreach_slot(&conf->uid_attrs, att, j) {
-		n = snprintf(p, len, "%s%s", j == 0 ? "" : " ", att);
-		if (n >= len)
-			return (p - buff) + n;
-		p += n;
-		len -= n;
+		ret = print_strbuf(buff, "%s%s", j == 0 ? "" : " ", att);
+		if (ret < 0)
+			return ret;
+		total += ret;
 	}
-	return p - buff;
+	return total;
 }
 
 static int uid_attrs_handler(struct config *conf, vector strvec)
@@ -526,18 +486,23 @@ declare_mp_snprint(minio_rq, print_nonzero)
 
 declare_def_handler(queue_without_daemon, set_yes_no)
 static int
-snprint_def_queue_without_daemon (struct config *conf,
-				  char * buff, int len, const void * data)
+snprint_def_queue_without_daemon(struct config *conf, struct strbuf *buff,
+				 const void * data)
 {
+	const char *qwd = "unknown";
+
 	switch (conf->queue_without_daemon) {
 	case QUE_NO_DAEMON_OFF:
-		return snprintf(buff, len, "\"no\"");
+		qwd = "no";
+		break;
 	case QUE_NO_DAEMON_ON:
-		return snprintf(buff, len, "\"yes\"");
+		qwd = "yes";
+		break;
 	case QUE_NO_DAEMON_FORCE:
-		return snprintf(buff, len, "\"forced\"");
+		qwd = "forced";
+		break;
 	}
-	return 0;
+	return append_strbuf_quoted(buff, qwd);
 }
 
 declare_def_handler(checker_timeout, set_int)
@@ -636,10 +601,11 @@ static int def_disable_changed_wwids_handler(struct config *conf, vector strvec)
 {
 	return 0;
 }
-static int snprint_def_disable_changed_wwids(struct config *conf, char *buff,
-					     int len, const void *data)
+static int snprint_def_disable_changed_wwids(struct config *conf,
+					     struct strbuf *buff,
+					     const void *data)
 {
-	return print_ignored(buff, len);
+	return print_ignored(buff);
 }
 
 declare_def_handler(remove_retries, set_int)
@@ -681,11 +647,10 @@ def_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_def_attr_snprint(option, function)			\
 static int								\
-snprint_def_ ## option (struct config *conf, char * buff, int len,	\
-			const void * data)				\
+snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
+			const void *data)				\
 {									\
-	return function (buff, len, conf->option,			\
-			 conf->attribute_flags);			\
+	return function(buff, conf->option, conf->attribute_flags);	\
 }
 
 #define declare_mp_attr_handler(option, function)			\
@@ -700,12 +665,11 @@ mp_ ## option ## _handler (struct config *conf, vector strvec)		\
 
 #define declare_mp_attr_snprint(option, function)			\
 static int								\
-snprint_mp_ ## option (struct config *conf, char * buff, int len,	\
+snprint_mp_ ## option (struct config *conf, struct strbuf *buff,	\
 		       const void * data)				\
 {									\
 	const struct mpentry * mpe = (const struct mpentry *)data;	\
-	return function (buff, len, mpe->option,			\
-			 mpe->attribute_flags);				\
+	return function(buff, mpe->option, mpe->attribute_flags);	\
 }
 
 static int
@@ -780,30 +744,30 @@ set_gid(vector strvec, void *ptr, int *flags)
 }
 
 static int
-print_mode(char * buff, int len, long v, int flags)
+print_mode(struct strbuf *buff, long v, int flags)
 {
 	mode_t mode = (mode_t)v;
 	if ((flags & (1 << ATTR_MODE)) == 0)
 		return 0;
-	return snprintf(buff, len, "0%o", mode);
+	return print_strbuf(buff, "0%o", mode);
 }
 
 static int
-print_uid(char * buff, int len, long v, int flags)
+print_uid(struct strbuf *buff, long v, int flags)
 {
 	uid_t uid = (uid_t)v;
 	if ((flags & (1 << ATTR_UID)) == 0)
 		return 0;
-	return snprintf(buff, len, "0%o", uid);
+	return print_strbuf(buff, "0%o", uid);
 }
 
 static int
-print_gid(char * buff, int len, long v, int flags)
+print_gid(struct strbuf *buff, long v, int flags)
 {
 	gid_t gid = (gid_t)v;
 	if ((flags & (1 << ATTR_GID)) == 0)
 		return 0;
-	return snprintf(buff, len, "0%o", gid);
+	return print_strbuf(buff, "0%o", gid);
 }
 
 declare_def_attr_handler(mode, set_mode)
@@ -843,16 +807,15 @@ set_undef_off_zero(vector strvec, void *ptr)
 	return 0;
 }
 
-int
-print_undef_off_zero(char * buff, int len, long v)
+int print_undef_off_zero(struct strbuf *buff, long v)
 {
 	if (v == UOZ_UNDEF)
 		return 0;
 	if (v == UOZ_OFF)
-		return snprintf(buff, len, "\"off\"");
+		return append_strbuf_str(buff, "off");
 	if (v == UOZ_ZERO)
-		return snprintf(buff, len, "0");
-	return snprintf(buff, len, "%ld", v);
+		return append_strbuf_str(buff, "0");
+	return print_int(buff, v);
 }
 
 declare_def_handler(fast_io_fail, set_undef_off_zero)
@@ -883,13 +846,13 @@ set_dev_loss(vector strvec, void *ptr)
 }
 
 int
-print_dev_loss(char * buff, int len, unsigned long v)
+print_dev_loss(struct strbuf *buff, unsigned long v)
 {
 	if (v == DEV_LOSS_TMO_UNSET)
 		return 0;
 	if (v >= MAX_DEV_LOSS_TMO)
-		return snprintf(buff, len, "\"infinity\"");
-	return snprintf(buff, len, "%lu", v);
+		return append_strbuf_quoted(buff, "infinity");
+	return print_strbuf(buff, "%lu", v);
 }
 
 declare_def_handler(dev_loss, set_dev_loss)
@@ -923,7 +886,7 @@ set_pgpolicy(vector strvec, void *ptr)
 }
 
 int
-print_pgpolicy(char * buff, int len, long pgpolicy)
+print_pgpolicy(struct strbuf *buff, long pgpolicy)
 {
 	char str[POLICY_NAME_SIZE];
 
@@ -932,7 +895,7 @@ print_pgpolicy(char * buff, int len, long pgpolicy)
 
 	get_pgpolicy_name(str, POLICY_NAME_SIZE, pgpolicy);
 
-	return snprintf(buff, len, "\"%s\"", str);
+	return append_strbuf_quoted(buff, str);
 }
 
 declare_def_handler(pgpolicy, set_pgpolicy)
@@ -1003,7 +966,7 @@ max_fds_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_max_fds (struct config *conf, char * buff, int len, const void * data)
+snprint_max_fds (struct config *conf, struct strbuf *buff, const void *data)
 {
 	int r = 0, max_fds;
 
@@ -1012,9 +975,9 @@ snprint_max_fds (struct config *conf, char * buff, int len, const void * data)
 
 	r = get_sys_max_fds(&max_fds);
 	if (!r && conf->max_fds >= max_fds)
-		return snprintf(buff, len, "\"max\"");
+		return append_strbuf_quoted(buff, "max");
 	else
-		return snprintf(buff, len, "%d", conf->max_fds);
+		return print_int(buff, conf->max_fds);
 }
 
 static int
@@ -1040,14 +1003,14 @@ set_rr_weight(vector strvec, void *ptr)
 }
 
 int
-print_rr_weight (char * buff, int len, long v)
+print_rr_weight (struct strbuf *buff, long v)
 {
 	if (!v)
 		return 0;
 	if (v == RR_WEIGHT_PRIO)
-		return snprintf(buff, len, "\"priorities\"");
+		return append_strbuf_quoted(buff, "priorities");
 	if (v == RR_WEIGHT_NONE)
-		return snprintf(buff, len, "\"uniform\"");
+		return append_strbuf_quoted(buff, "uniform");
 
 	return 0;
 }
@@ -1086,19 +1049,19 @@ set_pgfailback(vector strvec, void *ptr)
 }
 
 int
-print_pgfailback (char * buff, int len, long v)
+print_pgfailback (struct strbuf *buff, long v)
 {
 	switch(v) {
 	case  FAILBACK_UNDEF:
 		return 0;
 	case -FAILBACK_MANUAL:
-		return snprintf(buff, len, "\"manual\"");
+		return append_strbuf_quoted(buff, "manual");
 	case -FAILBACK_IMMEDIATE:
-		return snprintf(buff, len, "\"immediate\"");
+		return append_strbuf_quoted(buff, "immediate");
 	case -FAILBACK_FOLLOWOVER:
-		return snprintf(buff, len, "\"followover\"");
+		return append_strbuf_quoted(buff, "followover");
 	default:
-		return snprintf(buff, len, "%li", v);
+		return print_int(buff, v);
 	}
 }
 
@@ -1133,17 +1096,17 @@ no_path_retry_helper(vector strvec, void *ptr)
 }
 
 int
-print_no_path_retry(char * buff, int len, long v)
+print_no_path_retry(struct strbuf *buff, long v)
 {
 	switch(v) {
 	case NO_PATH_RETRY_UNDEF:
 		return 0;
 	case NO_PATH_RETRY_FAIL:
-		return snprintf(buff, len, "\"fail\"");
+		return append_strbuf_quoted(buff, "fail");
 	case NO_PATH_RETRY_QUEUE:
-		return snprintf(buff, len, "\"queue\"");
+		return append_strbuf_quoted(buff, "queue");
 	default:
-		return snprintf(buff, len, "%li", v);
+		return print_int(buff, v);
 	}
 }
 
@@ -1176,12 +1139,12 @@ def_log_checker_err_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_def_log_checker_err (struct config *conf, char * buff, int len,
-			     const void * data)
+snprint_def_log_checker_err(struct config *conf, struct strbuf *buff,
+			    const void * data)
 {
 	if (conf->log_checker_err == LOG_CHKR_ERR_ONCE)
-		return snprintf(buff, len, "once");
-	return snprintf(buff, len, "always");
+		return append_strbuf_quoted(buff, "once");
+	return append_strbuf_quoted(buff, "always");
 }
 
 static int
@@ -1216,18 +1179,17 @@ set_reservation_key(vector strvec, struct be64 *be64_ptr, uint8_t *flags_ptr,
 }
 
 int
-print_reservation_key(char * buff, int len, struct be64 key, uint8_t flags,
-		      int source)
+print_reservation_key(struct strbuf *buff,
+		      struct be64 key, uint8_t flags, int source)
 {
 	char *flagstr = "";
 	if (source == PRKEY_SOURCE_NONE)
 		return 0;
 	if (source == PRKEY_SOURCE_FILE)
-		return snprintf(buff, len, "file");
+		return append_strbuf_quoted(buff, "file");
 	if (flags & MPATH_F_APTPL_MASK)
 		flagstr = ":aptpl";
-	return snprintf(buff, len, "0x%" PRIx64 "%s", get_be64(key),
-			flagstr);
+	return print_strbuf(buff, "0x%" PRIx64 "%s", get_be64(key), flagstr);
 }
 
 static int
@@ -1239,12 +1201,11 @@ def_reservation_key_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_def_reservation_key (struct config *conf, char * buff, int len,
+snprint_def_reservation_key (struct config *conf, struct strbuf *buff,
 			     const void * data)
 {
-	return print_reservation_key(buff, len, conf->reservation_key,
-				     conf->sa_flags,
-				     conf->prkey_source);
+	return print_reservation_key(buff, conf->reservation_key,
+				     conf->sa_flags, conf->prkey_source);
 }
 
 static int
@@ -1259,13 +1220,12 @@ mp_reservation_key_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_mp_reservation_key (struct config *conf, char * buff, int len,
-			    const void * data)
+snprint_mp_reservation_key (struct config *conf, struct strbuf *buff,
+			    const void *data)
 {
 	const struct mpentry * mpe = (const struct mpentry *)data;
-	return print_reservation_key(buff, len, mpe->reservation_key,
-				     mpe->sa_flags,
-				     mpe->prkey_source);
+	return print_reservation_key(buff, mpe->reservation_key,
+				     mpe->sa_flags, mpe->prkey_source);
 }
 
 static int
@@ -1288,15 +1248,15 @@ set_off_int_undef(vector strvec, void *ptr)
 }
 
 int
-print_off_int_undef(char * buff, int len, long v)
+print_off_int_undef(struct strbuf *buff, long v)
 {
 	switch(v) {
 	case NU_UNDEF:
 		return 0;
 	case NU_NO:
-		return snprintf(buff, len, "\"no\"");
+		return append_strbuf_quoted(buff, "no");
 	default:
-		return snprintf(buff, len, "%li", v);
+		return print_int(buff, v);
 	}
 }
 
@@ -1455,13 +1415,13 @@ out:
 }
 
 static int
-snprint_hw_vpd_vendor(struct config *conf, char * buff, int len,
+snprint_hw_vpd_vendor(struct config *conf, struct strbuf *buff,
 		      const void * data)
 {
 	const struct hwentry * hwe = (const struct hwentry *)data;
 
 	if (hwe->vpd_vendor_id > 0 && hwe->vpd_vendor_id < VPD_VP_ARRAY_SIZE)
-		return snprintf(buff, len, "%s",
+		return append_strbuf_quoted(buff,
 				vpd_vendor_pages[hwe->vpd_vendor_id].name);
 	return 0;
 }
@@ -1561,19 +1521,18 @@ declare_ble_handler(blist_protocol)
 declare_ble_handler(elist_protocol)
 
 static int
-snprint_def_uxsock_timeout(struct config *conf, char * buff, int len,
-			   const void * data)
+snprint_def_uxsock_timeout(struct config *conf, struct strbuf *buff,
+			   const void *data)
 {
-	return snprintf(buff, len, "%u", conf->uxsock_timeout);
+	return print_strbuf(buff, "%u", conf->uxsock_timeout);
 }
 
 static int
-snprint_ble_simple (struct config *conf, char * buff, int len,
-		    const void * data)
+snprint_ble_simple (struct config *conf, struct strbuf *buff, const void *data)
 {
-	const struct blentry * ble = (const struct blentry *)data;
+	const struct blentry *ble = (const struct blentry *)data;
 
-	return snprintf(buff, len, "\"%s\"", ble->str);
+	return print_str(buff, ble->str);
 }
 
 static int
@@ -1593,24 +1552,22 @@ declare_ble_device_handler(vendor, elist_device, buff, NULL)
 declare_ble_device_handler(product, blist_device, NULL, buff)
 declare_ble_device_handler(product, elist_device, NULL, buff)
 
-static int
-snprint_bled_vendor (struct config *conf, char * buff, int len,
-		     const void * data)
+static int snprint_bled_vendor(struct config *conf, struct strbuf *buff,
+			       const void * data)
 {
 	const struct blentry_device * bled =
 		(const struct blentry_device *)data;
 
-	return snprintf(buff, len, "\"%s\"", bled->vendor);
+	return print_str(buff, bled->vendor);
 }
 
-static int
-snprint_bled_product (struct config *conf, char * buff, int len,
-		      const void * data)
+static int snprint_bled_product(struct config *conf, struct strbuf *buff,
+				const void *data)
 {
 	const struct blentry_device * bled =
 		(const struct blentry_device *)data;
 
-	return snprintf(buff, len, "\"%s\"", bled->product);
+	return print_str(buff, bled->product);
 }
 
 /*
@@ -1738,8 +1695,7 @@ deprecated_handler(struct config *conf, vector strvec)
 }
 
 static int
-snprint_deprecated (struct config *conf, char * buff, int len,
-		    const void * data)
+snprint_deprecated (struct config *conf, struct strbuf *buff, const void * data)
 {
 	return 0;
 }
diff --git a/libmultipath/dict.h b/libmultipath/dict.h
index a917e1c..d80f990 100644
--- a/libmultipath/dict.h
+++ b/libmultipath/dict.h
@@ -6,16 +6,17 @@
 #endif
 
 #include "byteorder.h"
+struct strbuf;
 
 void init_keywords(vector keywords);
 int get_sys_max_fds(int *);
-int print_rr_weight(char *buff, int len, long v);
-int print_pgfailback(char *buff, int len, long v);
-int print_pgpolicy(char *buff, int len, long v);
-int print_no_path_retry(char *buff, int len, long v);
-int print_undef_off_zero(char *buff, int len, long v);
-int print_dev_loss(char *buff, int len, unsigned long v);
-int print_reservation_key(char * buff, int len, struct be64 key, uint8_t
-			  flags, int source);
-int print_off_int_undef(char *buff, int len, long v);
+int print_rr_weight(struct strbuf *buff, long v);
+int print_pgfailback(struct strbuf *buff, long v);
+int print_pgpolicy(struct strbuf *buff, long v);
+int print_no_path_retry(struct strbuf *buff, long v);
+int print_undef_off_zero(struct strbuf *buff, long v);
+int print_dev_loss(struct strbuf *buff, unsigned long v);
+int print_reservation_key(struct strbuf *buff,
+			  struct be64 key, uint8_t flags, int source);
+int print_off_int_undef(struct strbuf *buff, long v);
 #endif /* _DICT_H */
diff --git a/libmultipath/parser.c b/libmultipath/parser.c
index c70243c..88c7b59 100644
--- a/libmultipath/parser.c
+++ b/libmultipath/parser.c
@@ -25,6 +25,7 @@
 #include "parser.h"
 #include "memory.h"
 #include "debug.h"
+#include "strbuf.h"
 
 /* local vars */
 static int sublevel = 0;
@@ -33,7 +34,7 @@ static int line_nr;
 int
 keyword_alloc(vector keywords, char *string,
 	      int (*handler) (struct config *, vector),
-	      int (*print) (struct config *, char *, int, const void*),
+	      print_fn *print,
 	      int unique)
 {
 	struct keyword *keyword;
@@ -72,7 +73,7 @@ install_sublevel_end(void)
 int
 _install_keyword(vector keywords, char *string,
 		 int (*handler) (struct config *, vector),
-		 int (*print) (struct config *, char *, int, const void*),
+		 print_fn *print,
 		 int unique)
 {
 	int i = 0;
@@ -153,42 +154,44 @@ snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
 		const void *data)
 {
 	int r;
-	int fwd = 0;
-	char *f = fmt;
+	char *f;
 	struct config *conf;
+	STRBUF_ON_STACK(sbuf);
 
 	if (!kw || !kw->print)
 		return 0;
 
 	do {
-		if (fwd == len || *f == '\0')
-			break;
-		if (*f != '%') {
-			*(buff + fwd) = *f;
-			fwd++;
-			continue;
+		f = strchr(fmt, '%');
+		if (f == NULL) {
+			r = append_strbuf_str(&sbuf, fmt);
+			goto out;
 		}
-		f++;
-		switch(*f) {
+		if (f != fmt &&
+		    (r = __append_strbuf_str(&sbuf, fmt, f - fmt)) < 0)
+			goto out;
+		fmt = f + 1;
+		switch(*fmt) {
 		case 'k':
-			fwd += snprintf(buff + fwd, len - fwd, "%s", kw->string);
+			if ((r = append_strbuf_str(&sbuf, kw->string)) < 0)
+				goto out;
 			break;
 		case 'v':
 			conf = get_multipath_config();
 			pthread_cleanup_push(put_multipath_config, conf);
-			r = kw->print(conf, buff + fwd, len - fwd, data);
+			r = kw->print(conf, &sbuf, data);
 			pthread_cleanup_pop(1);
-			if (!r) { /* no output if no value */
-				buff[0] = '\0';
-				return 0;
+			if (r < 0)
+				goto out;
+			else if (r == 0) {/* no output if no value */
+				reset_strbuf(&sbuf);
+				goto out;
 			}
-			fwd += r;
 			break;
 		}
-		if (fwd > len)
-			fwd = len;
-	} while (*f++);
-	return fwd;
+	} while (*fmt++);
+out:
+	return snprintf(buff, len, "%s", get_strbuf_str(&sbuf));
 }
 
 static const char quote_marker[] = { '\0', '"', '\0' };
diff --git a/libmultipath/parser.h b/libmultipath/parser.h
index 06666cc..e8b6eb2 100644
--- a/libmultipath/parser.h
+++ b/libmultipath/parser.h
@@ -34,16 +34,20 @@
 /* local includes */
 #include "vector.h"
 #include "config.h"
+struct strbuf;
 
 /* Global definitions */
 #define EOB  "}"
 #define MAXBUF	1024
 
-/* ketword definition */
+
+/* keyword definition */
+typedef int print_fn(struct config *, struct strbuf *, const void *);
+
 struct keyword {
 	char *string;
 	int (*handler) (struct config *, vector);
-	int (*print) (struct config *, char *, int, const void *);
+	print_fn *print;
 	vector sub;
 	int unique;
 };
@@ -60,16 +64,15 @@ struct keyword {
 /* Prototypes */
 extern int keyword_alloc(vector keywords, char *string,
 			 int (*handler) (struct config *, vector),
-			 int (*print) (struct config *, char *, int,
-				       const void *),
+			 print_fn *print,
 			 int unique);
 #define install_keyword_root(str, h) keyword_alloc(keywords, str, h, NULL, 1)
 extern void install_sublevel(void);
 extern void install_sublevel_end(void);
+
 extern int _install_keyword(vector keywords, char *string,
 			    int (*handler) (struct config *, vector),
-			    int (*print) (struct config *, char *, int,
-					  const void *),
+			    print_fn *print,
 			    int unique);
 #define install_keyword(str, vec, pri) _install_keyword(keywords, str, vec, pri, 1)
 #define install_keyword_multi(str, vec, pri) _install_keyword(keywords, str, vec, pri, 0)
diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c
index b7b3379..b287667 100644
--- a/libmultipath/propsel.c
+++ b/libmultipath/propsel.c
@@ -24,6 +24,7 @@
 #include "prioritizers/alua_rtpg.h"
 #include "prkey.h"
 #include "propsel.h"
+#include "strbuf.h"
 #include <inttypes.h>
 #include <libudev.h>
 
@@ -191,7 +192,7 @@ out:
 int select_rr_weight(struct config *conf, struct multipath * mp)
 {
 	const char *origin;
-	char buff[13];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(rr_weight);
 	mp_set_ovr(rr_weight);
@@ -199,15 +200,16 @@ int select_rr_weight(struct config *conf, struct multipath * mp)
 	mp_set_conf(rr_weight);
 	mp_set_default(rr_weight, DEFAULT_RR_WEIGHT);
 out:
-	print_rr_weight(buff, 13, mp->rr_weight);
-	condlog(3, "%s: rr_weight = %s %s", mp->alias, buff, origin);
+	print_rr_weight(&buff, mp->rr_weight);
+	condlog(3, "%s: rr_weight = %s %s", mp->alias,
+		get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_pgfailback(struct config *conf, struct multipath * mp)
 {
 	const char *origin;
-	char buff[13];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(pgfailback);
 	mp_set_ovr(pgfailback);
@@ -215,8 +217,9 @@ int select_pgfailback(struct config *conf, struct multipath * mp)
 	mp_set_conf(pgfailback);
 	mp_set_default(pgfailback, DEFAULT_FAILBACK);
 out:
-	print_pgfailback(buff, 13, mp->pgfailback);
-	condlog(3, "%s: failback = %s %s", mp->alias, buff, origin);
+	print_pgfailback(&buff, mp->pgfailback);
+	condlog(3, "%s: failback = %s %s", mp->alias,
+		get_strbuf_str(&buff), origin);
 	return 0;
 }
 
@@ -339,7 +342,7 @@ void reconcile_features_with_options(const char *id, char **features, int* no_pa
 {
 	static const char q_i_n_p[] = "queue_if_no_path";
 	static const char r_a_h_h[] = "retain_attached_hw_handler";
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	if (*features == NULL)
 		return;
@@ -360,17 +363,15 @@ void reconcile_features_with_options(const char *id, char **features, int* no_pa
 			id, q_i_n_p);
 		if (*no_path_retry == NO_PATH_RETRY_UNDEF) {
 			*no_path_retry = NO_PATH_RETRY_QUEUE;
-			print_no_path_retry(buff, sizeof(buff),
-					    *no_path_retry);
+			print_no_path_retry(&buff, *no_path_retry);
 			condlog(3, "%s: no_path_retry = %s (inherited setting from feature '%s')",
-				id, buff, q_i_n_p);
+				id, get_strbuf_str(&buff), q_i_n_p);
 		};
 		/* Warn only if features string is overridden */
 		if (*no_path_retry != NO_PATH_RETRY_QUEUE) {
-			print_no_path_retry(buff, sizeof(buff),
-					    *no_path_retry);
+			print_no_path_retry(&buff, *no_path_retry);
 			condlog(2, "%s: ignoring feature '%s' because no_path_retry is set to '%s'",
-				id, q_i_n_p, buff);
+				id, q_i_n_p, get_strbuf_str(&buff));
 		}
 		remove_feature(features, q_i_n_p);
 	}
@@ -704,7 +705,7 @@ out:
 int select_no_path_retry(struct config *conf, struct multipath *mp)
 {
 	const char *origin = NULL;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	if (mp->disable_queueing) {
 		condlog(0, "%s: queueing disabled", mp->alias);
@@ -716,10 +717,10 @@ int select_no_path_retry(struct config *conf, struct multipath *mp)
 	mp_set_hwe(no_path_retry);
 	mp_set_conf(no_path_retry);
 out:
-	print_no_path_retry(buff, 12, mp->no_path_retry);
+	print_no_path_retry(&buff, mp->no_path_retry);
 	if (origin)
-		condlog(3, "%s: no_path_retry = %s %s", mp->alias, buff,
-			origin);
+		condlog(3, "%s: no_path_retry = %s %s", mp->alias,
+			get_strbuf_str(&buff), origin);
 	else
 		condlog(3, "%s: no_path_retry = undef %s",
 			mp->alias, default_origin);
@@ -770,22 +771,23 @@ int select_minio(struct config *conf, struct multipath *mp)
 int select_fast_io_fail(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_ovr(fast_io_fail);
 	mp_set_hwe(fast_io_fail);
 	mp_set_conf(fast_io_fail);
 	mp_set_default(fast_io_fail, DEFAULT_FAST_IO_FAIL);
 out:
-	print_undef_off_zero(buff, 12, mp->fast_io_fail);
-	condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias, buff, origin);
+	print_undef_off_zero(&buff, mp->fast_io_fail);
+	condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias,
+		get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_dev_loss(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_ovr(dev_loss);
 	mp_set_hwe(dev_loss);
@@ -793,15 +795,16 @@ int select_dev_loss(struct config *conf, struct multipath *mp)
 	mp->dev_loss = DEV_LOSS_TMO_UNSET;
 	return 0;
 out:
-	print_dev_loss(buff, 12, mp->dev_loss);
-	condlog(3, "%s: dev_loss_tmo = %s %s", mp->alias, buff, origin);
+	print_dev_loss(&buff, mp->dev_loss);
+	condlog(3, "%s: dev_loss_tmo = %s %s", mp->alias,
+		get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_eh_deadline(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_ovr(eh_deadline);
 	mp_set_hwe(eh_deadline);
@@ -810,8 +813,9 @@ int select_eh_deadline(struct config *conf, struct multipath *mp)
 	/* not changing sysfs in default cause, so don't print anything */
 	return 0;
 out:
-	print_undef_off_zero(buff, 12, mp->eh_deadline);
-	condlog(3, "%s: eh_deadline = %s %s", mp->alias, buff, origin);
+	print_undef_off_zero(&buff, mp->eh_deadline);
+	condlog(3, "%s: eh_deadline = %s %s", mp->alias,
+		get_strbuf_str(&buff), origin);
 	return 0;
 }
 
@@ -833,7 +837,7 @@ out:
 int select_reservation_key(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[PRKEY_SIZE];
+	STRBUF_ON_STACK(buff);
 	char *from_file = "";
 	uint64_t prkey = 0;
 
@@ -851,10 +855,10 @@ out:
 		else
 			put_be64(mp->reservation_key, prkey);
 	}
-	print_reservation_key(buff, PRKEY_SIZE, mp->reservation_key,
+	print_reservation_key(&buff, mp->reservation_key,
 			      mp->sa_flags, mp->prkey_source);
-	condlog(3, "%s: reservation_key = %s %s%s", mp->alias, buff, origin,
-		from_file);
+	condlog(3, "%s: reservation_key = %s %s%s", mp->alias,
+		get_strbuf_str(&buff), origin, from_file);
 	return 0;
 }
 
@@ -951,16 +955,16 @@ use_delay_watch_checks(struct config *conf, struct multipath *mp)
 {
 	int value = NU_UNDEF;
 	const char *origin = default_origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	do_set(delay_watch_checks, mp->mpe, value, multipaths_origin);
 	do_set(delay_watch_checks, conf->overrides, value, overrides_origin);
 	do_set_from_hwe(delay_watch_checks, mp, value, hwe_origin);
 	do_set(delay_watch_checks, conf, value, conf_origin);
 out:
-	if (print_off_int_undef(buff, 12, value) != 0)
-		condlog(3, "%s: delay_watch_checks = %s %s", mp->alias, buff,
-			origin);
+	if (print_off_int_undef(&buff, value) > 0)
+		condlog(3, "%s: delay_watch_checks = %s %s", mp->alias,
+			get_strbuf_str(&buff), origin);
 	return value;
 }
 
@@ -969,23 +973,23 @@ use_delay_wait_checks(struct config *conf, struct multipath *mp)
 {
 	int value = NU_UNDEF;
 	const char *origin = default_origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	do_set(delay_wait_checks, mp->mpe, value, multipaths_origin);
 	do_set(delay_wait_checks, conf->overrides, value, overrides_origin);
 	do_set_from_hwe(delay_wait_checks, mp, value, hwe_origin);
 	do_set(delay_wait_checks, conf, value, conf_origin);
 out:
-	if (print_off_int_undef(buff, 12, value) != 0)
-		condlog(3, "%s: delay_wait_checks = %s %s", mp->alias, buff,
-			origin);
+	if (print_off_int_undef(&buff, value) > 0)
+		condlog(3, "%s: delay_wait_checks = %s %s", mp->alias,
+			get_strbuf_str(&buff), origin);
 	return value;
 }
 
 int select_delay_checks(struct config *conf, struct multipath *mp)
 {
 	int watch_checks, wait_checks;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	watch_checks = use_delay_watch_checks(conf, mp);
 	wait_checks = use_delay_wait_checks(conf, mp);
@@ -1001,16 +1005,17 @@ int select_delay_checks(struct config *conf, struct multipath *mp)
 		(watch_checks > 0)? delay_watch_origin : delay_wait_origin);
 	if (watch_checks > 0) {
 		mp->san_path_err_forget_rate = watch_checks;
-		print_off_int_undef(buff, 12, mp->san_path_err_forget_rate);
+		print_off_int_undef(&buff, mp->san_path_err_forget_rate);
 		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
-			buff, delay_watch_origin);
+			get_strbuf_str(&buff), delay_watch_origin);
+		reset_strbuf(&buff);
 	}
 	if (wait_checks > 0) {
 		mp->san_path_err_recovery_time = wait_checks *
 						 conf->max_checkint;
-		print_off_int_undef(buff, 12, mp->san_path_err_recovery_time);
+		print_off_int_undef(&buff, mp->san_path_err_recovery_time);
 		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
-			buff, delay_wait_origin);
+			get_strbuf_str(&buff), delay_wait_origin);
 	}
 	return 0;
 }
@@ -1029,7 +1034,7 @@ static int san_path_deprecated_warned;
 int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	if (marginal_path_check_enabled(mp)) {
 		mp->san_path_err_threshold = NU_NO;
@@ -1042,9 +1047,9 @@ int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
 	mp_set_conf(san_path_err_threshold);
 	mp_set_default(san_path_err_threshold, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12, mp->san_path_err_threshold) != 0)
+	if (print_off_int_undef(&buff, mp->san_path_err_threshold) > 0)
 		condlog(3, "%s: san_path_err_threshold = %s %s",
-			mp->alias, buff, origin);
+			mp->alias, get_strbuf_str(&buff), origin);
 	warn_san_path_deprecated(mp, san_path_err_threshold);
 	return 0;
 }
@@ -1052,7 +1057,7 @@ out:
 int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	if (marginal_path_check_enabled(mp)) {
 		mp->san_path_err_forget_rate = NU_NO;
@@ -1065,9 +1070,9 @@ int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
 	mp_set_conf(san_path_err_forget_rate);
 	mp_set_default(san_path_err_forget_rate, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12, mp->san_path_err_forget_rate) != 0)
-		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
-			buff, origin);
+	if (print_off_int_undef(&buff, mp->san_path_err_forget_rate) > 0)
+		condlog(3, "%s: san_path_err_forget_rate = %s %s",
+			mp->alias, get_strbuf_str(&buff), origin);
 	warn_san_path_deprecated(mp, san_path_err_forget_rate);
 	return 0;
 
@@ -1076,7 +1081,7 @@ out:
 int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	if (marginal_path_check_enabled(mp)) {
 		mp->san_path_err_recovery_time = NU_NO;
@@ -1089,9 +1094,9 @@ int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
 	mp_set_conf(san_path_err_recovery_time);
 	mp_set_default(san_path_err_recovery_time, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12, mp->san_path_err_recovery_time) != 0)
+	if (print_off_int_undef(&buff, mp->san_path_err_recovery_time) != 0)
 		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
-			buff, origin);
+			get_strbuf_str(&buff), origin);
 	warn_san_path_deprecated(mp, san_path_err_recovery_time);
 	return 0;
 
@@ -1100,7 +1105,7 @@ out:
 int select_marginal_path_err_sample_time(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(marginal_path_err_sample_time);
 	mp_set_ovr(marginal_path_err_sample_time);
@@ -1114,17 +1119,16 @@ out:
 			mp->alias, 2 * IOTIMEOUT_SEC);
 			mp->marginal_path_err_sample_time = 2 * IOTIMEOUT_SEC;
 	}
-	if (print_off_int_undef(buff, 12, mp->marginal_path_err_sample_time)
-	    != 0)
+	if (print_off_int_undef(&buff, mp->marginal_path_err_sample_time) > 0)
 		condlog(3, "%s: marginal_path_err_sample_time = %s %s",
-			mp->alias, buff, origin);
+			mp->alias, get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(marginal_path_err_rate_threshold);
 	mp_set_ovr(marginal_path_err_rate_threshold);
@@ -1132,17 +1136,16 @@ int select_marginal_path_err_rate_threshold(struct config *conf, struct multipat
 	mp_set_conf(marginal_path_err_rate_threshold);
 	mp_set_default(marginal_path_err_rate_threshold, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12, mp->marginal_path_err_rate_threshold)
-	    != 0)
+	if (print_off_int_undef(&buff, mp->marginal_path_err_rate_threshold) > 0)
 		condlog(3, "%s: marginal_path_err_rate_threshold = %s %s",
-			mp->alias, buff, origin);
+			mp->alias, get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(marginal_path_err_recheck_gap_time);
 	mp_set_ovr(marginal_path_err_recheck_gap_time);
@@ -1150,17 +1153,17 @@ int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multip
 	mp_set_conf(marginal_path_err_recheck_gap_time);
 	mp_set_default(marginal_path_err_recheck_gap_time, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12,
-				mp->marginal_path_err_recheck_gap_time) != 0)
+	if (print_off_int_undef(&buff,
+				mp->marginal_path_err_recheck_gap_time) > 0)
 		condlog(3, "%s: marginal_path_err_recheck_gap_time = %s %s",
-			mp->alias, buff, origin);
+			mp->alias, get_strbuf_str(&buff), origin);
 	return 0;
 }
 
 int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(marginal_path_double_failed_time);
 	mp_set_ovr(marginal_path_double_failed_time);
@@ -1168,10 +1171,9 @@ int select_marginal_path_double_failed_time(struct config *conf, struct multipat
 	mp_set_conf(marginal_path_double_failed_time);
 	mp_set_default(marginal_path_double_failed_time, DEFAULT_ERR_CHECKS);
 out:
-	if (print_off_int_undef(buff, 12, mp->marginal_path_double_failed_time)
-	    != 0)
+	if (print_off_int_undef(&buff, mp->marginal_path_double_failed_time) > 0)
 		condlog(3, "%s: marginal_path_double_failed_time = %s %s",
-			mp->alias, buff, origin);
+			mp->alias, get_strbuf_str(&buff), origin);
 	return 0;
 }
 
@@ -1215,7 +1217,7 @@ out:
 int select_ghost_delay (struct config *conf, struct multipath * mp)
 {
 	const char *origin;
-	char buff[12];
+	STRBUF_ON_STACK(buff);
 
 	mp_set_mpe(ghost_delay);
 	mp_set_ovr(ghost_delay);
@@ -1223,8 +1225,9 @@ int select_ghost_delay (struct config *conf, struct multipath * mp)
 	mp_set_conf(ghost_delay);
 	mp_set_default(ghost_delay, DEFAULT_GHOST_DELAY);
 out:
-	if (print_off_int_undef(buff, 12, mp->ghost_delay) != 0)
-		condlog(3, "%s: ghost_delay = %s %s", mp->alias, buff, origin);
+	if (print_off_int_undef(&buff, mp->ghost_delay) != 0)
+		condlog(3, "%s: ghost_delay = %s %s", mp->alias,
+			get_strbuf_str(&buff), origin);
 	return 0;
 }
 
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 5/9] libmultipath: use strbuf in print.c
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (3 preceding siblings ...)
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 4/9] libmultipath: use strbuf in dict.c mwilck
@ 2021-08-11 15:41 ` 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
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Use the strbuf API in print.c, wherever growing string buffers are
appropriate. This requires a large amount of changes in print.c itself,
and in other files that use functions from print.c. It makes no sense
to separate this into smaller patches though, as the various snprint_xyz()
functions depend on each other, and need to use similar prototypes.

libmultipath version must be bumped, as all the printing functions have
changed prototypes. Also, add the required strbuf functions to the
libmultipath API.

Change the libmultipath calls in prioritizers, foreign libs,
and in multipathd according to the strbuf-related API changes.

While working on print.c, I made a couple of other other minor changes:
 - all snprint_xyz() functions return a negative error in case of failure
   (most likely is -ENOMEM). In case of success, the number of printed
   characters is returned.
 - snprint_progress(): use fill_strbuf() rather than repeated iterations
 - _snprint_multipath(), _snprint_path(), snprint_multipath_header(),
   snprint_path_header(), _snprint_pathgroup(): use fill_strbuf()
   instead of the PAD/NOPAD logic.
 - _snprint_multipath_topology(): simplified the ASCII art generation for
   the topology tree somewhat.
 - snprint_json_xyz(): use fill_strbuf() rather than printing the indent
   repeatedly.
 - snprint_blacklist_group(), snprint_blacklist_devgroup(): combined two
   print statements into one.

In cli_handlers.c, fixed the returned values for the "len" parameter in
some functions (it's supposed to be the strlen of the reply + 1).

Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/blacklist.c                 |   13 +-
 libmultipath/discovery.c                 |   13 +-
 libmultipath/foreign.c                   |   78 +-
 libmultipath/foreign.h                   |   13 +-
 libmultipath/foreign/nvme.c              |  100 +-
 libmultipath/generic.c                   |   26 +-
 libmultipath/generic.h                   |   17 +-
 libmultipath/libmultipath.version        |   14 +-
 libmultipath/parser.c                    |    5 +-
 libmultipath/parser.h                    |    2 +-
 libmultipath/print.c                     | 1696 +++++++++-------------
 libmultipath/print.h                     |   67 +-
 libmultipath/prioritizers/weightedpath.c |   71 +-
 multipathd/cli_handlers.c                |  345 ++---
 14 files changed, 1000 insertions(+), 1460 deletions(-)

diff --git a/libmultipath/blacklist.c b/libmultipath/blacklist.c
index 6c6a597..4e315c9 100644
--- a/libmultipath/blacklist.c
+++ b/libmultipath/blacklist.c
@@ -14,6 +14,7 @@
 #include "blacklist.h"
 #include "structs_vec.h"
 #include "print.h"
+#include "strbuf.h"
 
 char *check_invert(char *str, bool *invert)
 {
@@ -341,19 +342,21 @@ int
 filter_protocol(const struct _vector *blist, const struct _vector *elist,
 		const struct path *pp)
 {
-	char buf[PROTOCOL_BUF_SIZE];
+	STRBUF_ON_STACK(buf);
+	const char *prot;
 	int r = MATCH_NOTHING;
 
 	if (pp) {
-		snprint_path_protocol(buf, sizeof(buf), pp);
+		snprint_path_protocol(&buf, pp);
+		prot = get_strbuf_str(&buf);
 
-		if (match_reglist(elist, buf))
+		if (match_reglist(elist, prot))
 			r = MATCH_PROTOCOL_BLIST_EXCEPT;
-		else if (match_reglist(blist, buf))
+		else if (match_reglist(blist, prot))
 			r = MATCH_PROTOCOL_BLIST;
+		log_filter(pp->dev, NULL, NULL, NULL, NULL, prot, r, 3);
 	}
 
-	log_filter(pp->dev, NULL, NULL, NULL, NULL, buf, r, 3);
 	return r;
 }
 
diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c
index e9f5703..f25fe9e 100644
--- a/libmultipath/discovery.c
+++ b/libmultipath/discovery.c
@@ -35,6 +35,7 @@
 #include "foreign.h"
 #include "configure.h"
 #include "print.h"
+#include "strbuf.h"
 
 struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE] = {
 	[VPD_VP_UNDEF]	= { 0x00, "undef" },
@@ -895,11 +896,11 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint)
 	}
 
 	if (err_path) {
-		char proto_buf[32];
+		STRBUF_ON_STACK(proto_buf);
 
-		snprint_path_protocol(proto_buf, sizeof(proto_buf), err_path);
+		snprint_path_protocol(&proto_buf, err_path);
 		condlog(2, "%s: setting dev_loss_tmo is unsupported for protocol %s",
-			mpp->alias, proto_buf);
+			mpp->alias, get_strbuf_str(&proto_buf));
 	}
 	return 0;
 }
@@ -2380,11 +2381,11 @@ int pathinfo(struct path *pp, struct config *conf, int mask)
 				 * It's likely that this path is not fit for
 				 * multipath use.
 				 */
-				char buf[16];
+				STRBUF_ON_STACK(buf);
 
-				snprint_path(buf, sizeof(buf), "%T", pp, 0);
+				snprint_path(&buf, "%T", pp, 0);
 				condlog(1, "%s: no WWID in state \"%s\", giving up",
-					pp->dev, buf);
+					pp->dev, get_strbuf_str(&buf));
 				return PATHINFO_SKIPPED;
 			}
 			return PATHINFO_OK;
diff --git a/libmultipath/foreign.c b/libmultipath/foreign.c
index fce1934..e091a1d 100644
--- a/libmultipath/foreign.c
+++ b/libmultipath/foreign.c
@@ -34,6 +34,7 @@
 #include "structs.h"
 #include "structs_vec.h"
 #include "print.h"
+#include "strbuf.h"
 
 static vector foreigns;
 
@@ -497,11 +498,11 @@ void foreign_multipath_layout(void)
 	pthread_cleanup_pop(1);
 }
 
-int snprint_foreign_topology(char *buf, int len, int verbosity)
+int snprint_foreign_topology(struct strbuf *buf, int verbosity)
 {
 	struct foreign *fgn;
 	int i;
-	char *c = buf;
+	size_t initial_len = get_strbuf_len(buf);
 
 	rdlock_foreigns();
 	if (foreigns == NULL) {
@@ -521,58 +522,32 @@ int snprint_foreign_topology(char *buf, int len, int verbosity)
 		vec = fgn->get_multipaths(fgn->context);
 		if (vec != NULL) {
 			vector_foreach_slot(vec, gm, j) {
-
-				c += _snprint_multipath_topology(gm, c,
-								 buf + len - c,
-								 verbosity);
-				if (c >= buf + len - 1)
+				if (_snprint_multipath_topology(
+					    gm, buf, verbosity) < 0)
 					break;
 			}
-			if (c >= buf + len - 1)
-				break;
 		}
 		fgn->release_multipaths(fgn->context, vec);
 		pthread_cleanup_pop(1);
 	}
 
 	pthread_cleanup_pop(1);
-	return c - buf;
+	return get_strbuf_len(buf) - initial_len;
 }
 
 void print_foreign_topology(int verbosity)
 {
-	int buflen = MAX_LINE_LEN * MAX_LINES;
-	char *buf = NULL, *tmp = NULL;
-
-	buf = calloc(1, buflen);
-
-	while (buf != NULL) {
-		char *c = buf;
-
-		c += snprint_foreign_topology(buf, buflen,
-						   verbosity);
-		if (c < buf + buflen - 1)
-			break;
-
-		buflen *= 2;
-		tmp = buf;
-		buf = realloc(buf, buflen);
-	}
+	STRBUF_ON_STACK(buf);
 
-	if (buf == NULL && tmp != NULL)
-		buf = tmp;
-
-	if (buf != NULL) {
-		printf("%s", buf);
-		free(buf);
-	}
+	snprint_foreign_topology(&buf, verbosity);
+	printf("%s", get_strbuf_str(&buf));
 }
 
-int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
+int snprint_foreign_paths(struct strbuf *buf, const char *style, int pretty)
 {
 	struct foreign *fgn;
 	int i;
-	char *c = buf;
+	size_t initial_len = get_strbuf_len(buf);
 
 	rdlock_foreigns();
 	if (foreigns == NULL) {
@@ -584,7 +559,7 @@ int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
 	vector_foreach_slot(foreigns, fgn, i) {
 		const struct _vector *vec;
 		const struct gen_path *gp;
-		int j;
+		int j, ret = 0;
 
 		fgn->lock(fgn->context);
 		pthread_cleanup_push(fgn->unlock, fgn->context);
@@ -592,28 +567,27 @@ int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
 		vec = fgn->get_paths(fgn->context);
 		if (vec != NULL) {
 			vector_foreach_slot(vec, gp, j) {
-				c += _snprint_path(gp, c, buf + len - c,
-						   style, pretty);
-				if (c >= buf + len - 1)
+				ret = _snprint_path(gp, buf, style, pretty);
+				if (ret < 0)
 					break;
 			}
-			if (c >= buf + len - 1)
-				break;
 		}
 		fgn->release_paths(fgn->context, vec);
 		pthread_cleanup_pop(1);
+		if (ret < 0)
+			break;
 	}
 
 	pthread_cleanup_pop(1);
-	return c - buf;
+	return get_strbuf_len(buf) - initial_len;
 }
 
-int snprint_foreign_multipaths(char *buf, int len,
+int snprint_foreign_multipaths(struct strbuf *buf,
 			       const char *style, int pretty)
 {
 	struct foreign *fgn;
 	int i;
-	char *c = buf;
+	size_t initial_len = get_strbuf_len(buf);
 
 	rdlock_foreigns();
 	if (foreigns == NULL) {
@@ -625,7 +599,7 @@ int snprint_foreign_multipaths(char *buf, int len,
 	vector_foreach_slot(foreigns, fgn, i) {
 		const struct _vector *vec;
 		const struct gen_multipath *gm;
-		int j;
+		int j, ret = 0;
 
 		fgn->lock(fgn->context);
 		pthread_cleanup_push(fgn->unlock, fgn->context);
@@ -633,18 +607,18 @@ int snprint_foreign_multipaths(char *buf, int len,
 		vec = fgn->get_multipaths(fgn->context);
 		if (vec != NULL) {
 			vector_foreach_slot(vec, gm, j) {
-				c += _snprint_multipath(gm, c, buf + len - c,
-							style, pretty);
-				if (c >= buf + len - 1)
+				ret = _snprint_multipath(gm, buf,
+							 style, pretty);
+				if (ret < 0)
 					break;
 			}
-			if (c >= buf + len - 1)
-				break;
 		}
 		fgn->release_multipaths(fgn->context, vec);
 		pthread_cleanup_pop(1);
+		if (ret < 0)
+			break;
 	}
 
 	pthread_cleanup_pop(1);
-	return c - buf;
+	return get_strbuf_len(buf) - initial_len;
 }
diff --git a/libmultipath/foreign.h b/libmultipath/foreign.h
index acd3360..77fc485 100644
--- a/libmultipath/foreign.h
+++ b/libmultipath/foreign.h
@@ -18,9 +18,9 @@
 #define _FOREIGN_H
 #include <stdbool.h>
 #include <libudev.h>
+#define LIBMP_FOREIGN_API ((1 << 8) | 1)
 
-#define LIBMP_FOREIGN_API ((1 << 8) | 0)
-
+struct strbuf;
 struct context;
 
 /* return codes of functions below returning "int" */
@@ -267,35 +267,32 @@ void foreign_multipath_layout(void);
  * prints topology information from foreign libraries into buffer,
  * '\0' - terminated.
  * @param buf: output buffer
- * @param len: size of output buffer
  * @param verbosity: verbosity level
  * @returns: number of printed characters excluding trailing '\0'.
  */
-int snprint_foreign_topology(char *buf, int len, int verbosity);
+int snprint_foreign_topology(struct strbuf *buf, int verbosity);
 
 /**
  * snprint_foreign_paths(buf, len, style, pad);
  * prints formatted path information from foreign libraries into buffer,
  * '\0' - terminated.
  * @param buf: output buffer
- * @param len: size of output buffer
  * @param style: format string
  * @param pad: whether to pad field width
  * @returns: number of printed characters excluding trailing '\0'.
  */
-int snprint_foreign_paths(char *buf, int len, const char *style, int pad);
+int snprint_foreign_paths(struct strbuf *buf, const char *style, int pad);
 
 /**
  * snprint_foreign_multipaths(buf, len, style, pad);
  * prints formatted map information from foreign libraries into buffer,
  * '\0' - terminated.
  * @param buf: output buffer
- * @param len: size of output buffer
  * @param style: format string
  * @param pad: whether to pad field width
  * @returns: number of printed characters excluding trailing '\0'.
  */
-int snprint_foreign_multipaths(char *buf, int len,
+int snprint_foreign_multipaths(struct strbuf *buf,
 			       const char *style, int pretty);
 
 /**
diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c
index b726be2..d40c086 100644
--- a/libmultipath/foreign/nvme.c
+++ b/libmultipath/foreign/nvme.c
@@ -37,6 +37,7 @@
 #include "debug.h"
 #include "structs.h"
 #include "sysfs.h"
+#include "strbuf.h"
 
 static const char nvme_vendor[] = "NVMe";
 static const char N_A[] = "n/a";
@@ -138,7 +139,7 @@ static void rstrip(char *str)
 }
 
 static int snprint_nvme_map(const struct gen_multipath *gmp,
-			    char *buff, int len, char wildcard)
+			    struct strbuf *buff, char wildcard)
 {
 	const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp);
 	char fld[NAME_LEN];
@@ -146,26 +147,26 @@ static int snprint_nvme_map(const struct gen_multipath *gmp,
 
 	switch (wildcard) {
 	case 'd':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysname(nvm->udev));
 	case 'n':
-		return snprintf(buff, len, "%s:nsid.%s",
+		return print_strbuf(buff, "%s:nsid.%s",
 				udev_device_get_sysattr_value(nvm->subsys,
 							      "subsysnqn"),
 				udev_device_get_sysattr_value(nvm->udev,
 							      "nsid"));
 	case 'w':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysattr_value(nvm->udev,
 							      "wwid"));
 	case 'N':
-		return snprintf(buff, len, "%u", nvm->nr_live);
+		return print_strbuf(buff, "%u", nvm->nr_live);
 	case 'S':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysattr_value(nvm->udev,
 							      "size"));
 	case 'v':
-		return snprintf(buff, len, "%s", nvme_vendor);
+		return append_strbuf_str(buff, nvme_vendor);
 	case 's':
 	case 'p':
 		snprintf(fld, sizeof(fld), "%s",
@@ -173,30 +174,30 @@ static int snprint_nvme_map(const struct gen_multipath *gmp,
 						      "model"));
 		rstrip(fld);
 		if (wildcard == 'p')
-			return snprintf(buff, len, "%s", fld);
-		return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
+			return append_strbuf_str(buff, fld);
+		return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld,
 				udev_device_get_sysattr_value(nvm->subsys,
 							      "firmware_rev"));
 	case 'e':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysattr_value(nvm->subsys,
 							      "firmware_rev"));
 	case 'r':
 		val = udev_device_get_sysattr_value(nvm->udev, "ro");
 		if (val[0] == 1)
-			return snprintf(buff, len, "%s", "ro");
+			return append_strbuf_str(buff, "ro");
 		else
-			return snprintf(buff, len, "%s", "rw");
+			return append_strbuf_str(buff, "rw");
 	case 'G':
-		return snprintf(buff, len, "%s", THIS);
+		return append_strbuf_str(buff, THIS);
 	case 'h':
 		if (nvm->ana_supported == YNU_YES)
-			return snprintf(buff, len, "ANA");
+			return append_strbuf_str(buff, "ANA");
 	default:
 		break;
 	}
 
-	return snprintf(buff, len, N_A);
+	return append_strbuf_str(buff, N_A);
 }
 
 static const struct _vector*
@@ -214,7 +215,7 @@ nvme_pg_rel_paths(__attribute__((unused)) const struct gen_pathgroup *gpg,
 	/* empty */
 }
 
-static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
+static int snprint_hcil(const struct nvme_path *np, struct strbuf *buf)
 {
 	unsigned int nvmeid, ctlid, nsid;
 	int rc;
@@ -223,14 +224,13 @@ static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
 	rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid);
 	if (rc != 3) {
 		condlog(1, "%s: failed to scan %s", __func__, sysname);
-		rc = snprintf(buf, len, "(ERR:%s)", sysname);
+		return print_strbuf(buf, "(ERR:%s)", sysname);
 	} else
-		rc = snprintf(buf, len, "%u:%u:%u", nvmeid, ctlid, nsid);
-	return (rc < len ? rc : len);
+		return print_strbuf(buf, "%u:%u:%u", nvmeid, ctlid, nsid);
 }
 
 static int snprint_nvme_path(const struct gen_path *gp,
-			     char *buff, int len, char wildcard)
+			     struct strbuf *buff, char wildcard)
 {
 	const struct nvme_path *np = const_gen_path_to_nvme(gp);
 	dev_t devt;
@@ -239,37 +239,37 @@ static int snprint_nvme_path(const struct gen_path *gp,
 
 	switch (wildcard) {
 	case 'w':
-		return snprintf(buff, len, "%s",
-				udev_device_get_sysattr_value(np->udev,
-							      "wwid"));
+		return print_strbuf(buff, "%s",
+				    udev_device_get_sysattr_value(np->udev,
+								  "wwid"));
 	case 'd':
-		return snprintf(buff, len, "%s",
-				udev_device_get_sysname(np->udev));
+		return print_strbuf(buff, "%s",
+				    udev_device_get_sysname(np->udev));
 	case 'i':
-		return snprint_hcil(np, buff, len);
+		return snprint_hcil(np, buff);
 	case 'D':
 		devt = udev_device_get_devnum(np->udev);
-		return snprintf(buff, len, "%u:%u", major(devt), minor(devt));
+		return print_strbuf(buff, "%u:%u", major(devt), minor(devt));
 	case 'o':
 		if (sysfs_attr_get_value(np->ctl, "state",
 					 fld, sizeof(fld)) > 0)
-			return snprintf(buff, len, "%s", fld);
+			return append_strbuf_str(buff, fld);
 		break;
 	case 'T':
 		if (sysfs_attr_get_value(np->udev, "ana_state", fld,
 					 sizeof(fld)) > 0)
-			return snprintf(buff, len, "%s", fld);
+			return append_strbuf_str(buff, fld);
 		break;
 	case 'p':
 		if (sysfs_attr_get_value(np->udev, "ana_state", fld,
 					 sizeof(fld)) > 0) {
 			rstrip(fld);
 			if (!strcmp(fld, "optimized"))
-				return snprintf(buff, len, "%d", 50);
+				return print_strbuf(buff, "%d", 50);
 			else if (!strcmp(fld, "non-optimized"))
-				return snprintf(buff, len, "%d", 10);
+				return print_strbuf(buff, "%d", 10);
 			else
-				return snprintf(buff, len, "%d", 0);
+				return print_strbuf(buff, "%d", 0);
 		}
 		break;
 	case 's':
@@ -277,46 +277,45 @@ static int snprint_nvme_path(const struct gen_path *gp,
 			 udev_device_get_sysattr_value(np->ctl,
 						      "model"));
 		rstrip(fld);
-		return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
-				udev_device_get_sysattr_value(np->ctl,
+		return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld,
+				    udev_device_get_sysattr_value(np->ctl,
 							      "firmware_rev"));
 	case 'S':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 			udev_device_get_sysattr_value(np->udev,
 						      "size"));
 	case 'z':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysattr_value(np->ctl,
 							      "serial"));
 	case 'm':
-		return snprintf(buff, len, "%s",
+		return append_strbuf_str(buff,
 				udev_device_get_sysname(np->map->udev));
 	case 'N':
 	case 'R':
-		return snprintf(buff, len, "%s:%s",
+		return print_strbuf(buff, "%s:%s",
 			udev_device_get_sysattr_value(np->ctl,
 						      "transport"),
 			udev_device_get_sysattr_value(np->ctl,
 						      "address"));
 	case 'G':
-		return snprintf(buff, len, "[%s]", THIS);
+		return print_strbuf(buff, "[%s]", THIS);
 	case 'a':
 		pci = udev_device_get_parent_with_subsystem_devtype(np->ctl,
 								    "pci",
 								    NULL);
 		if (pci != NULL)
-			return snprintf(buff, len, "PCI:%s",
-					udev_device_get_sysname(pci));
+			return print_strbuf(buff, "PCI:%s",
+					    udev_device_get_sysname(pci));
 		/* fall through */
 	default:
 		break;
 	}
-	return snprintf(buff, len, "%s", N_A);
-	return 0;
+	return append_strbuf_str(buff, N_A);
 }
 
 static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
-			   char *buff, int len, char wildcard)
+			   struct strbuf *buff, char wildcard)
 {
 	const struct nvme_pathgroup *pg = const_gen_pg_to_nvme(gmp);
 	const struct nvme_path *path = nvme_pg_to_path(pg);
@@ -324,22 +323,19 @@ static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
 	switch (wildcard) {
 	case 't':
 		return snprint_nvme_path(nvme_path_to_gen(path),
-					 buff, len, 'T');
+					 buff, 'T');
 	case 'p':
 		return snprint_nvme_path(nvme_path_to_gen(path),
-					 buff, len, 'p');
+					 buff, 'p');
 	default:
-		return snprintf(buff, len, N_A);
+		return append_strbuf_str(buff, N_A);
 	}
 }
 
 static int nvme_style(__attribute__((unused)) const struct gen_multipath* gm,
-		      char *buf, int len,
-		      __attribute__((unused)) int verbosity)
+		      struct strbuf *buf, __attribute__((unused)) int verbosity)
 {
-	int n = snprintf(buf, len, "%%w [%%G]:%%d %%s");
-
-	return (n < len ? n : len - 1);
+	return append_strbuf_str(buf, "%%w [%%G]:%%d %%s");
 }
 
 static const struct gen_multipath_ops nvme_map_ops = {
diff --git a/libmultipath/generic.c b/libmultipath/generic.c
index 5f03b9e..e7cf297 100644
--- a/libmultipath/generic.c
+++ b/libmultipath/generic.c
@@ -15,23 +15,23 @@
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-
-#include <string.h>
 #include "generic.h"
 #include "structs.h"
+#include "util.h"
+#include "strbuf.h"
 
-int generic_style(const struct gen_multipath* gm,
-		  char *buf, int len, __attribute__((unused)) int verbosity)
+int generic_style(const struct gen_multipath* gm, struct strbuf *buf,
+		  __attribute__((unused)) int verbosity)
 {
-	char alias_buf[WWID_SIZE];
-	char wwid_buf[WWID_SIZE];
-	int n = 0;
-
-	gm->ops->snprint(gm, alias_buf, sizeof(alias_buf), 'n');
-	gm->ops->snprint(gm, wwid_buf, sizeof(wwid_buf), 'w');
+	STRBUF_ON_STACK(tmp);
+	char *alias_buf __attribute__((cleanup(cleanup_charp)));
+	const char *wwid_buf;
 
-	n += snprintf(buf, len, "%%n %s[%%G]:%%d %%s",
-		      strcmp(alias_buf, wwid_buf) ? "(%w) " : "");
+	gm->ops->snprint(gm, &tmp, 'n');
+	alias_buf = steal_strbuf_str(&tmp);
+	gm->ops->snprint(gm, &tmp, 'w');
+	wwid_buf = get_strbuf_str(&tmp);
 
-	return (n < len ? n : len - 1);
+	return print_strbuf(buf, "%%n %s[%%G]:%%d %%s",
+			    strcmp(alias_buf, wwid_buf) ? "(%w) " : "");
 }
diff --git a/libmultipath/generic.h b/libmultipath/generic.h
index 6346ffe..57c123c 100644
--- a/libmultipath/generic.h
+++ b/libmultipath/generic.h
@@ -18,6 +18,7 @@
 #define _GENERIC_H
 #include "vector.h"
 
+struct strbuf;
 struct gen_multipath;
 struct gen_pathgroup;
 struct gen_path;
@@ -50,26 +51,24 @@ struct gen_multipath_ops {
 	 * 0-terminated, no more than "len" characters including trailing '\0'.
 	 *
 	 * @param gmp: generic multipath object to act on
-	 * @param buf: output buffer
-	 * @param buflen: buffer size
+	 * @param buf: output struct strbuf
 	 * @param wildcard: the multipath wildcard (see print.c)
 	 * @returns the number of characters printed (without trailing '\0').
 	 */
 	int (*snprint)(const struct gen_multipath*,
-		       char *buf, int len, char wildcard);
+		       struct strbuf *buf, char wildcard);
 	/**
 	 * method: style(gmp, buf, len, verbosity)
 	 * returns the format string to be used for the multipath object,
 	 * defined with the wildcards as defined in print.c
 	 * generic_style() should work well in most cases.
 	 * @param gmp: generic multipath object to act on
-	 * @param buf: output buffer
-	 * @param buflen: buffer size
+	 * @param buf: output strbuf
 	 * @param verbosity: verbosity level
 	 * @returns number of format chars printed
 	 */
 	int (*style)(const struct gen_multipath*,
-		     char *buf, int len, int verbosity);
+		     struct strbuf *buf, int verbosity);
 };
 
 /**
@@ -95,7 +94,7 @@ struct gen_pathgroup_ops {
 	 * see gen_multipath_ops->snprint() above
 	 */
 	int (*snprint)(const struct gen_pathgroup*,
-		       char *buf, int len, char wildcard);
+		       struct strbuf *buf, char wildcard);
 };
 
 struct gen_path_ops {
@@ -104,7 +103,7 @@ struct gen_path_ops {
 	 * see gen_multipath_ops->snprint() above
 	 */
 	int (*snprint)(const struct gen_path*,
-		       char *buf, int len, char wildcard);
+		       struct strbuf *buf, char wildcard);
 };
 
 struct gen_multipath {
@@ -129,6 +128,6 @@ struct gen_path {
  * foreign libraries.
  */
 int generic_style(const struct gen_multipath*,
-		  char *buf, int len, int verbosity);
+		  struct strbuf *buf, int verbosity);
 
 #endif /* _GENERIC_H */
diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
index 6dd52c2..1d84d97 100644
--- a/libmultipath/libmultipath.version
+++ b/libmultipath/libmultipath.version
@@ -31,7 +31,7 @@
  *   The new version inherits the previous ones.
  */
 
-LIBMULTIPATH_7.0.0 {
+LIBMULTIPATH_8.0.0 {
 global:
 	/* symbols referenced by multipath and multipathd */
 	add_foreign;
@@ -273,3 +273,15 @@ global:
 local:
 	*;
 };
+
+LIBMULTIPATH_8.1.0 {
+global:
+	reset_strbuf;
+	append_strbuf_str;
+	get_strbuf_len;
+	get_strbuf_str;
+	steal_strbuf_str;
+	fill_strbuf;
+	print_strbuf;
+	truncate_strbuf;
+} LIBMULTIPATH_8.0.0;
diff --git a/libmultipath/parser.c b/libmultipath/parser.c
index 88c7b59..8ca91bf 100644
--- a/libmultipath/parser.c
+++ b/libmultipath/parser.c
@@ -150,7 +150,7 @@ find_keyword(vector keywords, vector v, char * name)
 }
 
 int
-snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
+snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw,
 		const void *data)
 {
 	int r;
@@ -191,7 +191,8 @@ snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
 		}
 	} while (*fmt++);
 out:
-	return snprintf(buff, len, "%s", get_strbuf_str(&sbuf));
+	return __append_strbuf_str(buff, get_strbuf_str(&sbuf),
+				   get_strbuf_len(&sbuf));
 }
 
 static const char quote_marker[] = { '\0', '"', '\0' };
diff --git a/libmultipath/parser.h b/libmultipath/parser.h
index e8b6eb2..b43d46f 100644
--- a/libmultipath/parser.h
+++ b/libmultipath/parser.h
@@ -82,7 +82,7 @@ extern vector alloc_strvec(char *string);
 extern void *set_value(vector strvec);
 extern int process_file(struct config *conf, const char *conf_file);
 extern struct keyword * find_keyword(vector keywords, vector v, char * name);
-int snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
+int snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw,
 		    const void *data);
 bool is_quote(const char* token);
 
diff --git a/libmultipath/print.c b/libmultipath/print.c
index 29ce499..dd04dea 100644
--- a/libmultipath/print.c
+++ b/libmultipath/print.c
@@ -1,4 +1,4 @@
- /*
+/*
  * Copyright (c) 2005 Christophe Varoqui
  */
 #include <stdio.h>
@@ -31,59 +31,33 @@
 #include "discovery.h"
 #include "util.h"
 #include "foreign.h"
+#include "strbuf.h"
 
 #define MAX(x,y) (((x) > (y)) ? (x) : (y))
 #define MIN(x,y) (((x) > (y)) ? (y) : (x))
-#define TAIL     (line + len - 1 - c)
-#define NOPAD    s = c
-#define PAD(x) \
-do { \
-	while (c < (s + x) && (c < (line + len - 1))) \
-		*c++ = ' '; \
-	s = c; \
-} while (0)
-
-static char *
-__endline(char *line, size_t len, char *c)
-{
-	if (c > line) {
-		if (c >= line + len)
-			c = line + len - 1;
-		*(c - 1) = '\n';
-		*c = '\0';
-	}
-	return c;
-}
-
-#define PRINT(var, size, format, args...) \
-do { \
-	fwd = snprintf(var, size, format, ##args); \
-	c += (fwd >= size) ? size : fwd; \
-} while (0)
-
 /*
  * information printing helpers
  */
 static int
-snprint_str (char * buff, size_t len, const char * str)
+snprint_str(struct strbuf *buff, const char *str)
 {
-	return snprintf(buff, len, "%s", str);
+	return append_strbuf_str(buff, str);
 }
 
 static int
-snprint_int (char * buff, size_t len, int val)
+snprint_int (struct strbuf *buff, int val)
 {
-	return snprintf(buff, len, "%i", val);
+	return print_strbuf(buff, "%i", val);
 }
 
 static int
-snprint_uint (char * buff, size_t len, unsigned int val)
+snprint_uint (struct strbuf *buff, unsigned int val)
 {
-	return snprintf(buff, len, "%u", val);
+	return print_strbuf(buff, "%u", val);
 }
 
 static int
-snprint_size (char * buff, size_t len, unsigned long long size)
+snprint_size (struct strbuf *buff, unsigned long long size)
 {
 	float s = (float)(size >> 1); /* start with KB */
 	char units[] = {'K','M','G','T','P'};
@@ -94,184 +68,177 @@ snprint_size (char * buff, size_t len, unsigned long long size)
 		u++;
 	}
 
-	return snprintf(buff, len, "%.*f%c", s < 10, s, *u);
+	return print_strbuf(buff, "%.*f%c", s < 10, s, *u);
 }
 
 /*
  * multipath info printing functions
  */
 static int
-snprint_name (char * buff, size_t len, const struct multipath * mpp)
+snprint_name (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (mpp->alias)
-		return snprintf(buff, len, "%s", mpp->alias);
+		return append_strbuf_str(buff, mpp->alias);
 	else
-		return snprintf(buff, len, "%s", mpp->wwid);
+		return append_strbuf_str(buff, mpp->wwid);
 }
 
 static int
-snprint_sysfs (char * buff, size_t len, const struct multipath * mpp)
+snprint_sysfs (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (mpp->dmi)
-		return snprintf(buff, len, "dm-%i", mpp->dmi->minor);
+		return print_strbuf(buff, "dm-%i", mpp->dmi->minor);
 	else
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 }
 
 static int
-snprint_ro (char * buff, size_t len, const struct multipath * mpp)
+snprint_ro (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (!mpp->dmi)
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 	if (mpp->dmi->read_only)
-		return snprintf(buff, len, "ro");
+		return append_strbuf_str(buff, "ro");
 	else
-		return snprintf(buff, len, "rw");
+		return append_strbuf_str(buff, "rw");
 }
 
 static int
-snprint_progress (char * buff, size_t len, int cur, int total)
+snprint_progress (struct strbuf *buff, int cur, int total)
 {
-	char * c = buff;
-	char * end = buff + len;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc;
 
 	if (total > 0) {
 		int i = PROGRESS_LEN * cur / total;
 		int j = PROGRESS_LEN - i;
 
-		while (i-- > 0) {
-			c += snprintf(c, len, "X");
-			if ((len = (end - c)) <= 1) goto out;
-		}
-
-		while (j-- > 0) {
-			c += snprintf(c, len,  ".");
-			if ((len = (end - c)) <= 1) goto out;
+		if ((rc = fill_strbuf(buff, 'X', i)) < 0 ||
+		    (rc = fill_strbuf(buff, '.', j) < 0)) {
+			truncate_strbuf(buff, initial_len);
+			return rc;
 		}
 	}
 
-	c += snprintf(c, len, " %i/%i", cur, total);
-
-out:
-	buff[c - buff + 1] = '\0';
-	return (c - buff + 1);
+	if ((rc = print_strbuf(buff, " %i/%i", cur, total)) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 static int
-snprint_failback (char * buff, size_t len, const struct multipath * mpp)
+snprint_failback (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (mpp->pgfailback == -FAILBACK_IMMEDIATE)
-		return snprintf(buff, len, "immediate");
+		return append_strbuf_str(buff, "immediate");
 	if (mpp->pgfailback == -FAILBACK_FOLLOWOVER)
-		return snprintf(buff, len, "followover");
+		return append_strbuf_str(buff, "followover");
 
 	if (!mpp->failback_tick)
-		return snprintf(buff, len, "-");
+		return append_strbuf_str(buff, "-");
 	else
-		return snprint_progress(buff, len, mpp->failback_tick,
+		return snprint_progress(buff, mpp->failback_tick,
 					mpp->pgfailback);
 }
 
 static int
-snprint_queueing (char * buff, size_t len, const struct multipath * mpp)
+snprint_queueing (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (mpp->no_path_retry == NO_PATH_RETRY_FAIL)
-		return snprintf(buff, len, "off");
+		return append_strbuf_str(buff, "off");
 	else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE)
-		return snprintf(buff, len, "on");
+		return append_strbuf_str(buff, "on");
 	else if (mpp->no_path_retry == NO_PATH_RETRY_UNDEF)
-		return snprintf(buff, len, "-");
+		return append_strbuf_str(buff, "-");
 	else if (mpp->no_path_retry > 0) {
 		if (mpp->retry_tick > 0)
 
-			return snprintf(buff, len, "%i sec",
-					mpp->retry_tick);
+			return print_strbuf(buff, "%i sec", mpp->retry_tick);
 		else if (mpp->retry_tick == 0 && count_active_paths(mpp) > 0)
-			return snprintf(buff, len, "%i chk",
-					mpp->no_path_retry);
+			return print_strbuf(buff, "%i chk",
+					    mpp->no_path_retry);
 		else
-			return snprintf(buff, len, "off");
+			return append_strbuf_str(buff, "off");
 	}
 	return 0;
 }
 
 static int
-snprint_nb_paths (char * buff, size_t len, const struct multipath * mpp)
+snprint_nb_paths (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_int(buff, len, count_active_paths(mpp));
+	return snprint_int(buff, count_active_paths(mpp));
 }
 
 static int
-snprint_dm_map_state (char * buff, size_t len, const struct multipath * mpp)
+snprint_dm_map_state (struct strbuf *buff, const struct multipath * mpp)
 {
 	if (mpp->dmi && mpp->dmi->suspended)
-		return snprintf(buff, len, "suspend");
+		return append_strbuf_str(buff, "suspend");
 	else
-		return snprintf(buff, len, "active");
+		return append_strbuf_str(buff, "active");
 }
 
 static int
-snprint_multipath_size (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_size (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_size(buff, len, mpp->size);
+	return snprint_size(buff, mpp->size);
 }
 
 static int
-snprint_features (char * buff, size_t len, const struct multipath * mpp)
+snprint_features (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_str(buff, len, mpp->features);
+	return snprint_str(buff, mpp->features);
 }
 
 static int
-snprint_hwhandler (char * buff, size_t len, const struct multipath * mpp)
+snprint_hwhandler (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_str(buff, len, mpp->hwhandler);
+	return snprint_str(buff, mpp->hwhandler);
 }
 
 static int
-snprint_path_faults (char * buff, size_t len, const struct multipath * mpp)
+snprint_path_faults (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_path_failures);
+	return snprint_uint(buff, mpp->stat_path_failures);
 }
 
 static int
-snprint_switch_grp (char * buff, size_t len, const struct multipath * mpp)
+snprint_switch_grp (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_switchgroup);
+	return snprint_uint(buff, mpp->stat_switchgroup);
 }
 
 static int
-snprint_map_loads (char * buff, size_t len, const struct multipath * mpp)
+snprint_map_loads (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_map_loads);
+	return snprint_uint(buff, mpp->stat_map_loads);
 }
 
 static int
-snprint_total_q_time (char * buff, size_t len, const struct multipath * mpp)
+snprint_total_q_time (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_total_queueing_time);
+	return snprint_uint(buff, mpp->stat_total_queueing_time);
 }
 
 static int
-snprint_q_timeouts (char * buff, size_t len, const struct multipath * mpp)
+snprint_q_timeouts (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_queueing_timeouts);
+	return snprint_uint(buff, mpp->stat_queueing_timeouts);
 }
 
 static int
-snprint_map_failures (char * buff, size_t len, const struct multipath * mpp)
+snprint_map_failures (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_uint(buff, len, mpp->stat_map_failures);
+	return snprint_uint(buff, mpp->stat_map_failures);
 }
 
 static int
-snprint_multipath_uuid (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_uuid (struct strbuf *buff, const struct multipath * mpp)
 {
-	return snprint_str(buff, len, mpp->wwid);
+	return snprint_str(buff, mpp->wwid);
 }
 
 static int
-snprint_multipath_vpr (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_vpr (struct strbuf *buff, const struct multipath * mpp)
 {
 	struct pathgroup * pgp;
 	struct path * pp;
@@ -280,16 +247,16 @@ snprint_multipath_vpr (char * buff, size_t len, const struct multipath * mpp)
 	vector_foreach_slot(mpp->pg, pgp, i) {
 		vector_foreach_slot(pgp->paths, pp, j) {
 			if (strlen(pp->vendor_id) && strlen(pp->product_id))
-				return snprintf(buff, len, "%s,%s",
-						pp->vendor_id, pp->product_id);
+				return print_strbuf(buff, "%s,%s",
+						    pp->vendor_id, pp->product_id);
 		}
 	}
-	return snprintf(buff, len, "##,##");
+	return append_strbuf_str(buff, "##,##");
 }
 
 
 static int
-snprint_multipath_vend (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_vend (struct strbuf *buff, const struct multipath * mpp)
 {
 	struct pathgroup * pgp;
 	struct path * pp;
@@ -298,14 +265,14 @@ snprint_multipath_vend (char * buff, size_t len, const struct multipath * mpp)
 	vector_foreach_slot(mpp->pg, pgp, i) {
 		vector_foreach_slot(pgp->paths, pp, j) {
 			if (strlen(pp->vendor_id))
-				return snprintf(buff, len, "%s", pp->vendor_id);
+				return append_strbuf_str(buff, pp->vendor_id);
 		}
 	}
-	return snprintf(buff, len, "##");
+	return append_strbuf_str(buff, "##");
 }
 
 static int
-snprint_multipath_prod (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_prod (struct strbuf *buff, const struct multipath * mpp)
 {
 	struct pathgroup * pgp;
 	struct path * pp;
@@ -314,14 +281,14 @@ snprint_multipath_prod (char * buff, size_t len, const struct multipath * mpp)
 	vector_foreach_slot(mpp->pg, pgp, i) {
 		vector_foreach_slot(pgp->paths, pp, j) {
 			if (strlen(pp->product_id))
-				return snprintf(buff, len, "%s", pp->product_id);
+				return append_strbuf_str(buff, pp->product_id);
 		}
 	}
-	return snprintf(buff, len, "##");
+	return append_strbuf_str(buff, "##");
 }
 
 static int
-snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp)
+snprint_multipath_rev (struct strbuf *buff, const struct multipath * mpp)
 {
 	struct pathgroup * pgp;
 	struct path * pp;
@@ -330,40 +297,40 @@ snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp)
 	vector_foreach_slot(mpp->pg, pgp, i) {
 		vector_foreach_slot(pgp->paths, pp, j) {
 			if (strlen(pp->rev))
-				return snprintf(buff, len, "%s", pp->rev);
+				return append_strbuf_str(buff, pp->rev);
 		}
 	}
-	return snprintf(buff, len, "##");
+	return append_strbuf_str(buff, "##");
 }
 
 static int
-snprint_multipath_foreign (char * buff, size_t len,
+snprint_multipath_foreign (struct strbuf *buff,
 			   __attribute__((unused)) const struct multipath * pp)
 {
-	return snprintf(buff, len, "%s", "--");
+	return append_strbuf_str(buff, "--");
 }
 
 static int
-snprint_action (char * buff, size_t len, const struct multipath * mpp)
+snprint_action (struct strbuf *buff, const struct multipath * mpp)
 {
 	switch (mpp->action) {
 	case ACT_REJECT:
-		return snprint_str(buff, len, ACT_REJECT_STR);
+		return snprint_str(buff, ACT_REJECT_STR);
 	case ACT_RENAME:
-		return snprint_str(buff, len, ACT_RENAME_STR);
+		return snprint_str(buff, ACT_RENAME_STR);
 	case ACT_RELOAD:
-		return snprint_str(buff, len, ACT_RELOAD_STR);
+		return snprint_str(buff, ACT_RELOAD_STR);
 	case ACT_CREATE:
-		return snprint_str(buff, len, ACT_CREATE_STR);
+		return snprint_str(buff, ACT_CREATE_STR);
 	case ACT_SWITCHPG:
-		return snprint_str(buff, len, ACT_SWITCHPG_STR);
+		return snprint_str(buff, ACT_SWITCHPG_STR);
 	default:
 		return 0;
 	}
 }
 
 static int
-snprint_multipath_vpd_data(char * buff, size_t len,
+snprint_multipath_vpd_data(struct strbuf *buff,
 			   const struct multipath * mpp)
 {
 	struct pathgroup * pgp;
@@ -373,26 +340,26 @@ snprint_multipath_vpd_data(char * buff, size_t len,
 	vector_foreach_slot(mpp->pg, pgp, i)
 		vector_foreach_slot(pgp->paths, pp, j)
 			if (pp->vpd_data)
-				return snprintf(buff, len, "%s", pp->vpd_data);
-	return snprintf(buff, len, "[undef]");
+				return append_strbuf_str(buff, pp->vpd_data);
+	return append_strbuf_str(buff, "[undef]");
 }
 
 /*
  * path info printing functions
  */
 static int
-snprint_path_uuid (char * buff, size_t len, const struct path * pp)
+snprint_path_uuid (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_str(buff, len, pp->wwid);
+	return snprint_str(buff, pp->wwid);
 }
 
 static int
-snprint_hcil (char * buff, size_t len, const struct path * pp)
+snprint_hcil (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || pp->sg_id.host_no < 0)
-		return snprintf(buff, len, "#:#:#:#");
+		return append_strbuf_str(buff, "#:#:#:#");
 
-	return snprintf(buff, len, "%i:%i:%i:%" PRIu64,
+	return print_strbuf(buff, "%i:%i:%i:%" PRIu64,
 			pp->sg_id.host_no,
 			pp->sg_id.channel,
 			pp->sg_id.scsi_id,
@@ -400,159 +367,158 @@ snprint_hcil (char * buff, size_t len, const struct path * pp)
 }
 
 static int
-snprint_dev (char * buff, size_t len, const struct path * pp)
+snprint_dev (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || !strlen(pp->dev))
-		return snprintf(buff, len, "-");
+		return append_strbuf_str(buff, "-");
 	else
-		return snprint_str(buff, len, pp->dev);
+		return snprint_str(buff, pp->dev);
 }
 
 static int
-snprint_dev_t (char * buff, size_t len, const struct path * pp)
+snprint_dev_t (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || !strlen(pp->dev))
-		return snprintf(buff, len, "#:#");
+		return append_strbuf_str(buff, "#:#");
 	else
-		return snprint_str(buff, len, pp->dev_t);
+		return snprint_str(buff, pp->dev_t);
 }
 
 static int
-snprint_offline (char * buff, size_t len, const struct path * pp)
+snprint_offline (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || !pp->mpp)
-		return snprintf(buff, len, "unknown");
+		return append_strbuf_str(buff, "unknown");
 	else if (pp->offline)
-		return snprintf(buff, len, "offline");
+		return append_strbuf_str(buff, "offline");
 	else
-		return snprintf(buff, len, "running");
+		return append_strbuf_str(buff, "running");
 }
 
 static int
-snprint_chk_state (char * buff, size_t len, const struct path * pp)
+snprint_chk_state (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || !pp->mpp)
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 
 	switch (pp->state) {
 	case PATH_UP:
-		return snprintf(buff, len, "ready");
+		return append_strbuf_str(buff, "ready");
 	case PATH_DOWN:
-		return snprintf(buff, len, "faulty");
+		return append_strbuf_str(buff, "faulty");
 	case PATH_SHAKY:
-		return snprintf(buff, len, "shaky");
+		return append_strbuf_str(buff, "shaky");
 	case PATH_GHOST:
-		return snprintf(buff, len, "ghost");
+		return append_strbuf_str(buff, "ghost");
 	case PATH_PENDING:
-		return snprintf(buff, len, "i/o pending");
+		return append_strbuf_str(buff, "i/o pending");
 	case PATH_TIMEOUT:
-		return snprintf(buff, len, "i/o timeout");
+		return append_strbuf_str(buff, "i/o timeout");
 	case PATH_DELAYED:
-		return snprintf(buff, len, "delayed");
+		return append_strbuf_str(buff, "delayed");
 	default:
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 	}
 }
 
 static int
-snprint_dm_path_state (char * buff, size_t len, const struct path * pp)
+snprint_dm_path_state (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp)
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 
 	switch (pp->dmstate) {
 	case PSTATE_ACTIVE:
-		return snprintf(buff, len, "active");
+		return append_strbuf_str(buff, "active");
 	case PSTATE_FAILED:
-		return snprintf(buff, len, "failed");
+		return append_strbuf_str(buff, "failed");
 	default:
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 	}
 }
 
 static int
-snprint_vpr (char * buff, size_t len, const struct path * pp)
+snprint_vpr (struct strbuf *buff, const struct path * pp)
 {
-	return snprintf(buff, len, "%s,%s",
-			pp->vendor_id, pp->product_id);
+	return print_strbuf(buff, "%s,%s", pp->vendor_id, pp->product_id);
 }
 
 static int
-snprint_next_check (char * buff, size_t len, const struct path * pp)
+snprint_next_check (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp || !pp->mpp)
-		return snprintf(buff, len, "orphan");
+		return append_strbuf_str(buff, "orphan");
 
-	return snprint_progress(buff, len, pp->tick, pp->checkint);
+	return snprint_progress(buff, pp->tick, pp->checkint);
 }
 
 static int
-snprint_pri (char * buff, size_t len, const struct path * pp)
+snprint_pri (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_int(buff, len, pp ? pp->priority : -1);
+	return snprint_int(buff, pp ? pp->priority : -1);
 }
 
 static int
-snprint_pg_selector (char * buff, size_t len, const struct pathgroup * pgp)
+snprint_pg_selector (struct strbuf *buff, const struct pathgroup * pgp)
 {
 	const char *s = pgp->mpp->selector;
 
-	return snprint_str(buff, len, s ? s : "");
+	return snprint_str(buff, s ? s : "");
 }
 
 static int
-snprint_pg_pri (char * buff, size_t len, const struct pathgroup * pgp)
+snprint_pg_pri (struct strbuf *buff, const struct pathgroup * pgp)
 {
-	return snprint_int(buff, len, pgp->priority);
+	return snprint_int(buff, pgp->priority);
 }
 
 static int
-snprint_pg_state (char * buff, size_t len, const struct pathgroup * pgp)
+snprint_pg_state (struct strbuf *buff, const struct pathgroup * pgp)
 {
 	switch (pgp->status) {
 	case PGSTATE_ENABLED:
-		return snprintf(buff, len, "enabled");
+		return append_strbuf_str(buff, "enabled");
 	case PGSTATE_DISABLED:
-		return snprintf(buff, len, "disabled");
+		return append_strbuf_str(buff, "disabled");
 	case PGSTATE_ACTIVE:
-		return snprintf(buff, len, "active");
+		return append_strbuf_str(buff, "active");
 	default:
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 	}
 }
 
 static int
-snprint_pg_marginal (char * buff, size_t len, const struct pathgroup * pgp)
+snprint_pg_marginal (struct strbuf *buff, const struct pathgroup * pgp)
 {
 	if (pgp->marginal)
-		return snprintf(buff, len, "marginal");
-	return snprintf(buff, len, "normal");
+		return append_strbuf_str(buff, "marginal");
+	return append_strbuf_str(buff, "normal");
 }
 
 static int
-snprint_path_size (char * buff, size_t len, const struct path * pp)
+snprint_path_size (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_size(buff, len, pp->size);
+	return snprint_size(buff, pp->size);
 }
 
 int
-snprint_path_serial (char * buff, size_t len, const struct path * pp)
+snprint_path_serial (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_str(buff, len, pp->serial);
+	return snprint_str(buff, pp->serial);
 }
 
 static int
-snprint_path_mpp (char * buff, size_t len, const struct path * pp)
+snprint_path_mpp (struct strbuf *buff, const struct path * pp)
 {
 	if (!pp->mpp)
-		return snprintf(buff, len, "[orphan]");
+		return append_strbuf_str(buff, "[orphan]");
 	if (!pp->mpp->alias)
-		return snprintf(buff, len, "[unknown]");
-	return snprint_str(buff, len, pp->mpp->alias);
+		return append_strbuf_str(buff, "[unknown]");
+	return snprint_str(buff, pp->mpp->alias);
 }
 
 static int
-snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
+snprint_host_attr (struct strbuf *buff, const struct path * pp, char *attr)
 {
 	struct udev_device *host_dev = NULL;
 	char host_id[32];
@@ -560,7 +526,7 @@ snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
 	int ret;
 
 	if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP)
-		return snprintf(buff, len, "[undef]");
+		return append_strbuf_str(buff, "[undef]");
 	sprintf(host_id, "host%d", pp->sg_id.host_no);
 	host_dev = udev_device_new_from_subsystem_sysname(udev, "fc_host",
 							  host_id);
@@ -570,28 +536,28 @@ snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
 	}
 	value = udev_device_get_sysattr_value(host_dev, attr);
 	if (value)
-		ret = snprint_str(buff, len, value);
+		ret = snprint_str(buff, value);
 	udev_device_unref(host_dev);
 out:
 	if (!value)
-		ret = snprintf(buff, len, "[unknown]");
+		ret = append_strbuf_str(buff, "[unknown]");
 	return ret;
 }
 
 int
-snprint_host_wwnn (char * buff, size_t len, const struct path * pp)
+snprint_host_wwnn (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_host_attr(buff, len, pp, "node_name");
+	return snprint_host_attr(buff, pp, "node_name");
 }
 
 int
-snprint_host_wwpn (char * buff, size_t len, const struct path * pp)
+snprint_host_wwpn (struct strbuf *buff, const struct path * pp)
 {
-	return snprint_host_attr(buff, len, pp, "port_name");
+	return snprint_host_attr(buff, pp, "port_name");
 }
 
 int
-snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
+snprint_tgt_wwpn (struct strbuf *buff, const struct path * pp)
 {
 	struct udev_device *rport_dev = NULL;
 	char rport_id[42];
@@ -599,7 +565,7 @@ snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
 	int ret;
 
 	if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP)
-		return snprintf(buff, len, "[undef]");
+		return append_strbuf_str(buff, "[undef]");
 	sprintf(rport_id, "rport-%d:%d-%d",
 		pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id);
 	rport_dev = udev_device_new_from_subsystem_sysname(udev,
@@ -611,111 +577,111 @@ snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
 	}
 	value = udev_device_get_sysattr_value(rport_dev, "port_name");
 	if (value)
-		ret = snprint_str(buff, len, value);
+		ret = snprint_str(buff, value);
 	udev_device_unref(rport_dev);
 out:
 	if (!value)
-		ret = snprintf(buff, len, "[unknown]");
+		ret = append_strbuf_str(buff, "[unknown]");
 	return ret;
 }
 
 
 int
-snprint_tgt_wwnn (char * buff, size_t len, const struct path * pp)
+snprint_tgt_wwnn (struct strbuf *buff, const struct path * pp)
 {
 	if (pp->tgt_node_name[0] == '\0')
-		return snprintf(buff, len, "[undef]");
-	return snprint_str(buff, len, pp->tgt_node_name);
+		return append_strbuf_str(buff, "[undef]");
+	return snprint_str(buff, pp->tgt_node_name);
 }
 
 static int
-snprint_host_adapter (char * buff, size_t len, const struct path * pp)
+snprint_host_adapter (struct strbuf *buff, const struct path * pp)
 {
 	char adapter[SLOT_NAME_SIZE];
 
 	if (sysfs_get_host_adapter_name(pp, adapter))
-		return snprintf(buff, len, "[undef]");
-	return snprint_str(buff, len, adapter);
+		return append_strbuf_str(buff, "[undef]");
+	return snprint_str(buff, adapter);
 }
 
 static int
-snprint_path_checker (char * buff, size_t len, const struct path * pp)
+snprint_path_checker (struct strbuf *buff, const struct path * pp)
 {
 	const struct checker * c = &pp->checker;
-	return snprint_str(buff, len, checker_name(c));
+	return snprint_str(buff, checker_name(c));
 }
 
 static int
-snprint_path_foreign (char * buff, size_t len,
+snprint_path_foreign (struct strbuf *buff,
 		      __attribute__((unused)) const struct path * pp)
 {
-	return snprintf(buff, len, "%s", "--");
+	return append_strbuf_str(buff, "--");
 }
 
 static int
-snprint_path_failures(char * buff, size_t len, const struct path * pp)
+snprint_path_failures(struct strbuf *buff, const struct path * pp)
 {
-	return snprint_int(buff, len, pp->failcount);
+	return snprint_int(buff, pp->failcount);
 }
 
 /* if you add a protocol string bigger than "scsi:unspec" you must
  * also change PROTOCOL_BUF_SIZE */
 int
-snprint_path_protocol(char * buff, size_t len, const struct path * pp)
+snprint_path_protocol(struct strbuf *buff, const struct path * pp)
 {
 	switch (pp->bus) {
 	case SYSFS_BUS_SCSI:
 		switch (pp->sg_id.proto_id) {
 		case SCSI_PROTOCOL_FCP:
-			return snprintf(buff, len, "scsi:fcp");
+			return append_strbuf_str(buff, "scsi:fcp");
 		case SCSI_PROTOCOL_SPI:
-			return snprintf(buff, len, "scsi:spi");
+			return append_strbuf_str(buff, "scsi:spi");
 		case SCSI_PROTOCOL_SSA:
-			return snprintf(buff, len, "scsi:ssa");
+			return append_strbuf_str(buff, "scsi:ssa");
 		case SCSI_PROTOCOL_SBP:
-			return snprintf(buff, len, "scsi:sbp");
+			return append_strbuf_str(buff, "scsi:sbp");
 		case SCSI_PROTOCOL_SRP:
-			return snprintf(buff, len, "scsi:srp");
+			return append_strbuf_str(buff, "scsi:srp");
 		case SCSI_PROTOCOL_ISCSI:
-			return snprintf(buff, len, "scsi:iscsi");
+			return append_strbuf_str(buff, "scsi:iscsi");
 		case SCSI_PROTOCOL_SAS:
-			return snprintf(buff, len, "scsi:sas");
+			return append_strbuf_str(buff, "scsi:sas");
 		case SCSI_PROTOCOL_ADT:
-			return snprintf(buff, len, "scsi:adt");
+			return append_strbuf_str(buff, "scsi:adt");
 		case SCSI_PROTOCOL_ATA:
-			return snprintf(buff, len, "scsi:ata");
+			return append_strbuf_str(buff, "scsi:ata");
 		case SCSI_PROTOCOL_USB:
-			return snprintf(buff, len, "scsi:usb");
+			return append_strbuf_str(buff, "scsi:usb");
 		case SCSI_PROTOCOL_UNSPEC:
 		default:
-			return snprintf(buff, len, "scsi:unspec");
+			return append_strbuf_str(buff, "scsi:unspec");
 		}
 	case SYSFS_BUS_CCW:
-		return snprintf(buff, len, "ccw");
+		return append_strbuf_str(buff, "ccw");
 	case SYSFS_BUS_CCISS:
-		return snprintf(buff, len, "cciss");
+		return append_strbuf_str(buff, "cciss");
 	case SYSFS_BUS_NVME:
-		return snprintf(buff, len, "nvme");
+		return append_strbuf_str(buff, "nvme");
 	case SYSFS_BUS_UNDEF:
 	default:
-		return snprintf(buff, len, "undef");
+		return append_strbuf_str(buff, "undef");
 	}
 }
 
 int
-snprint_path_marginal(char * buff, size_t len, const struct path * pp)
+snprint_path_marginal(struct strbuf *buff, const struct path * pp)
 {
 	if (pp->marginal)
-		return snprintf(buff, len, "marginal");
-	return snprintf(buff, len, "normal");
+		return append_strbuf_str(buff, "marginal");
+	return append_strbuf_str(buff, "normal");
 }
 
 static int
-snprint_path_vpd_data(char * buff, size_t len, const struct path * pp)
+snprint_path_vpd_data(struct strbuf *buff, const struct path * pp)
 {
 	if (pp->vpd_data)
-		return snprintf(buff, len, "%s", pp->vpd_data);
-	return snprintf(buff, len, "[undef]");
+		return append_strbuf_str(buff, pp->vpd_data);
+	return append_strbuf_str(buff, "[undef]");
 }
 
 struct multipath_data mpd[] = {
@@ -782,24 +748,33 @@ struct pathgroup_data pgd[] = {
 	{0, NULL, 0 , NULL}
 };
 
-int
-snprint_wildcards (char * buff, int len)
+int snprint_wildcards(struct strbuf *buff)
 {
-	int i, fwd = 0;
+	int initial_len = get_strbuf_len(buff);
+	int i, rc;
 
-	fwd += snprintf(buff + fwd, len - fwd, "multipath format wildcards:\n");
+	if ((rc = append_strbuf_str(buff, "multipath format wildcards:\n")) < 0)
+		return rc;
 	for (i = 0; mpd[i].header; i++)
-		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
-				mpd[i].wildcard, mpd[i].header);
-	fwd += snprintf(buff + fwd, len - fwd, "\npath format wildcards:\n");
+		if ((rc = print_strbuf(buff, "%%%c  %s\n",
+				       mpd[i].wildcard, mpd[i].header)) < 0)
+			return rc;
+
+	if ((rc = append_strbuf_str(buff, "\npath format wildcards:\n")) < 0)
+		return rc;
 	for (i = 0; pd[i].header; i++)
-		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
-				pd[i].wildcard, pd[i].header);
-	fwd += snprintf(buff + fwd, len - fwd, "\npathgroup format wildcards:\n");
+		if ((rc = print_strbuf(buff, "%%%c  %s\n",
+				       pd[i].wildcard, pd[i].header)) < 0)
+			return rc;
+
+	if ((rc = append_strbuf_str(buff, "\npathgroup format wildcards:\n")) < 0)
+		return rc;
 	for (i = 0; pgd[i].header; i++)
-		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
-				pgd[i].wildcard, pgd[i].header);
-	return fwd;
+		if ((rc = print_strbuf(buff, "%%%c  %s\n",
+				       pgd[i].wildcard, pgd[i].header)) < 0)
+			return rc;
+
+	return get_strbuf_len(buff) - initial_len;
 }
 
 void
@@ -832,10 +807,10 @@ void
 _get_path_layout (const struct _vector *gpvec, enum layout_reset reset)
 {
 	int i, j;
-	char buff[MAX_FIELD_LEN];
 	const struct gen_path *gp;
 
 	for (j = 0; pd[j].header; j++) {
+		STRBUF_ON_STACK(buff);
 
 		reset_width(&pd[j].width, reset, pd[j].header);
 
@@ -843,9 +818,9 @@ _get_path_layout (const struct _vector *gpvec, enum layout_reset reset)
 			continue;
 
 		vector_foreach_slot (gpvec, gp, i) {
-			gp->ops->snprint(gp, buff, MAX_FIELD_LEN,
-					 pd[j].wildcard);
-			pd[j].width = MAX(pd[j].width, strlen(buff));
+			gp->ops->snprint(gp, &buff, pd[j].wildcard);
+			pd[j].width = MAX(pd[j].width, get_strbuf_len(&buff));
+			truncate_strbuf(&buff, 0);
 		}
 	}
 }
@@ -873,10 +848,10 @@ _get_multipath_layout (const struct _vector *gmvec,
 			    enum layout_reset reset)
 {
 	int i, j;
-	char buff[MAX_FIELD_LEN];
 	const struct gen_multipath * gm;
 
 	for (j = 0; mpd[j].header; j++) {
+		STRBUF_ON_STACK(buff);
 
 		reset_width(&mpd[j].width, reset, mpd[j].header);
 
@@ -884,9 +859,9 @@ _get_multipath_layout (const struct _vector *gmvec,
 			continue;
 
 		vector_foreach_slot (gmvec, gm, i) {
-			gm->ops->snprint(gm, buff, MAX_FIELD_LEN,
-					 mpd[j].wildcard);
-			mpd[j].width = MAX(mpd[j].width, strlen(buff));
+			gm->ops->snprint(gm, &buff, mpd[j].wildcard);
+			mpd[j].width = MAX(mpd[j].width, get_strbuf_len(&buff));
+			truncate_strbuf(&buff, 0);
 		}
 		condlog(4, "%s: width %d", mpd[j].header, mpd[j].width);
 	}
@@ -905,14 +880,14 @@ mpd_lookup(char wildcard)
 }
 
 int snprint_multipath_attr(const struct gen_multipath* gm,
-			   char *buf, int len, char wildcard)
+			   struct strbuf *buf, char wildcard)
 {
 	const struct multipath *mpp = gen_multipath_to_dm(gm);
 	struct multipath_data *mpd = mpd_lookup(wildcard);
 
 	if (mpd == NULL)
 		return 0;
-	return mpd->snprint(buf, len, mpp);
+	return mpd->snprint(buf, mpp);
 }
 
 static struct path_data *
@@ -928,14 +903,14 @@ pd_lookup(char wildcard)
 }
 
 int snprint_path_attr(const struct gen_path* gp,
-		      char *buf, int len, char wildcard)
+		      struct strbuf *buf, char wildcard)
 {
 	const struct path *pp = gen_path_to_dm(gp);
 	struct path_data *pd = pd_lookup(wildcard);
 
 	if (pd == NULL)
 		return 0;
-	return pd->snprint(buf, len, pp);
+	return pd->snprint(buf, pp);
 }
 
 static struct pathgroup_data *
@@ -951,220 +926,168 @@ pgd_lookup(char wildcard)
 }
 
 int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
-			   char *buf, int len, char wildcard)
+			   struct strbuf *buf, char wildcard)
 {
 	const struct pathgroup *pg = gen_pathgroup_to_dm(gpg);
 	struct pathgroup_data *pdg = pgd_lookup(wildcard);
 
 	if (pdg == NULL)
 		return 0;
-	return pdg->snprint(buf, len, pg);
+	return pdg->snprint(buf, pg);
 }
 
-int
-snprint_multipath_header (char * line, int len, const char * format)
+int snprint_multipath_header(struct strbuf *line, const char *format)
 {
-	char * c = line;   /* line cursor */
-	char * s = line;   /* for padding */
-	const char * f = format; /* format string cursor */
-	int fwd;
+	int initial_len = get_strbuf_len(line);
+	const char *f;
 	struct multipath_data * data;
+	int rc;
 
-	do {
-		if (TAIL <= 0)
-			break;
+	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
+		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
+			return rc;
 
-		if (*f != '%') {
-			*c++ = *f;
-			NOPAD;
-			continue;
-		}
-		f++;
-
-		if (!(data = mpd_lookup(*f)))
+		format = f + 1;
+		if (!(data = mpd_lookup(*format)))
 			continue; /* unknown wildcard */
 
-		PRINT(c, TAIL, "%s", data->header);
-		PAD(data->width);
-	} while (*f++);
+		if ((rc = append_strbuf_str(line, data->header)) < 0)
+			return rc;
+		else if ((unsigned int)rc < data->width)
+			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
+				return rc;
+	}
 
-	__endline(line, len, c);
-	return (c - line);
+	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
+		return rc;
+	return get_strbuf_len(line) - initial_len;
 }
 
-int
-_snprint_multipath (const struct gen_multipath * gmp,
-		    char * line, int len, const char * format, int pad)
+int _snprint_multipath(const struct gen_multipath *gmp,
+		       struct strbuf *line, const char *format, int pad)
 {
-	char * c = line;   /* line cursor */
-	char * s = line;   /* for padding */
-	const char * f = format; /* format string cursor */
-	int fwd;
+	int initial_len = get_strbuf_len(line);
+	const char *f;
 	struct multipath_data * data;
-	char buff[MAX_FIELD_LEN] = {};
+	int rc;
 
-	do {
-		if (TAIL <= 0)
-			break;
+	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
+		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
+			return rc;
 
-		if (*f != '%') {
-			*c++ = *f;
-			NOPAD;
-			continue;
-		}
-		f++;
-
-		if (!(data = mpd_lookup(*f)))
-			continue;
+		format = f + 1;
+		if (!(data = mpd_lookup(*format)))
+			continue; /* unknown wildcard */
 
-		gmp->ops->snprint(gmp, buff, MAX_FIELD_LEN, *f);
-		PRINT(c, TAIL, "%s", buff);
-		if (pad)
-			PAD(data->width);
-		buff[0] = '\0';
-	} while (*f++);
+		if ((rc = gmp->ops->snprint(gmp, line, *format)) < 0)
+			return rc;
+		else if (pad && (unsigned int)rc < data->width)
+			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
+				return rc;
+	}
 
-	__endline(line, len, c);
-	return (c - line);
+	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
+		return rc;
+	return get_strbuf_len(line) - initial_len;
 }
 
-int
-snprint_path_header (char * line, int len, const char * format)
+int snprint_path_header(struct strbuf *line, const char *format)
 {
-	char * c = line;   /* line cursor */
-	char * s = line;   /* for padding */
-	const char * f = format; /* format string cursor */
-	int fwd;
-	struct path_data * data;
-
-	do {
-		if (TAIL <= 0)
-			break;
+	int initial_len = get_strbuf_len(line);
+	const char *f;
+	struct path_data *data;
+	int rc;
 
-		if (*f != '%') {
-			*c++ = *f;
-			NOPAD;
-			continue;
-		}
-		f++;
+	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
+		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
+			return rc;
 
-		if (!(data = pd_lookup(*f)))
+		format = f + 1;
+		if (!(data = pd_lookup(*format)))
 			continue; /* unknown wildcard */
 
-		PRINT(c, TAIL, "%s", data->header);
-		PAD(data->width);
-	} while (*f++);
+		if ((rc = append_strbuf_str(line, data->header)) < 0)
+			return rc;
+		else if ((unsigned int)rc < data->width)
+			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
+				return rc;
+	}
 
-	__endline(line, len, c);
-	return (c - line);
+	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
+		return rc;
+	return get_strbuf_len(line) - initial_len;
 }
 
-int
-_snprint_path (const struct gen_path * gp, char * line, int len,
-	       const char * format, int pad)
+int _snprint_path(const struct gen_path *gp, struct strbuf *line,
+		  const char *format, int pad)
 {
-	char * c = line;   /* line cursor */
-	char * s = line;   /* for padding */
-	const char * f = format; /* format string cursor */
-	int fwd;
+	int initial_len = get_strbuf_len(line);
+	const char *f;
 	struct path_data * data;
-	char buff[MAX_FIELD_LEN];
+	int rc;
 
-	do {
-		if (TAIL <= 0)
-			break;
-
-		if (*f != '%') {
-			*c++ = *f;
-			NOPAD;
-			continue;
-		}
-		f++;
+	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
+		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
+			return rc;
 
-		if (!(data = pd_lookup(*f)))
-			continue;
+		format = f + 1;
+		if (!(data = pd_lookup(*format)))
+			continue; /* unknown wildcard */
 
-		gp->ops->snprint(gp, buff, MAX_FIELD_LEN, *f);
-		PRINT(c, TAIL, "%s", buff);
-		if (pad)
-			PAD(data->width);
-	} while (*f++);
+		if ((rc = gp->ops->snprint(gp, line, *format)) < 0)
+			return rc;
+		else if (pad && (unsigned int)rc < data->width)
+			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
+				return rc;
+	}
 
-	__endline(line, len, c);
-	return (c - line);
+	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
+		return rc;
+	return get_strbuf_len(line) - initial_len;
 }
 
-int
-_snprint_pathgroup (const struct gen_pathgroup * ggp, char * line, int len,
-		    char * format)
-{
-	char * c = line;   /* line cursor */
-	char * s = line;   /* for padding */
-	char * f = format; /* format string cursor */
-	int fwd;
-	struct pathgroup_data * data;
-	char buff[MAX_FIELD_LEN];
-
-	do {
-		if (TAIL <= 0)
-			break;
+int _snprint_pathgroup(const struct gen_pathgroup *ggp, struct strbuf *line,
+		       const char *format)
+{
+	int initial_len = get_strbuf_len(line);
+	const char *f;
+	struct pathgroup_data *data;
+	int rc;
 
-		if (*f != '%') {
-			*c++ = *f;
-			NOPAD;
-			continue;
-		}
-		f++;
+	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
+		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
+			return rc;
 
-		if (!(data = pgd_lookup(*f)))
-			continue;
+		format = f + 1;
+		if (!(data = pgd_lookup(*format)))
+			continue; /* unknown wildcard */
 
-		ggp->ops->snprint(ggp, buff, MAX_FIELD_LEN, *f);
-		PRINT(c, TAIL, "%s", buff);
-		PAD(data->width);
-	} while (*f++);
+		if ((rc = ggp->ops->snprint(ggp, line, *format)) < 0)
+			return rc;
+		else if ((unsigned int)rc < data->width)
+			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
+				return rc;
+	}
 
-	__endline(line, len, c);
-	return (c - line);
+	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
+		return rc;
+	return get_strbuf_len(line) - initial_len;
 }
-#define snprint_pathgroup(line, len, fmt, pgp) \
-	_snprint_pathgroup(dm_pathgroup_to_gen(pgp), line, len, fmt)
+
+#define snprint_pathgroup(line, fmt, pgp)				\
+	_snprint_pathgroup(dm_pathgroup_to_gen(pgp), line, fmt)
 
 void _print_multipath_topology(const struct gen_multipath *gmp, int verbosity)
 {
-	int resize;
-	char *buff = NULL;
-	char *old = NULL;
-	int len, maxlen = MAX_LINE_LEN * MAX_LINES;
-
-	buff = MALLOC(maxlen);
-	do {
-		if (!buff) {
-			if (old)
-				FREE(old);
-			condlog(0, "couldn't allocate memory for list: %s\n",
-				strerror(errno));
-			return;
-		}
+	STRBUF_ON_STACK(buff);
 
-		len = _snprint_multipath_topology(gmp, buff, maxlen, verbosity);
-		resize = (len == maxlen - 1);
-
-		if (resize) {
-			maxlen *= 2;
-			old = buff;
-			buff = REALLOC(buff, maxlen);
-		}
-	} while (resize);
-	printf("%s", buff);
-	FREE(buff);
+	_snprint_multipath_topology(gmp, &buff, verbosity);
+	printf("%s", get_strbuf_str(&buff));
 }
 
-int
-snprint_multipath_style(const struct gen_multipath *gmp, char *style, int len,
-			int verbosity)
+int snprint_multipath_style(const struct gen_multipath *gmp,
+			    struct strbuf *style, int verbosity)
 {
-	int n;
 	const struct multipath *mpp = gen_multipath_to_dm(gmp);
 	bool need_action = (verbosity > 1 &&
 			    mpp->action != ACT_NOTHING &&
@@ -1172,268 +1095,203 @@ snprint_multipath_style(const struct gen_multipath *gmp, char *style, int len,
 			    mpp->action != ACT_IMPOSSIBLE);
 	bool need_wwid = (strncmp(mpp->alias, mpp->wwid, WWID_SIZE));
 
-	n = snprintf(style, len, "%s%s%s%s",
-		     need_action ? "%A: " : "", "%n",
-		     need_wwid ? " (%w)" : "", " %d %s");
-	return MIN(n, len - 1);
+	return print_strbuf(style, "%s%s%s%s",
+			    need_action ? "%A: " : "", "%n",
+			    need_wwid ? " (%w)" : "", " %d %s");
 }
 
 int _snprint_multipath_topology(const struct gen_multipath *gmp,
-				char *buff, int len, int verbosity)
+				struct strbuf *buff, int verbosity)
 {
-	int j, i, fwd = 0;
+	int j, i, rc;
 	const struct _vector *pgvec;
 	const struct gen_pathgroup *gpg;
-	char style[64];
-	char * c = style;
-	char fmt[64];
-	char * f;
+	STRBUF_ON_STACK(style);
+	size_t initial_len = get_strbuf_len(buff);
 
 	if (verbosity <= 0)
-		return fwd;
+		return 0;
 
 	reset_multipath_layout();
 
 	if (verbosity == 1)
-		return _snprint_multipath(gmp, buff, len, "%n", 1);
-
-	if(isatty(1))
-		c += sprintf(c, "%c[%dm", 0x1B, 1); /* bold on */
+		return _snprint_multipath(gmp, buff, "%n", 1);
 
-	c += gmp->ops->style(gmp, c, sizeof(style) - (c - style),
-			     verbosity);
-	if(isatty(1))
-		c += sprintf(c, "%c[%dm", 0x1B, 0); /* bold off */
+	if(isatty(1) &&
+	   (rc = print_strbuf(&style, "%c[%dm", 0x1B, 1)) < 0) /* bold on */
+		return rc;
+	if ((rc = gmp->ops->style(gmp, &style, verbosity)) < 0)
+		return rc;
+	if(isatty(1) &&
+	   (rc = print_strbuf(&style, "%c[%dm", 0x1B, 0)) < 0) /* bold off */
+		return rc;
 
-	fwd += _snprint_multipath(gmp, buff + fwd, len - fwd, style, 1);
-	if (fwd >= len)
-		return len;
-	fwd += _snprint_multipath(gmp, buff + fwd, len - fwd,
-				  PRINT_MAP_PROPS, 1);
-	if (fwd >= len)
-		return len;
+	if ((rc = _snprint_multipath(gmp, buff, get_strbuf_str(&style), 1)) < 0
+	    || (rc = _snprint_multipath(gmp, buff, PRINT_MAP_PROPS, 1)) < 0)
+		return rc;
 
 	pgvec = gmp->ops->get_pathgroups(gmp);
 	if (pgvec == NULL)
-		return fwd;
+		goto out;
 
 	vector_foreach_slot (pgvec, gpg, j) {
 		const struct _vector *pathvec;
 		struct gen_path *gp;
+		bool last_group = j + 1 == VECTOR_SIZE(pgvec);
 
-		f=fmt;
-
-		if (j + 1 < VECTOR_SIZE(pgvec)) {
-			strcpy(f, "|-+- " PRINT_PG_INDENT);
-		} else
-			strcpy(f, "`-+- " PRINT_PG_INDENT);
-		fwd += _snprint_pathgroup(gpg, buff + fwd, len - fwd, fmt);
-
-		if (fwd >= len) {
-			fwd = len;
-			break;
-		}
+		if ((rc = print_strbuf(buff, "%c-+- ",
+				       last_group ? '`' : '|')) < 0 ||
+		    (rc = _snprint_pathgroup(gpg, buff, PRINT_PG_INDENT)) < 0)
+			return rc;
 
 		pathvec = gpg->ops->get_paths(gpg);
 		if (pathvec == NULL)
 			continue;
 
 		vector_foreach_slot (pathvec, gp, i) {
-			f=fmt;
-			if (*f != '|')
-				*f=' ';
-			f++;
-			if (i + 1 < VECTOR_SIZE(pathvec))
-				strcpy(f, " |- " PRINT_PATH_INDENT);
-			else
-				strcpy(f, " `- " PRINT_PATH_INDENT);
-			fwd += _snprint_path(gp, buff + fwd, len - fwd, fmt, 1);
-			if (fwd >= len) {
-				fwd = len;
-				break;
-			}
+			if ((rc = print_strbuf(buff, "%c %c- ",
+					       last_group ? ' ' : '|',
+					       i + 1 == VECTOR_SIZE(pathvec) ?
+					       '`': '|')) < 0 ||
+			    (rc = _snprint_path(gp, buff,
+						PRINT_PATH_INDENT, 1)) < 0)
+				return rc;
 		}
 		gpg->ops->rel_paths(gpg, pathvec);
-
-		if (fwd == len)
-			break;
 	}
+
 	gmp->ops->rel_pathgroups(gmp, pgvec);
-	return fwd;
+out:
+	return get_strbuf_len(buff) - initial_len;
 }
 
 
 static int
-snprint_json (char * buff, int len, int indent, char *json_str)
+snprint_json(struct strbuf *buff, int indent, const char *json_str)
 {
-	int fwd = 0, i;
+	int rc;
 
-	for (i = 0; i < indent; i++) {
-		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_INDENT);
-		if (fwd >= len)
-			return fwd;
-	}
+	if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0)
+		return rc;
 
-	fwd += snprintf(buff + fwd, len - fwd, "%s", json_str);
-	return fwd;
+	return append_strbuf_str(buff, json_str);
 }
 
-static int
-snprint_json_header (char * buff, int len)
+static int snprint_json_header(struct strbuf *buff)
 {
-	int fwd = 0;
-
-	fwd +=  snprint_json(buff, len, 0, PRINT_JSON_START_ELEM);
-	if (fwd >= len)
-		return fwd;
+	int rc;
 
-	fwd +=  snprintf(buff + fwd, len  - fwd, PRINT_JSON_START_VERSION,
-			PRINT_JSON_MAJOR_VERSION, PRINT_JSON_MINOR_VERSION);
-	return fwd;
+	if ((rc = snprint_json(buff, 0, PRINT_JSON_START_ELEM)) < 0)
+		return rc;
+	return print_strbuf(buff, PRINT_JSON_START_VERSION,
+			    PRINT_JSON_MAJOR_VERSION, PRINT_JSON_MINOR_VERSION);
 }
 
-static int
-snprint_json_elem_footer (char * buff, int len, int indent, int last)
+static int snprint_json_elem_footer(struct strbuf *buff, int indent, bool last)
 {
-	int fwd = 0, i;
+	int rc;
 
-	for (i = 0; i < indent; i++) {
-		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_INDENT);
-		if (fwd >= len)
-			return fwd;
-	}
+	if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0)
+		return rc;
 
-	if (last == 1)
-		fwd += snprintf(buff + fwd, len - fwd, "%s", PRINT_JSON_END_LAST_ELEM);
+	if (last)
+		return append_strbuf_str(buff, PRINT_JSON_END_LAST_ELEM);
 	else
-		fwd += snprintf(buff + fwd, len - fwd, "%s", PRINT_JSON_END_ELEM);
-	return fwd;
+		return append_strbuf_str(buff, PRINT_JSON_END_ELEM);
 }
 
-static int
-snprint_multipath_fields_json (char * buff, int len,
-		const struct multipath * mpp, int last)
+static int snprint_multipath_fields_json(struct strbuf *buff,
+					 const struct multipath *mpp, int last)
 {
-	int i, j, fwd = 0;
+	int i, j, rc;
 	struct path *pp;
 	struct pathgroup *pgp;
+	size_t initial_len = get_strbuf_len(buff);
 
-	fwd += snprint_multipath(buff, len, PRINT_JSON_MAP, mpp, 0);
-	if (fwd >= len)
-		return fwd;
-
-	fwd += snprint_json(buff + fwd, len - fwd, 2, PRINT_JSON_START_GROUPS);
-	if (fwd >= len)
-		return fwd;
+	if ((rc = snprint_multipath(buff, PRINT_JSON_MAP, mpp, 0)) < 0 ||
+	    (rc = snprint_json(buff, 2, PRINT_JSON_START_GROUPS)) < 0)
+		return rc;
 
 	vector_foreach_slot (mpp->pg, pgp, i) {
 
-		fwd += snprint_pathgroup(buff + fwd, len - fwd, PRINT_JSON_GROUP, pgp);
-		if (fwd >= len)
-			return fwd;
-
-		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_GROUP_NUM, i + 1);
-		if (fwd >= len)
-			return fwd;
-
-		fwd += snprint_json(buff + fwd, len - fwd, 3, PRINT_JSON_START_PATHS);
-		if (fwd >= len)
-			return fwd;
+		if ((rc = snprint_pathgroup(buff, PRINT_JSON_GROUP, pgp)) < 0 ||
+		    (rc = print_strbuf(buff, PRINT_JSON_GROUP_NUM, i + 1)) < 0 ||
+		    (rc = snprint_json(buff, 3, PRINT_JSON_START_PATHS)) < 0)
+			return rc;
 
 		vector_foreach_slot (pgp->paths, pp, j) {
-			fwd += snprint_path(buff + fwd, len - fwd, PRINT_JSON_PATH, pp, 0);
-			if (fwd >= len)
-				return fwd;
-
-			fwd += snprint_json_elem_footer(buff + fwd,
-					len - fwd, 3, j + 1 == VECTOR_SIZE(pgp->paths));
-			if (fwd >= len)
-				return fwd;
+			if ((rc = snprint_path(buff, PRINT_JSON_PATH,
+					       pp, 0)) < 0 ||
+			    (rc = snprint_json_elem_footer(
+				    buff, 3,
+				    j + 1 == VECTOR_SIZE(pgp->paths))) < 0)
+				return rc;
 		}
-		fwd += snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
-		if (fwd >= len)
-			return fwd;
-
-		fwd +=  snprint_json_elem_footer(buff + fwd,
-				len - fwd, 2, i + 1 == VECTOR_SIZE(mpp->pg));
-		if (fwd >= len)
-			return fwd;
+		if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
+		    (rc = snprint_json_elem_footer(
+			    buff, 2, i + 1 == VECTOR_SIZE(mpp->pg))) < 0)
+			return rc;
 	}
 
-	fwd += snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
-	if (fwd >= len)
-		return fwd;
+	if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
+	    (rc = snprint_json_elem_footer(buff, 1, last)) < 0)
+		return rc;
 
-	fwd += snprint_json_elem_footer(buff + fwd, len - fwd, 1, last);
-	return fwd;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-int
-snprint_multipath_map_json (char * buff, int len, const struct multipath * mpp)
+int snprint_multipath_map_json(struct strbuf *buff, const struct multipath * mpp)
 {
-	int fwd = 0;
-
-	fwd +=  snprint_json_header(buff, len);
-	if (fwd >= len)
-		return len;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc;
 
-	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_START_MAP);
-	if (fwd >= len)
-		return len;
+	if ((rc = snprint_json_header(buff)) < 0 ||
+	    (rc = snprint_json(buff, 0, PRINT_JSON_START_MAP)) < 0)
+		return rc;
 
-	fwd += snprint_multipath_fields_json(buff + fwd, len - fwd, mpp, 1);
-	if (fwd >= len)
-		return len;
+	if ((rc = snprint_multipath_fields_json(buff, mpp, 1)) < 0)
+		return rc;
 
-	fwd +=  snprint_json(buff + fwd, len - fwd, 0, "\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = snprint_json(buff, 0, "\n")) < 0 ||
+	    (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0)
+		return rc;
 
-	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_LAST);
-	if (fwd >= len)
-		return len;
-	return fwd;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-int
-snprint_multipath_topology_json (char * buff, int len, const struct vectors * vecs)
+int snprint_multipath_topology_json (struct strbuf *buff,
+				     const struct vectors * vecs)
 {
-	int i, fwd = 0;
+	int i;
 	struct multipath * mpp;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc;
 
-	fwd +=  snprint_json_header(buff, len);
-	if (fwd >= len)
-		return len;
-
-	fwd +=  snprint_json(buff + fwd, len  - fwd, 1, PRINT_JSON_START_MAPS);
-	if (fwd >= len)
-		return len;
+	if ((rc = snprint_json_header(buff)) < 0 ||
+	    (rc = snprint_json(buff, 1, PRINT_JSON_START_MAPS)) < 0)
+		return rc;
 
 	vector_foreach_slot(vecs->mpvec, mpp, i) {
-		fwd += snprint_multipath_fields_json(buff + fwd, len - fwd,
-				mpp, i + 1 == VECTOR_SIZE(vecs->mpvec));
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_multipath_fields_json(
+			     buff, mpp, i + 1 == VECTOR_SIZE(vecs->mpvec))) < 0)
+			return rc;
 	}
 
-	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
-	if (fwd >= len)
-		return len;
+	if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
+	    (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0)
+		return rc;
 
-	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_LAST);
-	if (fwd >= len)
-		return len;
-	return fwd;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 static int
 snprint_hwentry (const struct config *conf,
-		 char * buff, int len, const struct hwentry * hwe)
+		 struct strbuf *buff, const struct hwentry * hwe)
 {
-	int i;
-	int fwd = 0;
+	int i, rc;
 	struct keyword * kw;
 	struct keyword * rootkw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "devices");
 
@@ -1445,57 +1303,53 @@ snprint_hwentry (const struct config *conf,
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
+		return rc;
+
 	iterate_sub_keywords(rootkw, kw, i) {
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				kw, hwe);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, hwe)) < 0)
+			return rc;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+	if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-static int snprint_hwtable(const struct config *conf,
-			   char *buff, int len,
+static int snprint_hwtable(const struct config *conf, struct strbuf *buff,
 			   const struct _vector *hwtable)
 {
-	int fwd = 0;
-	int i;
+	int i, rc;
 	struct hwentry * hwe;
 	struct keyword * rootkw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "devices");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "devices {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "devices {\n")) < 0)
+		return rc;
+
 	vector_foreach_slot (hwtable, hwe, i) {
-		fwd += snprint_hwentry(conf, buff + fwd, len - fwd, hwe);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_hwentry(conf, buff, hwe)) < 0)
+			return rc;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+
+	return get_strbuf_len(buff) - initial_len;
 }
 
 static int
-snprint_mpentry (const struct config *conf, char * buff, int len,
+snprint_mpentry (const struct config *conf, struct strbuf *buff,
 		 const struct mpentry * mpe, const struct _vector *mpvec)
 {
-	int i;
-	int fwd = 0;
+	int i, rc;
 	struct keyword * kw;
 	struct keyword * rootkw;
 	struct multipath *mpp = NULL;
+	size_t initial_len = get_strbuf_len(buff);
 
 	if (mpvec != NULL && (mpp = find_mp_by_wwid(mpvec, mpe->wwid)) == NULL)
 		return 0;
@@ -1504,49 +1358,44 @@ snprint_mpentry (const struct config *conf, char * buff, int len,
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "\tmultipath {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "\tmultipath {\n")) < 0)
+		return rc;
+
 	iterate_sub_keywords(rootkw, kw, i) {
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				kw, mpe);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, mpe)) < 0)
+			return rc;
 	}
 	/*
 	 * This mpp doesn't have alias defined. Add the alias in a comment.
 	 */
-	if (mpp != NULL && strcmp(mpp->alias, mpp->wwid)) {
-		fwd += snprintf(buff + fwd, len - fwd, "\t\t# alias \"%s\"\n",
-				mpp->alias);
-		if (fwd >= len)
-			return len;
-	}
-	fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+	if (mpp != NULL && strcmp(mpp->alias, mpp->wwid) &&
+	    (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n", mpp->alias)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
+		return rc;
+
+	return get_strbuf_len(buff) - initial_len;
 }
 
-static int snprint_mptable(const struct config *conf,
-			   char *buff, int len, const struct _vector *mpvec)
+static int snprint_mptable(const struct config *conf, struct strbuf *buff,
+			   const struct _vector *mpvec)
 {
-	int fwd = 0;
-	int i;
+	int i, rc;
 	struct mpentry * mpe;
 	struct keyword * rootkw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "multipaths");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "multipaths {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "multipaths {\n")) < 0)
+		return rc;
+
 	vector_foreach_slot (conf->mptable, mpe, i) {
-		fwd += snprint_mpentry(conf, buff + fwd, len - fwd, mpe, mpvec);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_mpentry(conf, buff, mpe, mpvec)) < 0)
+			return rc;
 	}
 	if (mpvec != NULL) {
 		struct multipath *mpp;
@@ -1555,496 +1404,397 @@ static int snprint_mptable(const struct config *conf,
 			if (find_mpe(conf->mptable, mpp->wwid) != NULL)
 				continue;
 
-			fwd += snprintf(buff + fwd, len - fwd,
-					"\tmultipath {\n");
-			if (fwd >= len)
-				return len;
-			fwd += snprintf(buff + fwd, len - fwd,
-					"\t\twwid \"%s\"\n", mpp->wwid);
-			if (fwd >= len)
-				return len;
+			if ((rc = print_strbuf(buff,
+					       "\tmultipath {\n\t\twwid \"%s\"\n",
+					       mpp->wwid)) < 0)
+				return rc;
 			/*
 			 * This mpp doesn't have alias defined in
 			 * multipath.conf - otherwise find_mpe would have
 			 * found it. Add the alias in a comment.
 			 */
-			if (strcmp(mpp->alias, mpp->wwid)) {
-				fwd += snprintf(buff + fwd, len - fwd,
-						"\t\t# alias \"%s\"\n",
-						mpp->alias);
-				if (fwd >= len)
-					return len;
-			}
-			fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
-			if (fwd >= len)
-				return len;
+			if (strcmp(mpp->alias, mpp->wwid) &&
+			    (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n",
+					       mpp->alias)) < 0)
+				return rc;
+			if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
+				return rc;
 		}
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-static int snprint_overrides(const struct config *conf, char * buff, int len,
+static int snprint_overrides(const struct config *conf, struct strbuf *buff,
 			     const struct hwentry *overrides)
 {
-	int fwd = 0;
-	int i;
+	int i, rc;
 	struct keyword *rootkw;
 	struct keyword *kw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "overrides");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "overrides {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "overrides {\n")) < 0)
+		return rc;
 	if (!overrides)
 		goto out;
+
 	iterate_sub_keywords(rootkw, kw, i) {
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, NULL);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0)
+			return rc;
 	}
 out:
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-static int snprint_defaults(const struct config *conf, char *buff, int len)
+static int snprint_defaults(const struct config *conf, struct strbuf *buff)
 {
-	int fwd = 0;
-	int i;
+	int i, rc;
 	struct keyword *rootkw;
 	struct keyword *kw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "defaults");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "defaults {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "defaults {\n")) < 0)
+		return rc;
 
 	iterate_sub_keywords(rootkw, kw, i) {
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				kw, NULL);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0)
+			return rc;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-static int
-snprint_blacklist_group (char *buff, int len, int *fwd, vector *vec)
+static int snprint_blacklist_group(struct strbuf *buff, vector *vec)
 {
-	int threshold = MAX_LINE_LEN;
 	struct blentry * ble;
-	int pos;
-	int i;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc, i;
 
-	pos = *fwd;
 	if (!VECTOR_SIZE(*vec)) {
-		if ((len - pos - threshold) <= 0)
-			return 0;
-		pos += snprintf(buff + pos, len - pos, "        <empty>\n");
+		if ((rc = append_strbuf_str(buff, "        <empty>\n")) < 0)
+			return rc;
 	} else vector_foreach_slot (*vec, ble, i) {
-		if ((len - pos - threshold) <= 0)
-			return 0;
-		if (ble->origin == ORIGIN_CONFIG)
-			pos += snprintf(buff + pos, len - pos, "        (config file rule) ");
-		else if (ble->origin == ORIGIN_DEFAULT)
-			pos += snprintf(buff + pos, len - pos, "        (default rule)     ");
-		pos += snprintf(buff + pos, len - pos, "%s\n", ble->str);
+		rc = print_strbuf(buff, "        %s %s\n",
+				   ble->origin == ORIGIN_CONFIG ?
+				   "(config file rule)" :
+				   "(default rule)    ", ble->str);
+		if (rc < 0)
+			return rc;
 	}
 
-	*fwd = pos;
-	return pos;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 static int
-snprint_blacklist_devgroup (char *buff, int len, int *fwd, vector *vec)
+snprint_blacklist_devgroup (struct strbuf *buff, vector *vec)
 {
-	int threshold = MAX_LINE_LEN;
 	struct blentry_device * bled;
-	int pos;
-	int i;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc, i;
 
-	pos = *fwd;
 	if (!VECTOR_SIZE(*vec)) {
-		if ((len - pos - threshold) <= 0)
-			return 0;
-		pos += snprintf(buff + pos, len - pos, "        <empty>\n");
+		if ((rc = append_strbuf_str(buff, "        <empty>\n")) < 0)
+			return rc;
 	} else vector_foreach_slot (*vec, bled, i) {
-		if ((len - pos - threshold) <= 0)
-			return 0;
-		if (bled->origin == ORIGIN_CONFIG)
-			pos += snprintf(buff + pos, len - pos, "        (config file rule) ");
-		else if (bled->origin == ORIGIN_DEFAULT)
-			pos += snprintf(buff + pos, len - pos, "        (default rule)     ");
-		pos += snprintf(buff + pos, len - pos, "%s:%s\n", bled->vendor, bled->product);
+		rc = print_strbuf(buff, "        %s %s:%s\n",
+				  bled->origin == ORIGIN_CONFIG ?
+				  "(config file rule)" :
+				  "(default rule)    ",
+				  bled->vendor, bled->product);
+		if (rc < 0)
+			return rc;
 	}
 
-	*fwd = pos;
-	return pos;
-}
-
-int snprint_blacklist_report(struct config *conf, char *buff, int len)
-{
-	int threshold = MAX_LINE_LEN;
-	int fwd = 0;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "device node rules:\n"
-					       "- blacklist:\n");
-	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_devnode))
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
-	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_devnode) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "udev property rules:\n"
-					       "- blacklist:\n");
-	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_property))
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
-	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_property) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "protocol rules:\n"
-					       "- blacklist:\n");
-	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_protocol))
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
-	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_protocol) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "wwid rules:\n"
-					       "- blacklist:\n");
-	if (snprint_blacklist_group(buff, len, &fwd, &conf->blist_wwid) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
-	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_wwid) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "device rules:\n"
-					       "- blacklist:\n");
-	if (snprint_blacklist_devgroup(buff, len, &fwd, &conf->blist_device) == 0)
-		return len;
-
-	if ((len - fwd - threshold) <= 0)
-		return len;
-	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
-	if (snprint_blacklist_devgroup(buff, len, &fwd, &conf->elist_device) == 0)
-		return len;
-
-	if (fwd > len)
-		return len;
-	return fwd;
-}
-
-static int snprint_blacklist(const struct config *conf, char *buff, int len)
+	return get_strbuf_len(buff) - initial_len;
+}
+
+int snprint_blacklist_report(struct config *conf, struct strbuf *buff)
 {
-	int i;
+	size_t initial_len = get_strbuf_len(buff);
+	int rc;
+
+	if ((rc = append_strbuf_str(buff, "device node rules:\n- blacklist:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->blist_devnode)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->elist_devnode)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "udev property rules:\n- blacklist:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->blist_property)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->elist_property)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "protocol rules:\n- blacklist:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->blist_protocol)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->elist_protocol)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "wwid rules:\n- blacklist:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->blist_wwid)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_group(buff, &conf->elist_wwid)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "device rules:\n- blacklist:\n")) < 0)
+		return rc;
+	if ((rc = snprint_blacklist_devgroup(buff, &conf->blist_device)) < 0)
+		return rc;
+
+	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
+	     return rc;
+	if ((rc = snprint_blacklist_devgroup(buff, &conf->elist_device)) < 0)
+		return rc;
+
+	return get_strbuf_len(buff) - initial_len;
+}
+
+static int snprint_blacklist(const struct config *conf, struct strbuf *buff)
+{
+	int i, rc;
 	struct blentry * ble;
 	struct blentry_device * bled;
-	int fwd = 0;
 	struct keyword *rootkw;
 	struct keyword *kw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "blacklist");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "blacklist {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "blacklist {\n")) < 0)
+		return rc;
 
 	vector_foreach_slot (conf->blist_devnode, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ble);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->blist_wwid, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ble);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->blist_property, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "property");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ble);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->blist_protocol, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ble);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
+			return rc;
 	}
+
 	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
 	if (!rootkw)
 		return 0;
 
 	vector_foreach_slot (conf->blist_device, bled, i) {
-		fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
-		if (fwd >= len)
-			return len;
+		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
+			return rc;
+
 		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				       kw, bled);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, bled)) < 0)
+			return rc;
 		kw = find_keyword(conf->keywords, rootkw->sub, "product");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				       kw, bled);
-		if (fwd >= len)
-			return len;
-		fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff,
+					  "\t\t%k %v\n\t}\n", kw, bled)) < 0)
+			return rc;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 static int snprint_blacklist_except(const struct config *conf,
-				    char *buff, int len)
+				    struct strbuf *buff)
 {
-	int i;
+	int i, rc;
 	struct blentry * ele;
 	struct blentry_device * eled;
-	int fwd = 0;
 	struct keyword *rootkw;
 	struct keyword *kw;
+	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "blacklist_exceptions");
 	if (!rootkw)
 		return 0;
 
-	fwd += snprintf(buff + fwd, len - fwd, "blacklist_exceptions {\n");
-	if (fwd >= len)
-		return len;
+	if ((rc = append_strbuf_str(buff, "blacklist_exceptions {\n")) < 0)
+		return rc;
 
 	vector_foreach_slot (conf->elist_devnode, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ele);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->elist_wwid, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ele);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->elist_property, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "property");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ele);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
+			return rc;
 	}
 	vector_foreach_slot (conf->elist_protocol, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
-				       kw, ele);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
+			return rc;
 	}
+
 	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
 	if (!rootkw)
 		return 0;
 
 	vector_foreach_slot (conf->elist_device, eled, i) {
-		fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
-		if (fwd >= len)
-			return len;
+		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
+			return rc;
+
 		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				       kw, eled);
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, eled)) < 0)
+			return rc;
 		kw = find_keyword(conf->keywords, rootkw->sub, "product");
 		if (!kw)
 			return 0;
-		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
-				       kw, eled);
-		if (fwd >= len)
-			return len;
-		fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
-		if (fwd >= len)
-			return len;
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n",
+					  kw, eled)) < 0)
+			return rc;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "}\n");
-	if (fwd >= len)
-		return len;
-	return fwd;
+
+	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
+		return rc;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 char *snprint_config(const struct config *conf, int *len,
 		     const struct _vector *hwtable, const struct _vector *mpvec)
 {
+	STRBUF_ON_STACK(buff);
 	char *reply;
-	/* built-in config is >20kB already */
-	unsigned int maxlen = 32768;
-
-	for (reply = NULL; maxlen <= UINT_MAX/2; maxlen *= 2) {
-		char *c, *tmp = reply;
-
-		reply = REALLOC(reply, maxlen);
-		if (!reply) {
-			if (tmp)
-				free(tmp);
+	int rc;
+
+	if ((rc = snprint_defaults(conf, &buff)) < 0 ||
+	    (rc = snprint_blacklist(conf, &buff)) < 0 ||
+	    (rc = snprint_blacklist_except(conf, &buff)) < 0 ||
+	    (rc = snprint_hwtable(conf, &buff,
+				  hwtable ? hwtable : conf->hwtable)) < 0 ||
+	    (rc = snprint_overrides(conf, &buff, conf->overrides)) < 0)
+		return NULL;
+	if (VECTOR_SIZE(conf->mptable) > 0 ||
+	    (mpvec != NULL && VECTOR_SIZE(mpvec) > 0))
+		if ((rc = snprint_mptable(conf, &buff, mpvec)) < 0)
 			return NULL;
-		}
-
-		c = reply + snprint_defaults(conf, reply, maxlen);
-		if (c == reply + maxlen)
-			continue;
 
-		c += snprint_blacklist(conf, c, reply + maxlen - c);
-		if (c == reply + maxlen)
-			continue;
-
-		c += snprint_blacklist_except(conf, c, reply + maxlen - c);
-		if (c == reply + maxlen)
-			continue;
-
-		c += snprint_hwtable(conf, c, reply + maxlen - c,
-				     hwtable ? hwtable : conf->hwtable);
-		if (c == reply + maxlen)
-			continue;
+	if (len)
+		*len = get_strbuf_len(&buff);
+	reply = steal_strbuf_str(&buff);
 
-		c += snprint_overrides(conf, c, reply + maxlen - c,
-				       conf->overrides);
-		if (c == reply + maxlen)
-			continue;
-
-		if (VECTOR_SIZE(conf->mptable) > 0 ||
-		    (mpvec != NULL && VECTOR_SIZE(mpvec) > 0))
-			c += snprint_mptable(conf, c, reply + maxlen - c,
-					     mpvec);
-
-		if (c < reply + maxlen) {
-			if (len)
-				*len = c - reply;
-			return reply;
-		}
-	}
-
-	free(reply);
-	return NULL;
+	return reply;
 }
 
-int snprint_status(char *buff, int len, const struct vectors *vecs)
+int snprint_status(struct strbuf *buff, const struct vectors *vecs)
 {
-	int fwd = 0;
-	int i;
+	int i, rc;
 	unsigned int count[PATH_MAX_STATE] = {0};
+	int monitored_count = 0;
 	struct path * pp;
+	size_t initial_len = get_strbuf_len(buff);
 
 	vector_foreach_slot (vecs->pathvec, pp, i) {
 		count[pp->state]++;
 	}
-	fwd += snprintf(buff + fwd, len - fwd, "path checker states:\n");
-	for (i=0; i<PATH_MAX_STATE; i++) {
+	if ((rc = append_strbuf_str(buff, "path checker states:\n")) < 0)
+		return rc;
+	for (i = 0; i < PATH_MAX_STATE; i++) {
 		if (!count[i])
 			continue;
-		fwd += snprintf(buff + fwd, len - fwd, "%-20s%u\n",
-				checker_state_name(i), count[i]);
+		if ((rc = print_strbuf(buff, "%-20s%u\n",
+				       checker_state_name(i), count[i])) < 0)
+			return rc;
 	}
 
-	int monitored_count = 0;
-
 	vector_foreach_slot(vecs->pathvec, pp, i)
 		if (pp->fd >= 0)
 			monitored_count++;
-	fwd += snprintf(buff + fwd, len - fwd, "\npaths: %d\nbusy: %s\n",
-			monitored_count, is_uevent_busy()? "True" : "False");
+	if ((rc = print_strbuf(buff, "\npaths: %d\nbusy: %s\n",
+			       monitored_count,
+			       is_uevent_busy()? "True" : "False")) < 0)
+		return rc;
 
-	if (fwd >= len)
-		return len;
-	return fwd;
+	return get_strbuf_len(buff) - initial_len;
 }
 
-int snprint_devices(struct config *conf, char *buff, size_t len,
+int snprint_devices(struct config *conf, struct strbuf *buff,
 		    const struct vectors *vecs)
 {
-	size_t fwd = 0;
 	int r;
 	struct udev_enumerate *enm;
 	struct udev_list_entry *item, *first;
-
 	struct path * pp;
+	size_t initial_len = get_strbuf_len(buff);
 
 	enm = udev_enumerate_new(udev);
 	if (!enm)
 		return 1;
 	udev_enumerate_add_match_subsystem(enm, "block");
 
-	fwd += snprintf(buff + fwd, len - fwd, "available block devices:\n");
+	if ((r = append_strbuf_str(buff, "available block devices:\n")) < 0)
+		goto out;
 	r = udev_enumerate_scan_devices(enm);
 	if (r < 0)
 		goto out;
@@ -2066,10 +1816,6 @@ int snprint_devices(struct config *conf, char *buff, size_t len,
 			continue;
 		}
 
-		fwd += snprintf(buff + fwd, len - fwd, "    %s", devname);
-		if (fwd >= len)
-			break;
-
 		pp = find_path_by_dev(vecs->pathvec, devname);
 		if (!pp) {
 			const char *hidden;
@@ -2092,41 +1838,27 @@ int snprint_devices(struct config *conf, char *buff, size_t len,
 		} else
 			status = " devnode whitelisted, monitored";
 
-		fwd += snprintf(buff + fwd, len - fwd, " %s\n", status);
+		r = print_strbuf(buff, "    %s %s\n", devname, status);
 		udev_device_unref(u_dev);
-		if (fwd >= len)
+		if (r < 0)
 			break;
 	}
 out:
 	udev_enumerate_unref(enm);
+	if (r < 0)
+		return r;
 
-	if (fwd >= len)
-		return len;
-	return fwd;
+	return get_strbuf_len(buff) - initial_len;
 }
 
 /*
  * stdout printing helpers
  */
-void print_path(struct path *pp, char *style)
-{
-	char line[MAX_LINE_LEN];
-
-	memset(&line[0], 0, MAX_LINE_LEN);
-	snprint_path(&line[0], MAX_LINE_LEN, style, pp, 1);
-	printf("%s", line);
-}
-
-void print_all_paths(vector pathvec, int banner)
-{
-	print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
-}
-
-void print_all_paths_custo(vector pathvec, int banner, char *fmt)
+static void print_all_paths_custo(vector pathvec, int banner, const char *fmt)
 {
 	int i;
 	struct path * pp;
-	char line[MAX_LINE_LEN];
+	STRBUF_ON_STACK(line);
 
 	if (!VECTOR_SIZE(pathvec)) {
 		if (banner)
@@ -2135,12 +1867,18 @@ void print_all_paths_custo(vector pathvec, int banner, char *fmt)
 	}
 
 	if (banner)
-		fprintf(stdout, "===== paths list =====\n");
+		append_strbuf_str(&line, "===== paths list =====\n");
 
 	get_path_layout(pathvec, 1);
-	snprint_path_header(line, MAX_LINE_LEN, fmt);
-	fprintf(stdout, "%s", line);
+	snprint_path_header(&line, fmt);
 
 	vector_foreach_slot (pathvec, pp, i)
-		print_path(pp, fmt);
+		snprint_path(&line, fmt, pp, 1);
+
+	printf("%s", get_strbuf_str(&line));
+}
+
+void print_all_paths(vector pathvec, int banner)
+{
+	print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
 }
diff --git a/libmultipath/print.h b/libmultipath/print.h
index 0042cef..b922812 100644
--- a/libmultipath/print.h
+++ b/libmultipath/print.h
@@ -2,6 +2,8 @@
 #define _PRINT_H
 #include "dm-generic.h"
 
+struct strbuf;
+
 #define PRINT_PATH_LONG      "%w %i %d %D %p %t %T %s %o"
 #define PRINT_PATH_INDENT    "%i %d %D %t %T %o"
 #define PRINT_PATH_CHECKER   "%i %d %D %p %t %T %o %C"
@@ -25,7 +27,7 @@
 #define PRINT_JSON_END_LAST_ELEM  "}"
 #define PRINT_JSON_END_LAST       "}\n"
 #define PRINT_JSON_END_ARRAY      "]\n"
-#define PRINT_JSON_INDENT    "   "
+#define PRINT_JSON_INDENT_N    3
 #define PRINT_JSON_MAP       "{\n" \
 			     "      \"name\" : \"%n\",\n" \
 			     "      \"uuid\" : \"%w\",\n" \
@@ -79,21 +81,21 @@ struct path_data {
 	char wildcard;
 	char * header;
 	unsigned int width;
-	int (*snprint)(char * buff, size_t len, const struct path * pp);
+	int (*snprint)(struct strbuf *, const struct path * pp);
 };
 
 struct multipath_data {
 	char wildcard;
 	char * header;
 	unsigned int width;
-	int (*snprint)(char * buff, size_t len, const struct multipath * mpp);
+	int (*snprint)(struct strbuf *, const struct multipath * mpp);
 };
 
 struct pathgroup_data {
 	char wildcard;
 	char * header;
 	unsigned int width;
-	int (*snprint)(char * buff, size_t len, const struct pathgroup * pgp);
+	int (*snprint)(struct strbuf *, const struct pathgroup * pgp);
 };
 
 enum layout_reset {
@@ -106,37 +108,35 @@ void _get_path_layout (const struct _vector *gpvec, enum layout_reset);
 void get_path_layout (vector pathvec, int header);
 void _get_multipath_layout (const struct _vector *gmvec, enum layout_reset);
 void get_multipath_layout (vector mpvec, int header);
-int snprint_path_header (char *, int, const char *);
-int snprint_multipath_header (char *, int, const char *);
-int _snprint_path (const struct gen_path *, char *, int, const char *, int);
-#define snprint_path(buf, len, fmt, pp, v) \
-	_snprint_path(dm_path_to_gen(pp), buf, len, fmt,  v)
-int _snprint_multipath (const struct gen_multipath *, char *, int,
+int snprint_path_header(struct strbuf *, const char *);
+int snprint_multipath_header(struct strbuf *, const char *);
+int _snprint_path (const struct gen_path *, struct strbuf *, const char *, int);
+#define snprint_path(buf, fmt, pp, v) \
+	_snprint_path(dm_path_to_gen(pp), buf, fmt,  v)
+int _snprint_multipath (const struct gen_multipath *, struct strbuf *,
 			const char *, int);
-#define snprint_multipath(buf, len, fmt, mp, v)				\
-	_snprint_multipath(dm_multipath_to_gen(mp), buf, len, fmt,  v)
-int _snprint_multipath_topology (const struct gen_multipath *, char *, int,
+#define snprint_multipath(buf, fmt, mp, v)				\
+	_snprint_multipath(dm_multipath_to_gen(mp), buf, fmt,  v)
+int _snprint_multipath_topology (const struct gen_multipath *, struct strbuf *,
 				 int verbosity);
-#define snprint_multipath_topology(buf, len, mpp, v) \
-	_snprint_multipath_topology (dm_multipath_to_gen(mpp), buf, len, v)
-int snprint_multipath_topology_json (char * buff, int len,
-				const struct vectors * vecs);
+#define snprint_multipath_topology(buf, mpp, v) \
+	_snprint_multipath_topology (dm_multipath_to_gen(mpp), buf, v)
+int snprint_multipath_topology_json(struct strbuf *, const struct vectors *vecs);
 char *snprint_config(const struct config *conf, int *len,
 		     const struct _vector *hwtable,
 		     const struct _vector *mpvec);
-int snprint_multipath_map_json (char * buff, int len,
-				const struct multipath * mpp);
-int snprint_blacklist_report (struct config *, char *, int);
-int snprint_wildcards (char *, int);
-int snprint_status (char *, int, const struct vectors *);
-int snprint_devices (struct config *, char *, size_t, const struct vectors *);
-int snprint_path_serial (char *, size_t, const struct path *);
-int snprint_host_wwnn (char *, size_t, const struct path *);
-int snprint_host_wwpn (char *, size_t, const struct path *);
-int snprint_tgt_wwnn (char *, size_t, const struct path *);
-int snprint_tgt_wwpn (char *, size_t, const struct path *);
+int snprint_multipath_map_json(struct strbuf *, const struct multipath *mpp);
+int snprint_blacklist_report(struct config *, struct strbuf *);
+int snprint_wildcards(struct strbuf *);
+int snprint_status(struct strbuf *, const struct vectors *);
+int snprint_devices(struct config *, struct strbuf *, const struct vectors *);
+int snprint_path_serial(struct strbuf *, const struct path *);
+int snprint_host_wwnn(struct strbuf *, const struct path *);
+int snprint_host_wwpn(struct strbuf *, const struct path *);
+int snprint_tgt_wwnn(struct strbuf *, const struct path *);
+int snprint_tgt_wwpn(struct strbuf *, const struct path *);
 #define PROTOCOL_BUF_SIZE sizeof("scsi:unspec")
-int snprint_path_protocol(char *, size_t, const struct path *);
+int snprint_path_protocol(struct strbuf *, const struct path *);
 
 void _print_multipath_topology (const struct gen_multipath * gmp,
 				int verbosity);
@@ -144,14 +144,13 @@ void _print_multipath_topology (const struct gen_multipath * gmp,
 	_print_multipath_topology(dm_multipath_to_gen(mpp), v)
 
 void print_all_paths (vector pathvec, int banner);
-void print_all_paths_custo (vector pathvec, int banner, char *fmt);
 
 int snprint_path_attr(const struct gen_path* gp,
-		      char *buf, int len, char wildcard);
+		      struct strbuf *buf, char wildcard);
 int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
-			   char *buf, int len, char wildcard);
+			   struct strbuf *buf, char wildcard);
 int snprint_multipath_attr(const struct gen_multipath* gm,
-			   char *buf, int len, char wildcard);
+			   struct strbuf *buf, char wildcard);
 int snprint_multipath_style(const struct gen_multipath *gmp,
-			    char *style, int len, int verbosity);
+			    struct strbuf *style, int verbosity);
 #endif /* _PRINT_H */
diff --git a/libmultipath/prioritizers/weightedpath.c b/libmultipath/prioritizers/weightedpath.c
index 650088b..ea03fc3 100644
--- a/libmultipath/prioritizers/weightedpath.c
+++ b/libmultipath/prioritizers/weightedpath.c
@@ -35,52 +35,37 @@
 #include "structs_vec.h"
 #include "print.h"
 #include "util.h"
-
-#define CHECK_LEN \
-do { \
-	if ((p - str) >= (len - 1)) { \
-		condlog(0, "%s: %s - buffer size too small", pp->dev, pp->prio.name); \
-		return -1; \
-	} \
-} while(0)
+#include "strbuf.h"
 
 static int
-build_serial_path(struct path *pp, char *str, int len)
+build_serial_path(struct path *pp, struct strbuf *buf)
 {
-	char *p = str;
+	int rc = snprint_path_serial(buf, pp);
 
-	p += snprint_path_serial(p, str + len - p, pp);
-	CHECK_LEN;
-	return 0;
+	return rc < 0 ? rc : 0;
 }
 
 static int
-build_wwn_path(struct path *pp, char *str, int len)
+build_wwn_path(struct path *pp, struct strbuf *buf)
 {
-	char *p = str;
-
-	p += snprint_host_wwnn(p, str + len - p, pp);
-	CHECK_LEN;
-	p += snprintf(p, str + len - p, ":");
-	CHECK_LEN;
-	p += snprint_host_wwpn(p, str + len - p, pp);
-	CHECK_LEN;
-	p += snprintf(p, str + len - p, ":");
-	CHECK_LEN;
-	p += snprint_tgt_wwnn(p, str + len - p, pp);
-	CHECK_LEN;
-	p += snprintf(p, str + len - p, ":");
-	CHECK_LEN;
-	p += snprint_tgt_wwpn(p, str + len - p, pp);
-	CHECK_LEN;
+	int rc;
+
+	if ((rc = snprint_host_wwnn(buf, pp)) < 0 ||
+	    (rc = fill_strbuf(buf, ':', 1)) < 0 ||
+	    (rc = snprint_host_wwpn(buf, pp)) < 0 ||
+	    (rc = fill_strbuf(buf, ':', 1)) < 0 ||
+	    (rc = snprint_tgt_wwnn(buf, pp) < 0) ||
+	    (rc = fill_strbuf(buf, ':', 1) < 0) ||
+	    (rc = snprint_tgt_wwpn(buf, pp) < 0))
+		return rc;
 	return 0;
 }
 
 /* main priority routine */
 int prio_path_weight(struct path *pp, char *prio_args)
 {
-	char path[FILE_NAME_SIZE];
-	char *arg;
+	STRBUF_ON_STACK(path);
+	char *arg __attribute__((cleanup(cleanup_charp))) = NULL;
 	char *temp, *regex, *prio;
 	char split_char[] = " \t";
 	int priority = DEFAULT_PRIORITY, path_found = 0;
@@ -101,24 +86,22 @@ int prio_path_weight(struct path *pp, char *prio_args)
 	}
 
 	if (!strcmp(regex, HBTL)) {
-		sprintf(path, "%d:%d:%d:%" PRIu64, pp->sg_id.host_no,
-			pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun);
+		if (print_strbuf(&path, "%d:%d:%d:%" PRIu64, pp->sg_id.host_no,
+				 pp->sg_id.channel, pp->sg_id.scsi_id,
+				 pp->sg_id.lun) < 0)
+			return priority;
 	} else if (!strcmp(regex, DEV_NAME)) {
-		strcpy(path, pp->dev);
+		if (append_strbuf_str(&path, pp->dev) < 0)
+			return priority;
 	} else if (!strcmp(regex, SERIAL)) {
-		if (build_serial_path(pp, path, FILE_NAME_SIZE) != 0) {
-			FREE(arg);
+		if (build_serial_path(pp, &path) != 0)
 			return priority;
-		}
 	} else if (!strcmp(regex, WWN)) {
-		if (build_wwn_path(pp, path, FILE_NAME_SIZE) != 0) {
-			FREE(arg);
+		if (build_wwn_path(pp, &path) != 0)
 			return priority;
-		}
 	} else {
 		condlog(0, "%s: %s - Invalid arguments", pp->dev,
 			pp->prio.name);
-		FREE(arg);
 		return priority;
 	}
 
@@ -131,7 +114,8 @@ int prio_path_weight(struct path *pp, char *prio_args)
 			break;
 
 		if (!regcomp(&pathe, regex, REG_EXTENDED|REG_NOSUB)) {
-			if (!regexec(&pathe, path, 0, NULL, 0)) {
+			if (!regexec(&pathe, get_strbuf_str(&path), 0,
+				     NULL, 0)) {
 				path_found = 1;
 				priority = atoi(prio);
 			}
@@ -139,7 +123,6 @@ int prio_path_weight(struct path *pp, char *prio_args)
 		}
 	}
 
-	FREE(arg);
 	return priority;
 }
 
diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c
index bce40b1..6d3a0ae 100644
--- a/multipathd/cli_handlers.c
+++ b/multipathd/cli_handlers.c
@@ -30,6 +30,7 @@
 #include "cli.h"
 #include "uevent.h"
 #include "foreign.h"
+#include "strbuf.h"
 #include "cli_handlers.h"
 
 #define SET_REPLY_AND_LEN(__rep, __len, string_literal)			\
@@ -42,49 +43,30 @@ int
 show_paths (char ** r, int * len, struct vectors * vecs, char * style,
 	    int pretty)
 {
+	STRBUF_ON_STACK(reply);
 	int i;
 	struct path * pp;
-	char * c;
-	char * reply, * header;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	int hdr_len = 0;
 
 	get_path_layout(vecs->pathvec, 1);
 	foreign_path_layout();
 
-	reply = MALLOC(maxlen);
+	if (pretty && (hdr_len = snprint_path_header(&reply, style)) < 0)
+		return 1;
 
-	while (again) {
-		if (!reply)
+	vector_foreach_slot(vecs->pathvec, pp, i) {
+		if (snprint_path(&reply, style, pp, pretty) < 0)
 			return 1;
-
-		c = reply;
-
-		if (pretty)
-			c += snprint_path_header(c, reply + maxlen - c,
-						 style);
-		header = c;
-
-		vector_foreach_slot(vecs->pathvec, pp, i)
-			c += snprint_path(c, reply + maxlen - c,
-					  style, pp, pretty);
-
-		c += snprint_foreign_paths(c, reply + maxlen - c,
-					   style, pretty);
-
-		again = (c == reply + maxlen - 1);
-
-		REALLOC_REPLY(reply, again, maxlen);
 	}
+	if (snprint_foreign_paths(&reply, style, pretty) < 0)
+		return 1;
 
-	if (pretty && c == header) {
+	if (pretty && get_strbuf_len(&reply) == (size_t)hdr_len)
 		/* No output - clear header */
-		*reply = '\0';
-		c = reply;
-	}
+		truncate_strbuf(&reply, 0);
 
-	*r = reply;
-	*len = (int)(c - reply + 1);
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -92,28 +74,14 @@ int
 show_path (char ** r, int * len, struct vectors * vecs, struct path *pp,
 	   char * style)
 {
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	STRBUF_ON_STACK(reply);
 
 	get_path_layout(vecs->pathvec, 1);
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return 1;
-
-		c = reply;
-
-		c += snprint_path(c, reply + maxlen - c, style, pp, 0);
-
-		again = (c == reply + maxlen - 1);
+	if (snprint_path(&reply, style, pp, 0) < 0)
+		return 1;
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	*r = reply;
-	*len = (int)(c - reply + 1);
 	return 0;
 }
 
@@ -121,84 +89,51 @@ int
 show_map_topology (char ** r, int * len, struct multipath * mpp,
 		   struct vectors * vecs)
 {
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	STRBUF_ON_STACK(reply);
 
 	if (update_multipath(vecs, mpp->alias, 0))
 		return 1;
-	reply = MALLOC(maxlen);
 
-	while (again) {
-		if (!reply)
-			return 1;
-
-		c = reply;
-
-		c += snprint_multipath_topology(c, reply + maxlen - c, mpp, 2);
-		again = (c == reply + maxlen - 1);
+	if (snprint_multipath_topology(&reply, mpp, 2) < 0)
+		return 1;
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	*r = reply;
-	*len = (int)(c - reply + 1);
 	return 0;
 }
 
 int
 show_maps_topology (char ** r, int * len, struct vectors * vecs)
 {
+	STRBUF_ON_STACK(reply);
 	int i;
 	struct multipath * mpp;
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
 
 	get_path_layout(vecs->pathvec, 0);
 	foreign_path_layout();
 
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return 1;
-
-		c = reply;
-
-		vector_foreach_slot(vecs->mpvec, mpp, i) {
-			if (update_multipath(vecs, mpp->alias, 0)) {
-				i--;
-				continue;
-			}
-			c += snprint_multipath_topology(c, reply + maxlen - c,
-							mpp, 2);
+	vector_foreach_slot(vecs->mpvec, mpp, i) {
+		if (update_multipath(vecs, mpp->alias, 0)) {
+			i--;
+			continue;
 		}
-		c += snprint_foreign_topology(c, reply + maxlen - c, 2);
-
-		again = (c == reply + maxlen - 1);
-
-		REALLOC_REPLY(reply, again, maxlen);
+		if (snprint_multipath_topology(&reply, mpp, 2) < 0)
+			return 1;
 	}
+	if (snprint_foreign_topology(&reply, 2) < 0)
+		return 1;
 
-	*r = reply;
-	*len = (int)(c - reply + 1);
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
 int
 show_maps_json (char ** r, int * len, struct vectors * vecs)
 {
+	STRBUF_ON_STACK(reply);
 	int i;
 	struct multipath * mpp;
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
-
-	if (VECTOR_SIZE(vecs->mpvec) > 0)
-		maxlen *= PRINT_JSON_MULTIPLIER * VECTOR_SIZE(vecs->mpvec);
 
 	vector_foreach_slot(vecs->mpvec, mpp, i) {
 		if (update_multipath(vecs, mpp->alias, 0)) {
@@ -206,21 +141,11 @@ show_maps_json (char ** r, int * len, struct vectors * vecs)
 		}
 	}
 
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return 1;
-
-		c = reply;
-
-		c += snprint_multipath_topology_json(c, maxlen, vecs);
-		again = (c == reply + maxlen);
+	if (snprint_multipath_topology_json(&reply, vecs) < 0)
+		return 1;
 
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	*r = reply;
-	*len = (int)(c - reply);
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -228,28 +153,16 @@ int
 show_map_json (char ** r, int * len, struct multipath * mpp,
 		   struct vectors * vecs)
 {
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	STRBUF_ON_STACK(reply);
 
 	if (update_multipath(vecs, mpp->alias, 0))
 		return 1;
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return 1;
 
-		c = reply;
-
-		c += snprint_multipath_map_json(c, maxlen, mpp);
-		again = (c == reply + maxlen);
+	if (snprint_multipath_map_json(&reply, mpp) < 0)
+		return 1;
 
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	*r = reply;
-	*len = (int)(c - reply);
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -420,58 +333,40 @@ cli_list_maps_json (void * v, char ** reply, int * len, void * data)
 int
 cli_list_wildcards (void * v, char ** reply, int * len, void * data)
 {
-	char * c;
-
-	*reply = MALLOC(INITIAL_REPLY_LEN);
+	STRBUF_ON_STACK(buf);
 
-	if (!*reply)
+	if (snprint_wildcards(&buf) < 0)
 		return 1;
 
-	c = *reply;
-	c += snprint_wildcards(c, INITIAL_REPLY_LEN);
-
-	*len = INITIAL_REPLY_LEN;
+	*len = get_strbuf_len(&buf) + 1;
+	*reply = steal_strbuf_str(&buf);
 	return 0;
 }
 
 int
 show_status (char ** r, int *len, struct vectors * vecs)
 {
-	char * c;
-	char * reply;
-
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	reply = MALLOC(maxlen);
+	STRBUF_ON_STACK(reply);
 
-	if (!reply)
+	if (snprint_status(&reply, vecs) < 0)
 		return 1;
 
-	c = reply;
-	c += snprint_status(c, reply + maxlen - c, vecs);
-
-	*r = reply;
-	*len = (int)(c - reply + 1);
+	*len = get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
 int
 show_daemon (char ** r, int *len)
 {
-	char * c;
-	char * reply;
-
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	reply = MALLOC(maxlen);
+	STRBUF_ON_STACK(reply);
 
-	if (!reply)
+	if (print_strbuf(&reply, "pid %d %s\n",
+			 daemon_pid, daemon_status()) < 0)
 		return 1;
 
-	c = reply;
-	c += snprintf(c, INITIAL_REPLY_LEN, "pid %d %s\n",
-		      daemon_pid, daemon_status());
-
-	*r = reply;
-	*len = (int)(c - reply + 1);
+	*len = get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -479,26 +374,13 @@ int
 show_map (char ** r, int *len, struct multipath * mpp, char * style,
 	  int pretty)
 {
-	char * c;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
-
-	reply = MALLOC(maxlen);
-	while (again) {
-		if (!reply)
-			return 1;
+	STRBUF_ON_STACK(reply);
 
-		c = reply;
-		c += snprint_multipath(c, reply + maxlen - c, style,
-				       mpp, pretty);
-
-		again = (c == reply + maxlen - 1);
+	if (snprint_multipath(&reply, style, mpp, pretty) < 0)
+		return 1;
 
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	*r = reply;
-	*len = (int)(c - reply + 1);
+	*len = get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -506,51 +388,34 @@ int
 show_maps (char ** r, int *len, struct vectors * vecs, char * style,
 	   int pretty)
 {
+	STRBUF_ON_STACK(reply);
 	int i;
 	struct multipath * mpp;
-	char * c, *header;
-	char * reply;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	int hdr_len = 0;
 
 	get_multipath_layout(vecs->mpvec, 1);
 	foreign_multipath_layout();
 
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return 1;
-
-		c = reply;
-		if (pretty)
-			c += snprint_multipath_header(c, reply + maxlen - c,
-						      style);
-		header = c;
-
-		vector_foreach_slot(vecs->mpvec, mpp, i) {
-			if (update_multipath(vecs, mpp->alias, 0)) {
-				i--;
-				continue;
-			}
-			c += snprint_multipath(c, reply + maxlen - c,
-					       style, mpp, pretty);
+	if (pretty && (hdr_len = snprint_multipath_header(&reply, style)) < 0)
+		return 1;
 
+	vector_foreach_slot(vecs->mpvec, mpp, i) {
+		if (update_multipath(vecs, mpp->alias, 0)) {
+			i--;
+			continue;
 		}
-		c += snprint_foreign_multipaths(c, reply + maxlen - c,
-						style, pretty);
-		again = (c == reply + maxlen - 1);
-
-		REALLOC_REPLY(reply, again, maxlen);
+		if (snprint_multipath(&reply, style, mpp, pretty) < 0)
+			return 1;
 	}
+	if (snprint_foreign_multipaths(&reply, style, pretty) < 0)
+		return 1;
 
-	if (pretty && c == header) {
+	if (pretty && get_strbuf_len(&reply) == (size_t)hdr_len)
 		/* No output - clear header */
-		*reply = '\0';
-		c = reply;
-	}
-	*r = reply;
-	*len = (int)(c - reply + 1);
+		truncate_strbuf(&reply, 0);
+
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -1366,34 +1231,20 @@ cli_fail(void * v, char ** reply, int * len, void * data)
 int
 show_blacklist (char ** r, int * len)
 {
-	char *c = NULL;
-	char *reply = NULL;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	STRBUF_ON_STACK(reply);
 	struct config *conf;
-	int fail = 0;
-
-	reply = MALLOC(maxlen);
+	bool fail;
 
 	conf = get_multipath_config();
 	pthread_cleanup_push(put_multipath_config, conf);
-	while (again) {
-		if (!reply) {
-			fail = 1;
-			break;
-		}
-
-		c = reply;
-		c += snprint_blacklist_report(conf, c, maxlen);
-		again = (c == reply + maxlen);
-		REALLOC_REPLY(reply, again, maxlen);
-	}
+	fail = snprint_blacklist_report(conf, &reply) < 0;
 	pthread_cleanup_pop(1);
 
 	if (fail)
 		return 1;
-	*r = reply;
-	*len = (int)(c - reply + 1);
+
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 	return 0;
 }
 
@@ -1408,34 +1259,20 @@ cli_list_blacklist (void * v, char ** reply, int * len, void * data)
 int
 show_devices (char ** r, int * len, struct vectors *vecs)
 {
-	char *c = NULL;
-	char *reply = NULL;
-	unsigned int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
+	STRBUF_ON_STACK(reply);
 	struct config *conf;
-	int fail = 0;
-
-	reply = MALLOC(maxlen);
+	bool fail;
 
 	conf = get_multipath_config();
 	pthread_cleanup_push(put_multipath_config, conf);
-	while (again) {
-		if (!reply) {
-			fail = 1;
-			break;
-		}
-
-		c = reply;
-		c += snprint_devices(conf, c, maxlen, vecs);
-		again = (c == reply + maxlen);
-		REALLOC_REPLY(reply, again, maxlen);
-	}
+	fail = snprint_devices(conf, &reply, vecs) < 0;
 	pthread_cleanup_pop(1);
 
 	if (fail)
 		return 1;
-	*r = reply;
-	*len = (int)(c - reply + 1);
+
+	*len = (int)get_strbuf_len(&reply) + 1;
+	*r = steal_strbuf_str(&reply);
 
 	return 0;
 }
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 6/9] libmultipath: print.c: fail hard if keywords are not found
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (4 preceding siblings ...)
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 5/9] libmultipath: use strbuf in print.c mwilck
@ 2021-08-11 15:41 ` mwilck
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 7/9] libmultipath: print.h: move macros to print.c mwilck
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

A failure in find_keyword() means an internal error. Fail hard rather
than returning an empty string.

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/print.c | 101 +++++++++++++++----------------------------
 1 file changed, 35 insertions(+), 66 deletions(-)

diff --git a/libmultipath/print.c b/libmultipath/print.c
index dd04dea..507e675 100644
--- a/libmultipath/print.c
+++ b/libmultipath/print.c
@@ -10,6 +10,7 @@
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
+#include <assert.h>
 #include <libudev.h>
 
 #include "checkers.h"
@@ -1294,14 +1295,9 @@ snprint_hwentry (const struct config *conf,
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "devices");
-
-	if (!rootkw || !rootkw->sub)
-		return 0;
-
+	assert(rootkw && rootkw->sub);
 	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
-
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
 		return rc;
@@ -1324,8 +1320,7 @@ static int snprint_hwtable(const struct config *conf, struct strbuf *buff,
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "devices");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "devices {\n")) < 0)
 		return rc;
@@ -1355,8 +1350,7 @@ snprint_mpentry (const struct config *conf, struct strbuf *buff,
 		return 0;
 
 	rootkw = find_keyword(conf->keywords, NULL, "multipath");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "\tmultipath {\n")) < 0)
 		return rc;
@@ -1387,8 +1381,7 @@ static int snprint_mptable(const struct config *conf, struct strbuf *buff,
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "multipaths");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "multipaths {\n")) < 0)
 		return rc;
@@ -1435,8 +1428,7 @@ static int snprint_overrides(const struct config *conf, struct strbuf *buff,
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "overrides");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "overrides {\n")) < 0)
 		return rc;
@@ -1461,8 +1453,7 @@ static int snprint_defaults(const struct config *conf, struct strbuf *buff)
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "defaults");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "defaults {\n")) < 0)
 		return rc;
@@ -1584,63 +1575,52 @@ static int snprint_blacklist(const struct config *conf, struct strbuf *buff)
 	struct blentry * ble;
 	struct blentry_device * bled;
 	struct keyword *rootkw;
-	struct keyword *kw;
+	struct keyword *kw, *pkw;
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "blacklist");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "blacklist {\n")) < 0)
 		return rc;
 
 	vector_foreach_slot (conf->blist_devnode, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->blist_wwid, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->blist_property, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "property");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->blist_protocol, ble, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
 			return rc;
 	}
 
 	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
+	kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
+	pkw = find_keyword(conf->keywords, rootkw->sub, "product");
+	assert(kw && pkw);
 
 	vector_foreach_slot (conf->blist_device, bled, i) {
-		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
-			return rc;
-
-		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
-		if (!kw)
-			return 0;
-		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, bled)) < 0)
+		if ((rc = snprint_keyword(buff, "\tdevice {\n\t\t%k %v\n",
+					  kw, bled)) < 0)
 			return rc;
-		kw = find_keyword(conf->keywords, rootkw->sub, "product");
-		if (!kw)
-			return 0;
-		if ((rc = snprint_keyword(buff,
-					  "\t\t%k %v\n\t}\n", kw, bled)) < 0)
+		if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n",
+					  pkw, bled)) < 0)
 			return rc;
 	}
 
@@ -1656,63 +1636,52 @@ static int snprint_blacklist_except(const struct config *conf,
 	struct blentry * ele;
 	struct blentry_device * eled;
 	struct keyword *rootkw;
-	struct keyword *kw;
+	struct keyword *kw, *pkw;
 	size_t initial_len = get_strbuf_len(buff);
 
 	rootkw = find_keyword(conf->keywords, NULL, "blacklist_exceptions");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
 
 	if ((rc = append_strbuf_str(buff, "blacklist_exceptions {\n")) < 0)
 		return rc;
 
 	vector_foreach_slot (conf->elist_devnode, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->elist_wwid, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->elist_property, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "property");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
 			return rc;
 	}
 	vector_foreach_slot (conf->elist_protocol, ele, i) {
 		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
-		if (!kw)
-			return 0;
+		assert(kw);
 		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
 			return rc;
 	}
 
 	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
-	if (!rootkw)
-		return 0;
+	assert(rootkw);
+	kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
+	pkw = find_keyword(conf->keywords, rootkw->sub, "product");
+	assert(kw && pkw);
 
 	vector_foreach_slot (conf->elist_device, eled, i) {
-		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
-			return rc;
-
-		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
-		if (!kw)
-			return 0;
-		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, eled)) < 0)
+		if ((rc = snprint_keyword(buff, "\tdevice {\n\t\t%k %v\n",
+					  kw, eled)) < 0)
 			return rc;
-		kw = find_keyword(conf->keywords, rootkw->sub, "product");
-		if (!kw)
-			return 0;
 		if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n",
-					  kw, eled)) < 0)
+					  pkw, eled)) < 0)
 			return rc;
 	}
 
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 7/9] libmultipath: print.h: move macros to print.c
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (5 preceding siblings ...)
  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 ` 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
  8 siblings, 0 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Move all macros to print.c that aren't used in other source files.

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/print.c | 66 +++++++++++++++++++++++++++++++++++++++++
 libmultipath/print.h | 70 +-------------------------------------------
 2 files changed, 67 insertions(+), 69 deletions(-)

diff --git a/libmultipath/print.c b/libmultipath/print.c
index 507e675..2fb9f4e 100644
--- a/libmultipath/print.c
+++ b/libmultipath/print.c
@@ -34,6 +34,72 @@
 #include "foreign.h"
 #include "strbuf.h"
 
+#define PRINT_PATH_LONG      "%w %i %d %D %p %t %T %s %o"
+#define PRINT_PATH_INDENT    "%i %d %D %t %T %o"
+#define PRINT_MAP_PROPS      "size=%S features='%f' hwhandler='%h' wp=%r"
+#define PRINT_PG_INDENT      "policy='%s' prio=%p status=%t"
+
+#define PRINT_JSON_MULTIPLIER     5
+#define PRINT_JSON_MAJOR_VERSION  0
+#define PRINT_JSON_MINOR_VERSION  1
+#define PRINT_JSON_START_VERSION  "   \"major_version\": %d,\n" \
+				  "   \"minor_version\": %d,\n"
+#define PRINT_JSON_START_ELEM     "{\n"
+#define PRINT_JSON_START_MAP      "   \"map\":"
+#define PRINT_JSON_START_MAPS     "\"maps\": ["
+#define PRINT_JSON_START_PATHS    "\"paths\": ["
+#define PRINT_JSON_START_GROUPS   "\"path_groups\": ["
+#define PRINT_JSON_END_ELEM       "},"
+#define PRINT_JSON_END_LAST_ELEM  "}"
+#define PRINT_JSON_END_LAST       "}\n"
+#define PRINT_JSON_END_ARRAY      "]\n"
+#define PRINT_JSON_INDENT_N    3
+#define PRINT_JSON_MAP       "{\n" \
+			     "      \"name\" : \"%n\",\n" \
+			     "      \"uuid\" : \"%w\",\n" \
+			     "      \"sysfs\" : \"%d\",\n" \
+			     "      \"failback\" : \"%F\",\n" \
+			     "      \"queueing\" : \"%Q\",\n" \
+			     "      \"paths\" : %N,\n" \
+			     "      \"write_prot\" : \"%r\",\n" \
+			     "      \"dm_st\" : \"%t\",\n" \
+			     "      \"features\" : \"%f\",\n" \
+			     "      \"hwhandler\" : \"%h\",\n" \
+			     "      \"action\" : \"%A\",\n" \
+			     "      \"path_faults\" : %0,\n" \
+			     "      \"vend\" : \"%v\",\n" \
+			     "      \"prod\" : \"%p\",\n" \
+			     "      \"rev\" : \"%e\",\n" \
+			     "      \"switch_grp\" : %1,\n" \
+			     "      \"map_loads\" : %2,\n" \
+			     "      \"total_q_time\" : %3,\n" \
+			     "      \"q_timeouts\" : %4,"
+
+#define PRINT_JSON_GROUP     "{\n" \
+			     "         \"selector\" : \"%s\",\n" \
+			     "         \"pri\" : %p,\n" \
+			     "         \"dm_st\" : \"%t\",\n" \
+			     "         \"marginal_st\" : \"%M\","
+
+#define PRINT_JSON_GROUP_NUM "         \"group\" : %d,\n"
+
+#define PRINT_JSON_PATH      "{\n" \
+			     "            \"dev\" : \"%d\",\n"\
+			     "            \"dev_t\" : \"%D\",\n" \
+			     "            \"dm_st\" : \"%t\",\n" \
+			     "            \"dev_st\" : \"%o\",\n" \
+			     "            \"chk_st\" : \"%T\",\n" \
+			     "            \"checker\" : \"%c\",\n" \
+			     "            \"pri\" : %p,\n" \
+			     "            \"host_wwnn\" : \"%N\",\n" \
+			     "            \"target_wwnn\" : \"%n\",\n" \
+			     "            \"host_wwpn\" : \"%R\",\n" \
+			     "            \"target_wwpn\" : \"%r\",\n" \
+			     "            \"host_adapter\" : \"%a\",\n" \
+			     "            \"marginal_st\" : \"%M\""
+
+#define PROGRESS_LEN  10
+
 #define MAX(x,y) (((x) > (y)) ? (x) : (y))
 #define MIN(x,y) (((x) > (y)) ? (y) : (x))
 /*
diff --git a/libmultipath/print.h b/libmultipath/print.h
index b922812..c6674a5 100644
--- a/libmultipath/print.h
+++ b/libmultipath/print.h
@@ -2,80 +2,12 @@
 #define _PRINT_H
 #include "dm-generic.h"
 
-struct strbuf;
-
-#define PRINT_PATH_LONG      "%w %i %d %D %p %t %T %s %o"
-#define PRINT_PATH_INDENT    "%i %d %D %t %T %o"
 #define PRINT_PATH_CHECKER   "%i %d %D %p %t %T %o %C"
 #define PRINT_MAP_STATUS     "%n %F %Q %N %t %r"
 #define PRINT_MAP_STATS      "%n %0 %1 %2 %3 %4"
 #define PRINT_MAP_NAMES      "%n %d %w"
-#define PRINT_MAP_PROPS      "size=%S features='%f' hwhandler='%h' wp=%r"
-#define PRINT_PG_INDENT      "policy='%s' prio=%p status=%t"
-
-#define PRINT_JSON_MULTIPLIER     5
-#define PRINT_JSON_MAJOR_VERSION  0
-#define PRINT_JSON_MINOR_VERSION  1
-#define PRINT_JSON_START_VERSION  "   \"major_version\": %d,\n" \
-				  "   \"minor_version\": %d,\n"
-#define PRINT_JSON_START_ELEM     "{\n"
-#define PRINT_JSON_START_MAP      "   \"map\":"
-#define PRINT_JSON_START_MAPS     "\"maps\": ["
-#define PRINT_JSON_START_PATHS    "\"paths\": ["
-#define PRINT_JSON_START_GROUPS   "\"path_groups\": ["
-#define PRINT_JSON_END_ELEM       "},"
-#define PRINT_JSON_END_LAST_ELEM  "}"
-#define PRINT_JSON_END_LAST       "}\n"
-#define PRINT_JSON_END_ARRAY      "]\n"
-#define PRINT_JSON_INDENT_N    3
-#define PRINT_JSON_MAP       "{\n" \
-			     "      \"name\" : \"%n\",\n" \
-			     "      \"uuid\" : \"%w\",\n" \
-			     "      \"sysfs\" : \"%d\",\n" \
-			     "      \"failback\" : \"%F\",\n" \
-			     "      \"queueing\" : \"%Q\",\n" \
-			     "      \"paths\" : %N,\n" \
-			     "      \"write_prot\" : \"%r\",\n" \
-			     "      \"dm_st\" : \"%t\",\n" \
-			     "      \"features\" : \"%f\",\n" \
-			     "      \"hwhandler\" : \"%h\",\n" \
-			     "      \"action\" : \"%A\",\n" \
-			     "      \"path_faults\" : %0,\n" \
-			     "      \"vend\" : \"%v\",\n" \
-			     "      \"prod\" : \"%p\",\n" \
-			     "      \"rev\" : \"%e\",\n" \
-			     "      \"switch_grp\" : %1,\n" \
-			     "      \"map_loads\" : %2,\n" \
-			     "      \"total_q_time\" : %3,\n" \
-			     "      \"q_timeouts\" : %4,"
-
-#define PRINT_JSON_GROUP     "{\n" \
-			     "         \"selector\" : \"%s\",\n" \
-			     "         \"pri\" : %p,\n" \
-			     "         \"dm_st\" : \"%t\",\n" \
-			     "         \"marginal_st\" : \"%M\","
 
-#define PRINT_JSON_GROUP_NUM "         \"group\" : %d,\n"
-
-#define PRINT_JSON_PATH      "{\n" \
-			     "            \"dev\" : \"%d\",\n"\
-			     "            \"dev_t\" : \"%D\",\n" \
-			     "            \"dm_st\" : \"%t\",\n" \
-			     "            \"dev_st\" : \"%o\",\n" \
-			     "            \"chk_st\" : \"%T\",\n" \
-			     "            \"checker\" : \"%c\",\n" \
-			     "            \"pri\" : %p,\n" \
-			     "            \"host_wwnn\" : \"%N\",\n" \
-			     "            \"target_wwnn\" : \"%n\",\n" \
-			     "            \"host_wwpn\" : \"%R\",\n" \
-			     "            \"target_wwpn\" : \"%r\",\n" \
-			     "            \"host_adapter\" : \"%a\",\n" \
-			     "            \"marginal_st\" : \"%M\""
-
-#define MAX_LINE_LEN  80
-#define MAX_LINES     64
-#define MAX_FIELD_LEN 128
-#define PROGRESS_LEN  10
+struct strbuf;
 
 struct path_data {
 	char wildcard;
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 8/9] libmultipath: use strbuf in alias.c.
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (6 preceding siblings ...)
  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 ` mwilck
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 9/9] multipathd: use strbuf in cli.c mwilck
  8 siblings, 0 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

We can avoid some buffer length checks here, too. Also, simplify the
implementation of format_devname().

Created a wrapper for the format_devname() test case, to avoid chaning
the test cases themselves.

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 libmultipath/alias.c | 84 +++++++++++++++++++-------------------------
 tests/alias.c        | 41 +++++++++++++--------
 2 files changed, 63 insertions(+), 62 deletions(-)

diff --git a/libmultipath/alias.c b/libmultipath/alias.c
index 02bc9d6..ad7e512 100644
--- a/libmultipath/alias.c
+++ b/libmultipath/alias.c
@@ -22,7 +22,7 @@
 #include "util.h"
 #include "errno.h"
 #include "devmapper.h"
-
+#include "strbuf.h"
 
 /*
  * significant parts of this file were taken from iscsi-bindings.c of the
@@ -61,31 +61,23 @@ valid_alias(const char *alias)
 	return 1;
 }
 
-
-static int
-format_devname(char *name, int id, int len, const char *prefix)
+static int format_devname(struct strbuf *buf, int id)
 {
-	int pos;
-	int prefix_len = strlen(prefix);
+	/*
+	 * We need: 7 chars for 32bit integers, 14 chars for 64bit integers
+	 */
+	char devname[2 * sizeof(int)];
+	int pos = sizeof(devname) - 1, rc;
 
-	if (len <= prefix_len + 1 || id <= 0)
+	if (id <= 0)
 		return -1;
 
-	memset(name, 0, len);
-	strcpy(name, prefix);
-	name[len - 1] = '\0';
-	for (pos = len - 2; pos >= prefix_len; pos--) {
-		id--;
-		name[pos] = 'a' + id % 26;
-		if (id < 26)
-			break;
-		id /= 26;
-	}
-	if (pos < prefix_len)
-		return -1;
+	devname[pos] = '\0';
+	for (; id >= 1; id /= 26)
+		devname[--pos] = 'a' + --id % 26;
 
-	memmove(name + prefix_len, name + pos, len - pos);
-	return (prefix_len + len - pos - 1);
+	rc = append_strbuf_str(buf, devname + pos);
+	return rc >= 0 ? rc : -1;
 }
 
 static int
@@ -123,11 +115,14 @@ scan_devname(const char *alias, const char *prefix)
 static int
 id_already_taken(int id, const char *prefix, const char *map_wwid)
 {
-	char alias[LINE_MAX];
+	STRBUF_ON_STACK(buf);
+	const char *alias;
 
-	if (format_devname(alias, id, LINE_MAX, prefix) < 0)
+	if (append_strbuf_str(&buf, prefix) < 0 ||
+	    format_devname(&buf, id) < 0)
 		return 0;
 
+	alias = get_strbuf_str(&buf);
 	if (dm_map_present(alias)) {
 		char wwid[WWID_SIZE];
 
@@ -285,10 +280,10 @@ rlookup_binding(FILE *f, char *buff, const char *map_alias)
 static char *
 allocate_binding(int fd, const char *wwid, int id, const char *prefix)
 {
-	char buf[LINE_MAX];
+	STRBUF_ON_STACK(buf);
 	off_t offset;
+	ssize_t len;
 	char *alias, *c;
-	int i;
 
 	if (id <= 0) {
 		condlog(0, "%s: cannot allocate new binding for id %d",
@@ -296,16 +291,12 @@ allocate_binding(int fd, const char *wwid, int id, const char *prefix)
 		return NULL;
 	}
 
-	i = format_devname(buf, id, LINE_MAX, prefix);
-	if (i == -1)
+	if (append_strbuf_str(&buf, prefix) < 0 ||
+	    format_devname(&buf, id) == -1)
 		return NULL;
 
-	c = buf + i;
-	if (snprintf(c, LINE_MAX - i, " %s\n", wwid) >= LINE_MAX - i) {
-		condlog(1, "%s: line too long for %s\n", __func__, wwid);
+	if (print_strbuf(&buf, " %s\n", wwid) < 0)
 		return NULL;
-	}
-	buf[LINE_MAX - 1] = '\0';
 
 	offset = lseek(fd, 0, SEEK_END);
 	if (offset < 0){
@@ -313,24 +304,25 @@ allocate_binding(int fd, const char *wwid, int id, const char *prefix)
 			strerror(errno));
 		return NULL;
 	}
-	if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)){
+
+	len = get_strbuf_len(&buf);
+	alias = steal_strbuf_str(&buf);
+
+	if (write(fd, alias, len) != len) {
 		condlog(0, "Cannot write binding to bindings file : %s",
 			strerror(errno));
 		/* clear partial write */
 		if (ftruncate(fd, offset))
 			condlog(0, "Cannot truncate the header : %s",
 				strerror(errno));
+		free(alias);
 		return NULL;
 	}
-	c = strchr(buf, ' ');
+	c = strchr(alias, ' ');
 	if (c)
 		*c = '\0';
 
-	condlog(3, "Created new binding [%s] for WWID [%s]", buf, wwid);
-	alias = strdup(buf);
-	if (alias == NULL)
-		condlog(0, "cannot copy new alias from bindings file: out of memory");
-
+	condlog(3, "Created new binding [%s] for WWID [%s]", alias, wwid);
 	return alias;
 }
 
@@ -560,7 +552,7 @@ static int add_binding(Bindings *bindings, const char *alias, const char *wwid)
 static int write_bindings_file(const Bindings *bindings, int fd)
 {
 	struct binding *bnd;
-	char line[LINE_MAX];
+	STRBUF_ON_STACK(line);
 	int i;
 
 	if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1)
@@ -570,16 +562,12 @@ static int write_bindings_file(const Bindings *bindings, int fd)
 	vector_foreach_slot(bindings, bnd, i) {
 		int len;
 
-		len = snprintf(line, sizeof(line), "%s %s\n",
-			       bnd->alias, bnd->wwid);
-
-		if (len < 0 || (size_t)len >= sizeof(line)) {
-			condlog(1, "%s: line overflow", __func__);
+		if ((len = print_strbuf(&line, "%s %s\n",
+					bnd->alias, bnd->wwid)) < 0)
 			return -1;
-		}
-
-		if (write(fd, line, len) != len)
+		if (write(fd, get_strbuf_str(&line), len) != len)
 			return -1;
+		truncate_strbuf(&line, 0);
 	}
 	return 0;
 }
diff --git a/tests/alias.c b/tests/alias.c
index 7e7c187..3ca6c28 100644
--- a/tests/alias.c
+++ b/tests/alias.c
@@ -81,12 +81,25 @@ int __wrap_dm_get_uuid(const char *name, char *uuid, int uuid_len)
 	return ret;
 }
 
+/* strbuf wrapper for the old format_devname() */
+static int __format_devname(char *name, int id, size_t len, const char *prefix)
+{
+	STRBUF_ON_STACK(buf);
+
+	if (append_strbuf_str(&buf, prefix) < 0 ||
+	    format_devname(&buf, id) < 0 ||
+	    len <= get_strbuf_len(&buf))
+		return -1;
+	strcpy(name, get_strbuf_str(&buf));
+	return get_strbuf_len(&buf);
+}
+
 static void fd_mpatha(void **state)
 {
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 1, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 1, sizeof(buf), "FOO");
 	assert_int_equal(rc, 4);
 	assert_string_equal(buf, "FOOa");
 }
@@ -97,7 +110,7 @@ static void fd_mpathz(void **state)
 	char buf[5];
 	int rc;
 
-	rc = format_devname(buf, 26, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26, sizeof(buf), "FOO");
 	assert_int_equal(rc, 4);
 	assert_string_equal(buf, "FOOz");
 }
@@ -107,7 +120,7 @@ static void fd_mpathaa(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26 + 1, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26 + 1, sizeof(buf), "FOO");
 	assert_int_equal(rc, 5);
 	assert_string_equal(buf, "FOOaa");
 }
@@ -117,7 +130,7 @@ static void fd_mpathzz(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26*26 + 26, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26*26 + 26, sizeof(buf), "FOO");
 	assert_int_equal(rc, 5);
 	assert_string_equal(buf, "FOOzz");
 }
@@ -127,7 +140,7 @@ static void fd_mpathaaa(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26*26 + 27, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26*26 + 27, sizeof(buf), "FOO");
 	assert_int_equal(rc, 6);
 	assert_string_equal(buf, "FOOaaa");
 }
@@ -137,7 +150,7 @@ static void fd_mpathzzz(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26*26*26 + 26*26 + 26, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26*26*26 + 26*26 + 26, sizeof(buf), "FOO");
 	assert_int_equal(rc, 6);
 	assert_string_equal(buf, "FOOzzz");
 }
@@ -147,7 +160,7 @@ static void fd_mpathaaaa(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26*26*26 + 26*26 + 27, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 26*26*26 + 26*26 + 27, sizeof(buf), "FOO");
 	assert_int_equal(rc, 7);
 	assert_string_equal(buf, "FOOaaaa");
 }
@@ -157,7 +170,7 @@ static void fd_mpathzzzz(void **state)
 	char buf[32];
 	int rc;
 
-	rc = format_devname(buf, 26*26*26*26 + 26*26*26 + 26*26 + 26,
+	rc = __format_devname(buf, 26*26*26*26 + 26*26*26 + 26*26 + 26,
 			    sizeof(buf), "FOO");
 	assert_int_equal(rc, 7);
 	assert_string_equal(buf, "FOOzzzz");
@@ -169,7 +182,7 @@ static void fd_mpath_max(void **state)
 	char buf[32];
 	int rc;
 
-	rc  = format_devname(buf, INT_MAX, sizeof(buf), "");
+	rc  = __format_devname(buf, INT_MAX, sizeof(buf), "");
 	assert_int_equal(rc, strlen(MPATH_ID_INT_MAX));
 	assert_string_equal(buf, MPATH_ID_INT_MAX);
 }
@@ -180,7 +193,7 @@ static void fd_mpath_max1(void **state)
 	char buf[32];
 	int rc;
 
-	rc  = format_devname(buf, INT_MIN, sizeof(buf), "");
+	rc  = __format_devname(buf, INT_MIN, sizeof(buf), "");
 	assert_int_equal(rc, -1);
 }
 
@@ -189,7 +202,7 @@ static void fd_mpath_short(void **state)
 	char buf[4];
 	int rc;
 
-	rc = format_devname(buf, 1, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 1, sizeof(buf), "FOO");
 	assert_int_equal(rc, -1);
 }
 
@@ -198,7 +211,7 @@ static void fd_mpath_short1(void **state)
 	char buf[5];
 	int rc;
 
-	rc = format_devname(buf, 27, sizeof(buf), "FOO");
+	rc = __format_devname(buf, 27, sizeof(buf), "FOO");
 	assert_int_equal(rc, -1);
 }
 
@@ -323,7 +336,7 @@ static void sd_fd_many(void **state)
 	int rc, i;
 
 	for (i = 1; i < 5000; i++) {
-		rc = format_devname(buf, i, sizeof(buf), "MPATH");
+		rc = __format_devname(buf, i, sizeof(buf), "MPATH");
 		assert_in_range(rc, 6, 8);
 		rc = scan_devname(buf, "MPATH");
 		assert_int_equal(rc, i);
@@ -338,7 +351,7 @@ static void sd_fd_random(void **state)
 	srandom(1);
 	for (i = 1; i < 1000; i++) {
 		n = random() & 0xffff;
-		rc = format_devname(buf, n, sizeof(buf), "MPATH");
+		rc = __format_devname(buf, n, sizeof(buf), "MPATH");
 		assert_in_range(rc, 6, 9);
 		rc = scan_devname(buf, "MPATH");
 		assert_int_equal(rc, n);
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [dm-devel] [PATCH v2 9/9] multipathd: use strbuf in cli.c
  2021-08-11 15:41 [dm-devel] [PATCH v2 0/9] multipath-tools: use variable-size string buffers mwilck
                   ` (7 preceding siblings ...)
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 8/9] libmultipath: use strbuf in alias.c mwilck
@ 2021-08-11 15:41 ` mwilck
  8 siblings, 0 replies; 17+ messages in thread
From: mwilck @ 2021-08-11 15:41 UTC (permalink / raw)
  To: Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel, Martin Wilck

From: Martin Wilck <mwilck@suse.com>

Here, too, strbuf can be used to simplify code.

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>
Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 multipathd/cli.c | 94 ++++++++++++++++++------------------------------
 1 file changed, 34 insertions(+), 60 deletions(-)

diff --git a/multipathd/cli.c b/multipathd/cli.c
index bdc9fb1..4d6c37c 100644
--- a/multipathd/cli.c
+++ b/multipathd/cli.c
@@ -16,6 +16,7 @@
 #include "mpath_cmd.h"
 #include "cli.h"
 #include "debug.h"
+#include "strbuf.h"
 
 static vector keys;
 static vector handlers;
@@ -354,107 +355,80 @@ alloc_handlers (void)
 }
 
 static int
-genhelp_sprint_aliases (char * reply, int maxlen, vector keys,
+genhelp_sprint_aliases (struct strbuf *reply, vector keys,
 			struct key * refkw)
 {
-	int i, len = 0;
+	int i;
 	struct key * kw;
+	size_t initial_len = get_strbuf_len(reply);
 
 	vector_foreach_slot (keys, kw, i) {
-		if (kw->code == refkw->code && kw != refkw) {
-			len += snprintf(reply + len, maxlen - len,
-					"|%s", kw->str);
-			if (len >= maxlen)
-				return len;
-		}
+		if (kw->code == refkw->code && kw != refkw &&
+		    print_strbuf(reply, "|%s", kw->str) < 0)
+			return -1;
 	}
 
-	return len;
+	return get_strbuf_len(reply) - initial_len;
 }
 
 static int
-do_genhelp(char *reply, int maxlen, const char *cmd, int error) {
-	int len = 0;
+do_genhelp(struct strbuf *reply, const char *cmd, int error) {
 	int i, j;
 	uint64_t fp;
 	struct handler * h;
 	struct key * kw;
+	int rc = 0;
+	size_t initial_len = get_strbuf_len(reply);
 
 	switch(error) {
 	case ENOMEM:
-		len += snprintf(reply + len, maxlen - len,
-				"%s: Not enough memory\n", cmd);
+		rc = print_strbuf(reply, "%s: Not enough memory\n", cmd);
 		break;
 	case EAGAIN:
-		len += snprintf(reply + len, maxlen - len,
-				"%s: not found\n", cmd);
+		rc = print_strbuf(reply, "%s: not found\n", cmd);
 		break;
 	case EINVAL:
-		len += snprintf(reply + len, maxlen - len,
-				"%s: Missing argument\n", cmd);
+		rc = print_strbuf(reply, "%s: Missing argument\n", cmd);
 		break;
 	}
-	if (len >= maxlen)
-		goto out;
-	len += snprintf(reply + len, maxlen - len, VERSION_STRING);
-	if (len >= maxlen)
-		goto out;
-	len += snprintf(reply + len, maxlen - len, "CLI commands reference:\n");
-	if (len >= maxlen)
-		goto out;
+	if (rc < 0)
+		return -1;
+
+	if (print_strbuf(reply, VERSION_STRING) < 0 ||
+	    append_strbuf_str(reply, "CLI commands reference:\n") < 0)
+		return -1;
 
 	vector_foreach_slot (handlers, h, i) {
 		fp = h->fingerprint;
 		vector_foreach_slot (keys, kw, j) {
 			if ((kw->code & fp)) {
 				fp -= kw->code;
-				len += snprintf(reply + len , maxlen - len,
-						" %s", kw->str);
-				if (len >= maxlen)
-					goto out;
-				len += genhelp_sprint_aliases(reply + len,
-							      maxlen - len,
-							      keys, kw);
-				if (len >= maxlen)
-					goto out;
+				if (print_strbuf(reply, " %s", kw->str) < 0 ||
+				    genhelp_sprint_aliases(reply, keys, kw) < 0)
+					return -1;
 
 				if (kw->has_param) {
-					len += snprintf(reply + len,
-							maxlen - len,
-							" $%s", kw->str);
-					if (len >= maxlen)
-						goto out;
+					if (print_strbuf(reply, " $%s",
+							 kw->str) < 0)
+						return -1;
 				}
 			}
 		}
-		len += snprintf(reply + len, maxlen - len, "\n");
-		if (len >= maxlen)
-			goto out;
+		if (append_strbuf_str(reply, "\n") < 0)
+			return -1;
 	}
-out:
-	return len;
+	return get_strbuf_len(reply) - initial_len;
 }
 
 
 static char *
 genhelp_handler (const char *cmd, int error)
 {
-	char * reply;
-	char * p = NULL;
-	int maxlen = INITIAL_REPLY_LEN;
-	int again = 1;
-
-	reply = MALLOC(maxlen);
-
-	while (again) {
-		if (!reply)
-			return NULL;
-		p = reply;
-		p += do_genhelp(reply, maxlen, cmd, error);
-		again = ((p - reply) >= maxlen);
-		REALLOC_REPLY(reply, again, maxlen);
-	}
-	return reply;
+	STRBUF_ON_STACK(reply);
+
+	if (do_genhelp(&reply, cmd, error) == -1)
+		condlog(0, "genhelp_handler: out of memory");
+	return steal_strbuf_str(&reply);
 }
 
 int
-- 
2.32.0


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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
@ 2021-08-11 16:08   ` Bart Van Assche
  2021-08-12  7:19     ` Martin Wilck
  2021-08-30 20:45   ` Benjamin Marzinski
  1 sibling, 1 reply; 17+ messages in thread
From: Bart Van Assche @ 2021-08-11 16:08 UTC (permalink / raw)
  To: mwilck, Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel

On 8/11/21 8:41 AM, mwilck@suse.com wrote:
> 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.

Has it been considered to switch to C++ and use std::string and/or 
std::ostringstream instead of implementing a string library in C?

Thanks,

Bart.



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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers
  2021-08-11 16:08   ` Bart Van Assche
@ 2021-08-12  7:19     ` Martin Wilck
  0 siblings, 0 replies; 17+ messages in thread
From: Martin Wilck @ 2021-08-12  7:19 UTC (permalink / raw)
  To: Bart Van Assche, Benjamin Marzinski, Christophe Varoqui; +Cc: dm-devel

Hello Bart,

On Mi, 2021-08-11 at 09:08 -0700, Bart Van Assche wrote:
> On 8/11/21 8:41 AM, mwilck@suse.com wrote:
> > 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.
> 
> Has it been considered to switch to C++ and use std::string and/or 
> std::ostringstream instead of implementing a string library in C?
> 

no, not at this time. It's an interesting long-term perspective, but
for the time being, I think we can quite well stick with C.

Regards,
Martin



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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 1/9] libmultipath: variable-size parameters in dm_get_map()
  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
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Marzinski @ 2021-08-30 20:44 UTC (permalink / raw)
  To: mwilck; +Cc: dm-devel

On Wed, Aug 11, 2021 at 05:41:42PM +0200, mwilck@suse.com wrote:
> From: Martin Wilck <mwilck@suse.com>
> 
> We've seen a crash of multipath in disassemble_map because of a params
> string exceeding PARAMS_SIZE. While the crash could have been fixed by
> a simple error check, I believe multipath should be able to work with
> arbitrary long parameter strings passed from the kernel.
> 
> The parameter list of dm_get_map() has changed. Bumped ABI version and
> removed dm_get_map() and some functions from the ABI, which are only
> called from libmultipath itself.
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>

> ---
>  libmultipath/devmapper.c          | 44 ++++++++++++++++++++-----------
>  libmultipath/devmapper.h          |  4 +--
>  libmultipath/libmultipath.version |  6 +----
>  libmultipath/structs_vec.c        | 11 +++++---
>  4 files changed, 38 insertions(+), 27 deletions(-)
> 
> diff --git a/libmultipath/devmapper.c b/libmultipath/devmapper.c
> index 945e625..c05dc20 100644
> --- a/libmultipath/devmapper.c
> +++ b/libmultipath/devmapper.c
> @@ -648,7 +648,7 @@ int dm_map_present(const char * str)
>  	return (do_get_info(str, &info) == 0);
>  }
>  
> -int dm_get_map(const char *name, unsigned long long *size, char *outparams)
> +int dm_get_map(const char *name, unsigned long long *size, char **outparams)
>  {
>  	int r = DMP_ERR;
>  	struct dm_task *dmt;
> @@ -682,12 +682,13 @@ int dm_get_map(const char *name, unsigned long long *size, char *outparams)
>  	if (size)
>  		*size = length;
>  
> -	if (!outparams) {
> +	if (!outparams)
>  		r = DMP_OK;
> -		goto out;
> +	else {
> +		*outparams = strdup(params);
> +		r = *outparams ? DMP_OK : DMP_ERR;
>  	}
> -	if (snprintf(outparams, PARAMS_SIZE, "%s", params) <= PARAMS_SIZE)
> -		r = DMP_OK;
> +
>  out:
>  	dm_task_destroy(dmt);
>  	return r;
> @@ -761,7 +762,7 @@ is_mpath_part(const char *part_name, const char *map_name)
>  	return 0;
>  }
>  
> -int dm_get_status(const char *name, char *outstatus)
> +int dm_get_status(const char *name, char **outstatus)
>  {
>  	int r = DMP_ERR;
>  	struct dm_task *dmt;
> @@ -799,8 +800,12 @@ int dm_get_status(const char *name, char *outstatus)
>  		goto out;
>  	}
>  
> -	if (snprintf(outstatus, PARAMS_SIZE, "%s", status) <= PARAMS_SIZE)
> +	if (!outstatus)
>  		r = DMP_OK;
> +	else {
> +		*outstatus = strdup(status);
> +		r = *outstatus ? DMP_OK : DMP_ERR;
> +	}
>  out:
>  	if (r != DMP_OK)
>  		condlog(0, "%s: error getting map status string", name);
> @@ -1049,7 +1054,7 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
>  	int queue_if_no_path = 0;
>  	int udev_flags = 0;
>  	unsigned long long mapsize;
> -	char params[PARAMS_SIZE] = {0};
> +	char *params = NULL;
>  
>  	if (dm_is_mpath(mapname) != 1)
>  		return 0; /* nothing to do */
> @@ -1065,7 +1070,7 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
>  			return 1;
>  
>  	if (need_suspend &&
> -	    dm_get_map(mapname, &mapsize, params) == DMP_OK &&
> +	    dm_get_map(mapname, &mapsize, &params) == DMP_OK &&
>  	    strstr(params, "queue_if_no_path")) {
>  		if (!dm_queue_if_no_path(mapname, 0))
>  			queue_if_no_path = 1;
> @@ -1073,6 +1078,8 @@ int _dm_flush_map (const char * mapname, int need_sync, int deferred_remove,
>  			/* Leave queue_if_no_path alone if unset failed */
>  			queue_if_no_path = -1;
>  	}
> +	free(params);
> +	params = NULL;
>  
>  	if (dm_remove_partmaps(mapname, need_sync, deferred_remove))
>  		return 1;
> @@ -1431,7 +1438,7 @@ do_foreach_partmaps (const char * mapname,
>  	struct dm_task *dmt;
>  	struct dm_names *names;
>  	unsigned next = 0;
> -	char params[PARAMS_SIZE];
> +	char *params = NULL;
>  	unsigned long long size;
>  	char dev_t[32];
>  	int r = 1;
> @@ -1474,7 +1481,7 @@ do_foreach_partmaps (const char * mapname,
>  		    /*
>  		     * and we can fetch the map table from the kernel
>  		     */
> -		    dm_get_map(names->name, &size, &params[0]) == DMP_OK &&
> +		    dm_get_map(names->name, &size, &params) == DMP_OK &&
>  
>  		    /*
>  		     * and the table maps over the multipath map
> @@ -1486,12 +1493,15 @@ do_foreach_partmaps (const char * mapname,
>  				goto out;
>  		}
>  
> +		free(params);
> +		params = NULL;
>  		next = names->next;
>  		names = (void *) names + next;
>  	} while (next);
>  
>  	r = 0;
>  out:
> +	free(params);
>  	dm_task_destroy (dmt);
>  	return r;
>  }
> @@ -1620,17 +1630,19 @@ struct rename_data {
>  static int
>  rename_partmap (const char *name, void *data)
>  {
> -	char buff[PARAMS_SIZE];
> +	char *buff = NULL;
>  	int offset;
>  	struct rename_data *rd = (struct rename_data *)data;
>  
>  	if (strncmp(name, rd->old, strlen(rd->old)) != 0)
>  		return 0;
>  	for (offset = strlen(rd->old); name[offset] && !(isdigit(name[offset])); offset++); /* do nothing */
> -	snprintf(buff, PARAMS_SIZE, "%s%s%s", rd->new, rd->delim,
> -		 name + offset);
> -	dm_rename(name, buff, rd->delim, SKIP_KPARTX_OFF);
> -	condlog(4, "partition map %s renamed", name);
> +	if (asprintf(&buff, "%s%s%s", rd->new, rd->delim, name + offset) >= 0) {
> +		dm_rename(name, buff, rd->delim, SKIP_KPARTX_OFF);
> +		free(buff);
> +		condlog(4, "partition map %s renamed", name);
> +	} else
> +		condlog(1, "failed to rename partition map %s", name);
>  	return 0;
>  }
>  
> diff --git a/libmultipath/devmapper.h b/libmultipath/devmapper.h
> index e29b4d4..45a676d 100644
> --- a/libmultipath/devmapper.h
> +++ b/libmultipath/devmapper.h
> @@ -44,8 +44,8 @@ int dm_addmap_create (struct multipath *mpp, char *params);
>  int dm_addmap_reload (struct multipath *mpp, char *params, int flush);
>  int dm_map_present (const char *);
>  int dm_map_present_by_uuid(const char *uuid);
> -int dm_get_map(const char *, unsigned long long *, char *);
> -int dm_get_status(const char *, char *);
> +int dm_get_map(const char *, unsigned long long *, char **);
> +int dm_get_status(const char *, char **);
>  int dm_type(const char *, char *);
>  int dm_is_mpath(const char *);
>  int _dm_flush_map (const char *, int, int, int, int);
> diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
> index 0cff311..7567837 100644
> --- a/libmultipath/libmultipath.version
> +++ b/libmultipath/libmultipath.version
> @@ -31,7 +31,7 @@
>   *   The new version inherits the previous ones.
>   */
>  
> -LIBMULTIPATH_5.0.0 {
> +LIBMULTIPATH_6.0.0 {
>  global:
>  	/* symbols referenced by multipath and multipathd */
>  	add_foreign;
> @@ -58,8 +58,6 @@ global:
>  	count_active_paths;
>  	delete_all_foreign;
>  	delete_foreign;
> -	disassemble_map;
> -	disassemble_status;
>  	dlog;
>  	dm_cancel_deferred_remove;
>  	dm_enablegroup;
> @@ -70,10 +68,8 @@ global:
>  	dm_geteventnr;
>  	dm_get_info;
>  	dm_get_major_minor;
> -	dm_get_map;
>  	dm_get_maps;
>  	dm_get_multipath;
> -	dm_get_status;
>  	dm_get_uuid;
>  	dm_is_mpath;
>  	dm_mapname;
> diff --git a/libmultipath/structs_vec.c b/libmultipath/structs_vec.c
> index 7539019..24d6fd2 100644
> --- a/libmultipath/structs_vec.c
> +++ b/libmultipath/structs_vec.c
> @@ -416,12 +416,12 @@ int
>  update_multipath_table (struct multipath *mpp, vector pathvec, int flags)
>  {
>  	int r = DMP_ERR;
> -	char params[PARAMS_SIZE] = {0};
> +	char *params = NULL;
>  
>  	if (!mpp)
>  		return r;
>  
> -	r = dm_get_map(mpp->alias, &mpp->size, params);
> +	r = dm_get_map(mpp->alias, &mpp->size, &params);
>  	if (r != DMP_OK) {
>  		condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting table" : "map not present");
>  		return r;
> @@ -429,14 +429,17 @@ update_multipath_table (struct multipath *mpp, vector pathvec, int flags)
>  
>  	if (disassemble_map(pathvec, params, mpp)) {
>  		condlog(2, "%s: cannot disassemble map", mpp->alias);
> +		free(params);
>  		return DMP_ERR;
>  	}
>  
> -	*params = '\0';
> -	if (dm_get_status(mpp->alias, params) != DMP_OK)
> +	free(params);
> +	params = NULL;
> +	if (dm_get_status(mpp->alias, &params) != DMP_OK)
>  		condlog(2, "%s: %s", mpp->alias, (r == DMP_ERR)? "error getting status" : "map not present");
>  	else if (disassemble_status(params, mpp))
>  		condlog(2, "%s: cannot disassemble status", mpp->alias);
> +	free(params);
>  
>  	/* FIXME: we should deal with the return value here */
>  	update_pathvec_from_dm(pathvec, mpp, flags);
> -- 
> 2.32.0

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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers
  2021-08-11 15:41 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
  2021-08-11 16:08   ` Bart Van Assche
@ 2021-08-30 20:45   ` Benjamin Marzinski
  1 sibling, 0 replies; 17+ messages in thread
From: Benjamin Marzinski @ 2021-08-30 20:45 UTC (permalink / raw)
  To: mwilck; +Cc: dm-devel

On Wed, Aug 11, 2021 at 05:41:43PM +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.
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 3/9] libmultipath: variable-size parameters in assemble_map()
  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
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Marzinski @ 2021-08-30 20:45 UTC (permalink / raw)
  To: mwilck; +Cc: dm-devel

On Wed, Aug 11, 2021 at 05:41:44PM +0200, mwilck@suse.com wrote:
> From: Martin Wilck <mwilck@suse.com>
> 
> Instead of using fixed PARAMS_SIZE-sized arrays for parameters, use
> dynamically allocated memory.
> 
> The library version needs to be bumped, because setup_map() argument
> list has changed.
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>

> ---
>  libmultipath/configure.c          | 18 ++++++------
>  libmultipath/configure.h          |  3 +-
>  libmultipath/dmparser.c           | 47 ++++++++++---------------------
>  libmultipath/dmparser.h           |  2 +-
>  libmultipath/libmultipath.version |  5 +++-
>  libmultipath/structs.h            |  1 -
>  libmultipath/util.c               |  5 ++++
>  libmultipath/util.h               |  1 +
>  multipathd/cli_handlers.c         |  4 +--
>  multipathd/main.c                 | 20 +++++++------
>  10 files changed, 50 insertions(+), 56 deletions(-)
> 
> diff --git a/libmultipath/configure.c b/libmultipath/configure.c
> index a6ae335..1227864 100644
> --- a/libmultipath/configure.c
> +++ b/libmultipath/configure.c
> @@ -292,8 +292,7 @@ static int wait_for_pending_paths(struct multipath *mpp,
>  	return n_pending;
>  }
>  
> -int setup_map(struct multipath *mpp, char *params, int params_size,
> -	      struct vectors *vecs)
> +int setup_map(struct multipath *mpp, char **params, struct vectors *vecs)
>  {
>  	struct pathgroup * pgp;
>  	struct config *conf;
> @@ -462,7 +461,7 @@ int setup_map(struct multipath *mpp, char *params, int params_size,
>  	 * transform the mp->pg vector of vectors of paths
>  	 * into a mp->params strings to feed the device-mapper
>  	 */
> -	if (assemble_map(mpp, params, params_size)) {
> +	if (assemble_map(mpp, params)) {
>  		condlog(0, "%s: problem assembing map", mpp->alias);
>  		return 1;
>  	}
> @@ -811,7 +810,7 @@ void select_action (struct multipath *mpp, const struct _vector *curmp,
>  		remove_feature(&mpp_feat, "retain_attached_hw_handler");
>  		remove_feature(&cmpp_feat, "queue_if_no_path");
>  		remove_feature(&cmpp_feat, "retain_attached_hw_handler");
> -		if (strncmp(mpp_feat, cmpp_feat, PARAMS_SIZE)) {
> +		if (strcmp(mpp_feat, cmpp_feat)) {
>  			select_reload_action(mpp, "features change");
>  			FREE(cmpp_feat);
>  			FREE(mpp_feat);
> @@ -1128,14 +1127,14 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
>  	int ret = CP_FAIL;
>  	int k, i, r;
>  	int is_daemon = (cmd == CMD_NONE) ? 1 : 0;
> -	char params[PARAMS_SIZE];
> +	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
>  	struct multipath * mpp;
> -	struct path * pp1;
> +	struct path * pp1 = NULL;
>  	struct path * pp2;
>  	vector curmp = vecs->mpvec;
>  	vector pathvec = vecs->pathvec;
>  	vector newmp;
> -	struct config *conf;
> +	struct config *conf = NULL;
>  	int allow_queueing;
>  	struct bitfield *size_mismatch_seen;
>  
> @@ -1247,8 +1246,7 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
>  		}
>  		verify_paths(mpp);
>  
> -		params[0] = '\0';
> -		if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
> +		if (setup_map(mpp, &params, vecs)) {
>  			remove_map(mpp, vecs->pathvec, vecs->mpvec, KEEP_VEC);
>  			continue;
>  		}
> @@ -1260,6 +1258,8 @@ int coalesce_paths (struct vectors *vecs, vector mpvec, char *refwwid,
>  				      force_reload == FORCE_RELOAD_YES ? 1 : 0);
>  
>  		r = domap(mpp, params, is_daemon);
> +		free(params);
> +		params = NULL;
>  
>  		if (r == DOMAP_FAIL || r == DOMAP_RETRY) {
>  			condlog(3, "%s: domap (%u) failure "
> diff --git a/libmultipath/configure.h b/libmultipath/configure.h
> index 70cf77a..92a5aba 100644
> --- a/libmultipath/configure.h
> +++ b/libmultipath/configure.h
> @@ -47,8 +47,7 @@ enum {
>  
>  struct vectors;
>  
> -int setup_map (struct multipath * mpp, char * params, int params_size,
> -	       struct vectors *vecs );
> +int setup_map (struct multipath * mpp, char **params, struct vectors *vecs);
>  void select_action (struct multipath *mpp, const struct _vector *curmp,
>  		    int force_reload);
>  int domap (struct multipath * mpp, char * params, int is_daemon);
> diff --git a/libmultipath/dmparser.c b/libmultipath/dmparser.c
> index b306c46..4ba7f33 100644
> --- a/libmultipath/dmparser.c
> +++ b/libmultipath/dmparser.c
> @@ -15,6 +15,7 @@
>  #include "util.h"
>  #include "debug.h"
>  #include "dmparser.h"
> +#include "strbuf.h"
>  
>  #define WORD_SIZE 64
>  
> @@ -41,40 +42,21 @@ merge_words(char **dst, const char *word)
>  	return 0;
>  }
>  
> -#define APPEND(p, end, args...)						\
> -({									\
> -	int ret;							\
> -									\
> -	ret = snprintf(p, end - p, ##args);				\
> -	if (ret < 0) {							\
> -		condlog(0, "%s: conversion error", mp->alias);		\
> -		goto err;						\
> -	}								\
> -	p += ret;							\
> -	if (p >= end) {							\
> -		condlog(0, "%s: params too small", mp->alias);		\
> -		goto err;						\
> -	}								\
> -})
> -
>  /*
>   * Transforms the path group vector into a proper device map string
>   */
> -int
> -assemble_map (struct multipath * mp, char * params, int len)
> +int assemble_map(struct multipath *mp, char **params)
>  {
> +	static const char no_path_retry[] = "queue_if_no_path";
> +	static const char retain_hwhandler[] = "retain_attached_hw_handler";
>  	int i, j;
>  	int minio;
>  	int nr_priority_groups, initial_pg_nr;
> -	char * p;
> -	const char *const end = params + len;
> -	char no_path_retry[] = "queue_if_no_path";
> -	char retain_hwhandler[] = "retain_attached_hw_handler";
> +	STRBUF_ON_STACK(buff);
>  	struct pathgroup * pgp;
>  	struct path * pp;
>  
>  	minio = mp->minio;
> -	p = params;
>  
>  	nr_priority_groups = VECTOR_SIZE(mp->pg);
>  	initial_pg_nr = (nr_priority_groups ? mp->bestpg : 0);
> @@ -87,14 +69,15 @@ assemble_map (struct multipath * mp, char * params, int len)
>  	    get_linux_version_code() < KERNEL_VERSION(4, 3, 0))
>  		add_feature(&mp->features, retain_hwhandler);
>  
> -	/* mp->features must not be NULL */
> -	APPEND(p, end, "%s %s %i %i", mp->features, mp->hwhandler,
> -		nr_priority_groups, initial_pg_nr);
> +	if (print_strbuf(&buff, "%s %s %i %i", mp->features, mp->hwhandler,
> +			 nr_priority_groups, initial_pg_nr) < 0)
> +		goto err;
>  
>  	vector_foreach_slot (mp->pg, pgp, i) {
>  		pgp = VECTOR_SLOT(mp->pg, i);
> -		APPEND(p, end, " %s %i 1", mp->selector,
> -		       VECTOR_SIZE(pgp->paths));
> +		if (print_strbuf(&buff, " %s %i 1", mp->selector,
> +				 VECTOR_SIZE(pgp->paths)) < 0)
> +			goto err;
>  
>  		vector_foreach_slot (pgp->paths, pp, j) {
>  			int tmp_minio = minio;
> @@ -106,19 +89,19 @@ assemble_map (struct multipath * mp, char * params, int len)
>  				condlog(0, "dev_t not set for '%s'", pp->dev);
>  				goto err;
>  			}
> -			APPEND(p, end, " %s %d", pp->dev_t, tmp_minio);
> +			if (print_strbuf(&buff, " %s %d", pp->dev_t, tmp_minio) < 0)
> +				goto err;
>  		}
>  	}
>  
> -	condlog(4, "%s: assembled map [%s]", mp->alias, params);
> +	*params = steal_strbuf_str(&buff);
> +	condlog(4, "%s: assembled map [%s]", mp->alias, *params);
>  	return 0;
>  
>  err:
>  	return 1;
>  }
>  
> -#undef APPEND
> -
>  /*
>   * Caution callers: If this function encounters yet unkown path devices, it
>   * adds them uninitialized to the mpp.
> diff --git a/libmultipath/dmparser.h b/libmultipath/dmparser.h
> index 212fee5..666ae74 100644
> --- a/libmultipath/dmparser.h
> +++ b/libmultipath/dmparser.h
> @@ -1,3 +1,3 @@
> -int assemble_map (struct multipath *, char *, int);
> +int assemble_map (struct multipath *, char **);
>  int disassemble_map (const struct _vector *, const char *, struct multipath *);
>  int disassemble_status (const char *, struct multipath *);
> diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
> index 7567837..6dd52c2 100644
> --- a/libmultipath/libmultipath.version
> +++ b/libmultipath/libmultipath.version
> @@ -31,7 +31,7 @@
>   *   The new version inherits the previous ones.
>   */
>  
> -LIBMULTIPATH_6.0.0 {
> +LIBMULTIPATH_7.0.0 {
>  global:
>  	/* symbols referenced by multipath and multipathd */
>  	add_foreign;
> @@ -267,6 +267,9 @@ global:
>  	/* added in 4.5.0 */
>  	get_vpd_sgio;
>  	trigger_partitions_udev_change;
> +
> +	/* added in 7.0.0 */
> +	cleanup_charp;
>  local:
>  	*;
>  };
> diff --git a/libmultipath/structs.h b/libmultipath/structs.h
> index c52bcee..399540e 100644
> --- a/libmultipath/structs.h
> +++ b/libmultipath/structs.h
> @@ -13,7 +13,6 @@
>  #define SERIAL_SIZE		128
>  #define NODE_NAME_SIZE		224
>  #define PATH_STR_SIZE		16
> -#define PARAMS_SIZE		4096
>  #define FILE_NAME_SIZE		256
>  #define CALLOUT_MAX_SIZE	256
>  #define BLK_DEV_SIZE		33
> diff --git a/libmultipath/util.c b/libmultipath/util.c
> index 0e37f3f..e2fafe8 100644
> --- a/libmultipath/util.c
> +++ b/libmultipath/util.c
> @@ -455,3 +455,8 @@ int should_exit(void)
>  {
>  	return 0;
>  }
> +
> +void cleanup_charp(char **p)
> +{
> +	free(*p);
> +}
> diff --git a/libmultipath/util.h b/libmultipath/util.h
> index e9b48f9..89027f8 100644
> --- a/libmultipath/util.h
> +++ b/libmultipath/util.h
> @@ -123,4 +123,5 @@ static inline void clear_bit_in_bitfield(unsigned int bit, struct bitfield *bf)
>  		___p;		       \
>  	})
>  
> +void cleanup_charp(char **p);
>  #endif /* _UTIL_H */
> diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c
> index d70e1db..bce40b1 100644
> --- a/multipathd/cli_handlers.c
> +++ b/multipathd/cli_handlers.c
> @@ -972,12 +972,12 @@ cli_reload(void *v, char **reply, int *len, void *data)
>  int resize_map(struct multipath *mpp, unsigned long long size,
>  	       struct vectors * vecs)
>  {
> -	char params[PARAMS_SIZE] = {0};
> +	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
>  	unsigned long long orig_size = mpp->size;
>  
>  	mpp->size = size;
>  	update_mpp_paths(mpp, vecs->pathvec);
> -	if (setup_map(mpp, params, PARAMS_SIZE, vecs) != 0) {
> +	if (setup_map(mpp, &params, vecs) != 0) {
>  		condlog(0, "%s: failed to setup map for resize : %s",
>  			mpp->alias, strerror(errno));
>  		mpp->size = orig_size;
> diff --git a/multipathd/main.c b/multipathd/main.c
> index bdd629e..ae8272e 100644
> --- a/multipathd/main.c
> +++ b/multipathd/main.c
> @@ -489,7 +489,7 @@ static int
>  update_map (struct multipath *mpp, struct vectors *vecs, int new_map)
>  {
>  	int retries = 3;
> -	char params[PARAMS_SIZE] = {0};
> +	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
>  
>  retry:
>  	condlog(4, "%s: updating new map", mpp->alias);
> @@ -502,13 +502,15 @@ retry:
>  	verify_paths(mpp);
>  	mpp->action = ACT_RELOAD;
>  
> -	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
> +	if (setup_map(mpp, &params, vecs)) {
>  		condlog(0, "%s: failed to setup new map in update", mpp->alias);
>  		retries = -1;
>  		goto fail;
>  	}
>  	if (domap(mpp, params, 1) == DOMAP_FAIL && retries-- > 0) {
>  		condlog(0, "%s: map_udate sleep", mpp->alias);
> +		free(params);
> +		params = NULL;
>  		sleep(1);
>  		goto retry;
>  	}
> @@ -1028,7 +1030,7 @@ int
>  ev_add_path (struct path * pp, struct vectors * vecs, int need_do_map)
>  {
>  	struct multipath * mpp;
> -	char params[PARAMS_SIZE] = {0};
> +	char *params __attribute((cleanup(cleanup_charp))) = NULL;
>  	int retries = 3;
>  	int start_waiter = 0;
>  	int ret;
> @@ -1104,7 +1106,7 @@ rescan:
>  	/*
>  	 * push the map to the device-mapper
>  	 */
> -	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
> +	if (setup_map(mpp, &params, vecs)) {
>  		condlog(0, "%s: failed to setup map for addition of new "
>  			"path %s", mpp->alias, pp->dev);
>  		goto fail_map;
> @@ -1129,6 +1131,8 @@ rescan:
>  			condlog(0, "%s: ev_add_path sleep", mpp->alias);
>  			sleep(1);
>  			update_mpp_paths(mpp, vecs->pathvec);
> +			free(params);
> +			params = NULL;
>  			goto rescan;
>  		}
>  		else if (mpp->action == ACT_RELOAD)
> @@ -1189,7 +1193,7 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map)
>  {
>  	struct multipath * mpp;
>  	int i, retval = REMOVE_PATH_SUCCESS;
> -	char params[PARAMS_SIZE] = {0};
> +	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
>  
>  	/*
>  	 * avoid referring to the map of an orphaned path
> @@ -1250,7 +1254,7 @@ ev_remove_path (struct path *pp, struct vectors * vecs, int need_do_map)
>  			 */
>  		}
>  
> -		if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
> +		if (setup_map(mpp, &params, vecs)) {
>  			condlog(0, "%s: failed to setup map for"
>  				" removal of path %s", mpp->alias, pp->dev);
>  			goto fail;
> @@ -1940,7 +1944,7 @@ int update_prio(struct path *pp, int refresh_all)
>  static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh,
>  		      int is_daemon)
>  {
> -	char params[PARAMS_SIZE] = {0};
> +	char *params __attribute__((cleanup(cleanup_charp))) = NULL;
>  	struct path *pp;
>  	int i, r;
>  
> @@ -1958,7 +1962,7 @@ static int reload_map(struct vectors *vecs, struct multipath *mpp, int refresh,
>  			}
>  		}
>  	}
> -	if (setup_map(mpp, params, PARAMS_SIZE, vecs)) {
> +	if (setup_map(mpp, &params, vecs)) {
>  		condlog(0, "%s: failed to setup map", mpp->alias);
>  		return 1;
>  	}
> -- 
> 2.32.0

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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 4/9] libmultipath: use strbuf in dict.c
  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
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Marzinski @ 2021-08-30 20:46 UTC (permalink / raw)
  To: mwilck; +Cc: dm-devel

On Wed, Aug 11, 2021 at 05:41:45PM +0200, mwilck@suse.com wrote:
> From: Martin Wilck <mwilck@suse.com>
> 
> Temporary solution for snprint_keyword(), as print.c hasn't been migrated yet.
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>

> ---
>  libmultipath/dict.c    | 314 ++++++++++++++++++-----------------------
>  libmultipath/dict.h    |  19 +--
>  libmultipath/parser.c  |  47 +++---
>  libmultipath/parser.h  |  15 +-
>  libmultipath/propsel.c | 147 +++++++++----------
>  5 files changed, 254 insertions(+), 288 deletions(-)
> 
> diff --git a/libmultipath/dict.c b/libmultipath/dict.c
> index dd08abf..7a72738 100644
> --- a/libmultipath/dict.c
> +++ b/libmultipath/dict.c
> @@ -26,6 +26,7 @@
>  #include <mpath_persist.h>
>  #include "mpath_cmd.h"
>  #include "dict.h"
> +#include "strbuf.h"
>  
>  static int
>  set_int(vector strvec, void *ptr)
> @@ -143,84 +144,45 @@ set_yes_no_undef(vector strvec, void *ptr)
>  	return 0;
>  }
>  
> -static int
> -print_int (char *buff, int len, long v)
> +static int print_int(struct strbuf *buff, long v)
>  {
> -	return snprintf(buff, len, "%li", v);
> +	return print_strbuf(buff, "%li", v);
>  }
>  
> -static int
> -print_nonzero (char *buff, int len, long v)
> +static int print_nonzero(struct strbuf *buff, long v)
>  {
>  	if (!v)
>  		return 0;
> -	return snprintf(buff, len, "%li", v);
> +	return print_strbuf(buff, "%li", v);
>  }
>  
> -static int
> -print_str (char *buff, int len, const char *ptr)
> +static int print_str(struct strbuf *buff, const char *ptr)
>  {
> -	char *p;
> -	char *last;
> -	const char *q;
> +	int ret = append_strbuf_quoted(buff, ptr);
>  
> -	if (!ptr || len <= 0)
> -		return 0;
> -
> -	q = strchr(ptr, '"');
> -	if (q == NULL)
> -		return snprintf(buff, len, "\"%s\"", ptr);
> -
> -	last = buff + len - 1;
> -	p = buff;
> -	if (p >= last)
> -		goto out;
> -	*p++ = '"';
> -	if (p >= last)
> -		goto out;
> -	for (; q; q = strchr(ptr, '"')) {
> -		if (q + 1 - ptr < last - p)
> -			p = mempcpy(p, ptr, q + 1 - ptr);
> -		else {
> -			p = mempcpy(p, ptr, last - p);
> -			goto out;
> -		}
> -		*p++ = '"';
> -		if (p >= last)
> -			goto out;
> -		ptr = q + 1;
> -	}
> -	p += strlcpy(p, ptr, last - p);
> -	if (p >= last)
> -		goto out;
> -	*p++ = '"';
> -	*p = '\0';
> -	return p - buff;
> -out:
> -	*p = '\0';
> -	return len;
> +	/*
> +	 * -EINVAL aka (ptr == NULL) means "not set".
> +	 * Returning an error here breaks unit tests
> +	 * (logic in snprint_keyword()).
> +	 */
> +	return ret == -EINVAL ? 0 : ret;
>  }
>  
> -static int
> -print_ignored (char *buff, int len)
> +static int print_ignored(struct strbuf *buff)
>  {
> -	return snprintf(buff, len, "ignored");
> +	return append_strbuf_quoted(buff, "ignored");
>  }
>  
> -static int
> -print_yes_no (char *buff, int len, long v)
> +static int print_yes_no(struct strbuf *buff, long v)
>  {
> -	return snprintf(buff, len, "\"%s\"",
> -			(v == YN_NO)? "no" : "yes");
> +	return append_strbuf_quoted(buff, v == YN_NO ? "no" : "yes");
>  }
>  
> -static int
> -print_yes_no_undef (char *buff, int len, long v)
> +static int print_yes_no_undef(struct strbuf *buff, long v)
>  {
>  	if (!v)
>  		return 0;
> -	return snprintf(buff, len, "\"%s\"",
> -			(v == YNU_NO)? "no" : "yes");
> +	return append_strbuf_quoted(buff, v == YNU_NO? "no" : "yes");
>  }
>  
>  #define declare_def_handler(option, function)				\
> @@ -232,32 +194,32 @@ def_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_def_snprint(option, function)				\
>  static int								\
> -snprint_def_ ## option (struct config *conf, char * buff, int len,	\
> -			const void * data)				\
> +snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
> +			const void *data)				\
>  {									\
> -	return function (buff, len, conf->option);			\
> +	return function(buff, conf->option);				\
>  }
>  
>  #define declare_def_snprint_defint(option, function, value)		\
>  static int								\
> -snprint_def_ ## option (struct config *conf, char * buff, int len,	\
> -			const void * data)				\
> +snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
> +			const void *data)				\
>  {									\
>  	int i = value;							\
>  	if (!conf->option)						\
> -		return function (buff, len, i);				\
> -	return function (buff, len, conf->option);			\
> +		return function(buff, i);				\
> +	return function (buff, conf->option);				\
>  }
>  
>  #define declare_def_snprint_defstr(option, function, value)		\
>  static int								\
> -snprint_def_ ## option (struct config *conf, char * buff, int len,	\
> -			const void * data)				\
> +snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
> +			const void *data)				\
>  {									\
>  	static const char *s = value;					\
>  	if (!conf->option)						\
> -		return function (buff, len, s);				\
> -	return function (buff, len, conf->option);			\
> +		return function(buff, s);				\
> +	return function(buff, conf->option);				\
>  }
>  
>  #define declare_hw_handler(option, function)				\
> @@ -272,11 +234,11 @@ hw_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_hw_snprint(option, function)				\
>  static int								\
> -snprint_hw_ ## option (struct config *conf, char * buff, int len,	\
> -		       const void * data)				\
> +snprint_hw_ ## option (struct config *conf, struct strbuf *buff,	\
> +		       const void *data)				\
>  {									\
>  	const struct hwentry * hwe = (const struct hwentry *)data;	\
> -	return function (buff, len, hwe->option);			\
> +	return function(buff, hwe->option);				\
>  }
>  
>  #define declare_ovr_handler(option, function)				\
> @@ -290,10 +252,10 @@ ovr_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_ovr_snprint(option, function)				\
>  static int								\
> -snprint_ovr_ ## option (struct config *conf, char * buff, int len,	\
> -			const void * data)				\
> +snprint_ovr_ ## option (struct config *conf, struct strbuf *buff,	\
> +			const void *data)				\
>  {									\
> -	return function (buff, len, conf->overrides->option);		\
> +	return function (buff, conf->overrides->option);		\
>  }
>  
>  #define declare_mp_handler(option, function)				\
> @@ -308,11 +270,11 @@ mp_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_mp_snprint(option, function)				\
>  static int								\
> -snprint_mp_ ## option (struct config *conf, char * buff, int len,	\
> -		       const void * data)				\
> +snprint_mp_ ## option (struct config *conf, struct strbuf *buff,	\
> +		       const void *data)				\
>  {									\
>  	const struct mpentry * mpe = (const struct mpentry *)data;	\
> -	return function (buff, len, mpe->option);			\
> +	return function(buff, mpe->option);				\
>  }
>  
>  static int checkint_handler(struct config *conf, vector strvec)
> @@ -354,13 +316,13 @@ static int def_partition_delim_handler(struct config *conf, vector strvec)
>  	return 0;
>  }
>  
> -static int snprint_def_partition_delim(struct config *conf, char *buff,
> -				       int len, const void *data)
> +static int snprint_def_partition_delim(struct config *conf, struct strbuf *buff,
> +				       const void *data)
>  {
>  	if (default_partition_delim == NULL || conf->partition_delim != NULL)
> -		return print_str(buff, len, conf->partition_delim);
> +		return print_str(buff, conf->partition_delim);
>  	else
> -		return print_str(buff, len, UNSET_PARTITION_DELIM);
> +		return print_str(buff, UNSET_PARTITION_DELIM);
>  }
>  
>  static const char * const find_multipaths_optvals[] = {
> @@ -403,10 +365,10 @@ def_find_multipaths_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_def_find_multipaths(struct config *conf, char *buff, int len,
> +snprint_def_find_multipaths(struct config *conf, struct strbuf *buff,
>  			    const void *data)
>  {
> -	return print_str(buff, len,
> +	return append_strbuf_quoted(buff,
>  			 find_multipaths_optvals[conf->find_multipaths]);
>  }
>  
> @@ -419,21 +381,19 @@ declare_ovr_snprint(selector, print_str)
>  declare_mp_handler(selector, set_str)
>  declare_mp_snprint(selector, print_str)
>  
> -static int snprint_uid_attrs(struct config *conf, char *buff, int len,
> +static int snprint_uid_attrs(struct config *conf, struct strbuf *buff,
>  			     const void *dummy)
>  {
> -	char *p = buff;
> -	int n, j;
> +	int j, ret, total = 0;
>  	const char *att;
>  
>  	vector_foreach_slot(&conf->uid_attrs, att, j) {
> -		n = snprintf(p, len, "%s%s", j == 0 ? "" : " ", att);
> -		if (n >= len)
> -			return (p - buff) + n;
> -		p += n;
> -		len -= n;
> +		ret = print_strbuf(buff, "%s%s", j == 0 ? "" : " ", att);
> +		if (ret < 0)
> +			return ret;
> +		total += ret;
>  	}
> -	return p - buff;
> +	return total;
>  }
>  
>  static int uid_attrs_handler(struct config *conf, vector strvec)
> @@ -526,18 +486,23 @@ declare_mp_snprint(minio_rq, print_nonzero)
>  
>  declare_def_handler(queue_without_daemon, set_yes_no)
>  static int
> -snprint_def_queue_without_daemon (struct config *conf,
> -				  char * buff, int len, const void * data)
> +snprint_def_queue_without_daemon(struct config *conf, struct strbuf *buff,
> +				 const void * data)
>  {
> +	const char *qwd = "unknown";
> +
>  	switch (conf->queue_without_daemon) {
>  	case QUE_NO_DAEMON_OFF:
> -		return snprintf(buff, len, "\"no\"");
> +		qwd = "no";
> +		break;
>  	case QUE_NO_DAEMON_ON:
> -		return snprintf(buff, len, "\"yes\"");
> +		qwd = "yes";
> +		break;
>  	case QUE_NO_DAEMON_FORCE:
> -		return snprintf(buff, len, "\"forced\"");
> +		qwd = "forced";
> +		break;
>  	}
> -	return 0;
> +	return append_strbuf_quoted(buff, qwd);
>  }
>  
>  declare_def_handler(checker_timeout, set_int)
> @@ -636,10 +601,11 @@ static int def_disable_changed_wwids_handler(struct config *conf, vector strvec)
>  {
>  	return 0;
>  }
> -static int snprint_def_disable_changed_wwids(struct config *conf, char *buff,
> -					     int len, const void *data)
> +static int snprint_def_disable_changed_wwids(struct config *conf,
> +					     struct strbuf *buff,
> +					     const void *data)
>  {
> -	return print_ignored(buff, len);
> +	return print_ignored(buff);
>  }
>  
>  declare_def_handler(remove_retries, set_int)
> @@ -681,11 +647,10 @@ def_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_def_attr_snprint(option, function)			\
>  static int								\
> -snprint_def_ ## option (struct config *conf, char * buff, int len,	\
> -			const void * data)				\
> +snprint_def_ ## option (struct config *conf, struct strbuf *buff,	\
> +			const void *data)				\
>  {									\
> -	return function (buff, len, conf->option,			\
> -			 conf->attribute_flags);			\
> +	return function(buff, conf->option, conf->attribute_flags);	\
>  }
>  
>  #define declare_mp_attr_handler(option, function)			\
> @@ -700,12 +665,11 @@ mp_ ## option ## _handler (struct config *conf, vector strvec)		\
>  
>  #define declare_mp_attr_snprint(option, function)			\
>  static int								\
> -snprint_mp_ ## option (struct config *conf, char * buff, int len,	\
> +snprint_mp_ ## option (struct config *conf, struct strbuf *buff,	\
>  		       const void * data)				\
>  {									\
>  	const struct mpentry * mpe = (const struct mpentry *)data;	\
> -	return function (buff, len, mpe->option,			\
> -			 mpe->attribute_flags);				\
> +	return function(buff, mpe->option, mpe->attribute_flags);	\
>  }
>  
>  static int
> @@ -780,30 +744,30 @@ set_gid(vector strvec, void *ptr, int *flags)
>  }
>  
>  static int
> -print_mode(char * buff, int len, long v, int flags)
> +print_mode(struct strbuf *buff, long v, int flags)
>  {
>  	mode_t mode = (mode_t)v;
>  	if ((flags & (1 << ATTR_MODE)) == 0)
>  		return 0;
> -	return snprintf(buff, len, "0%o", mode);
> +	return print_strbuf(buff, "0%o", mode);
>  }
>  
>  static int
> -print_uid(char * buff, int len, long v, int flags)
> +print_uid(struct strbuf *buff, long v, int flags)
>  {
>  	uid_t uid = (uid_t)v;
>  	if ((flags & (1 << ATTR_UID)) == 0)
>  		return 0;
> -	return snprintf(buff, len, "0%o", uid);
> +	return print_strbuf(buff, "0%o", uid);
>  }
>  
>  static int
> -print_gid(char * buff, int len, long v, int flags)
> +print_gid(struct strbuf *buff, long v, int flags)
>  {
>  	gid_t gid = (gid_t)v;
>  	if ((flags & (1 << ATTR_GID)) == 0)
>  		return 0;
> -	return snprintf(buff, len, "0%o", gid);
> +	return print_strbuf(buff, "0%o", gid);
>  }
>  
>  declare_def_attr_handler(mode, set_mode)
> @@ -843,16 +807,15 @@ set_undef_off_zero(vector strvec, void *ptr)
>  	return 0;
>  }
>  
> -int
> -print_undef_off_zero(char * buff, int len, long v)
> +int print_undef_off_zero(struct strbuf *buff, long v)
>  {
>  	if (v == UOZ_UNDEF)
>  		return 0;
>  	if (v == UOZ_OFF)
> -		return snprintf(buff, len, "\"off\"");
> +		return append_strbuf_str(buff, "off");
>  	if (v == UOZ_ZERO)
> -		return snprintf(buff, len, "0");
> -	return snprintf(buff, len, "%ld", v);
> +		return append_strbuf_str(buff, "0");
> +	return print_int(buff, v);
>  }
>  
>  declare_def_handler(fast_io_fail, set_undef_off_zero)
> @@ -883,13 +846,13 @@ set_dev_loss(vector strvec, void *ptr)
>  }
>  
>  int
> -print_dev_loss(char * buff, int len, unsigned long v)
> +print_dev_loss(struct strbuf *buff, unsigned long v)
>  {
>  	if (v == DEV_LOSS_TMO_UNSET)
>  		return 0;
>  	if (v >= MAX_DEV_LOSS_TMO)
> -		return snprintf(buff, len, "\"infinity\"");
> -	return snprintf(buff, len, "%lu", v);
> +		return append_strbuf_quoted(buff, "infinity");
> +	return print_strbuf(buff, "%lu", v);
>  }
>  
>  declare_def_handler(dev_loss, set_dev_loss)
> @@ -923,7 +886,7 @@ set_pgpolicy(vector strvec, void *ptr)
>  }
>  
>  int
> -print_pgpolicy(char * buff, int len, long pgpolicy)
> +print_pgpolicy(struct strbuf *buff, long pgpolicy)
>  {
>  	char str[POLICY_NAME_SIZE];
>  
> @@ -932,7 +895,7 @@ print_pgpolicy(char * buff, int len, long pgpolicy)
>  
>  	get_pgpolicy_name(str, POLICY_NAME_SIZE, pgpolicy);
>  
> -	return snprintf(buff, len, "\"%s\"", str);
> +	return append_strbuf_quoted(buff, str);
>  }
>  
>  declare_def_handler(pgpolicy, set_pgpolicy)
> @@ -1003,7 +966,7 @@ max_fds_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_max_fds (struct config *conf, char * buff, int len, const void * data)
> +snprint_max_fds (struct config *conf, struct strbuf *buff, const void *data)
>  {
>  	int r = 0, max_fds;
>  
> @@ -1012,9 +975,9 @@ snprint_max_fds (struct config *conf, char * buff, int len, const void * data)
>  
>  	r = get_sys_max_fds(&max_fds);
>  	if (!r && conf->max_fds >= max_fds)
> -		return snprintf(buff, len, "\"max\"");
> +		return append_strbuf_quoted(buff, "max");
>  	else
> -		return snprintf(buff, len, "%d", conf->max_fds);
> +		return print_int(buff, conf->max_fds);
>  }
>  
>  static int
> @@ -1040,14 +1003,14 @@ set_rr_weight(vector strvec, void *ptr)
>  }
>  
>  int
> -print_rr_weight (char * buff, int len, long v)
> +print_rr_weight (struct strbuf *buff, long v)
>  {
>  	if (!v)
>  		return 0;
>  	if (v == RR_WEIGHT_PRIO)
> -		return snprintf(buff, len, "\"priorities\"");
> +		return append_strbuf_quoted(buff, "priorities");
>  	if (v == RR_WEIGHT_NONE)
> -		return snprintf(buff, len, "\"uniform\"");
> +		return append_strbuf_quoted(buff, "uniform");
>  
>  	return 0;
>  }
> @@ -1086,19 +1049,19 @@ set_pgfailback(vector strvec, void *ptr)
>  }
>  
>  int
> -print_pgfailback (char * buff, int len, long v)
> +print_pgfailback (struct strbuf *buff, long v)
>  {
>  	switch(v) {
>  	case  FAILBACK_UNDEF:
>  		return 0;
>  	case -FAILBACK_MANUAL:
> -		return snprintf(buff, len, "\"manual\"");
> +		return append_strbuf_quoted(buff, "manual");
>  	case -FAILBACK_IMMEDIATE:
> -		return snprintf(buff, len, "\"immediate\"");
> +		return append_strbuf_quoted(buff, "immediate");
>  	case -FAILBACK_FOLLOWOVER:
> -		return snprintf(buff, len, "\"followover\"");
> +		return append_strbuf_quoted(buff, "followover");
>  	default:
> -		return snprintf(buff, len, "%li", v);
> +		return print_int(buff, v);
>  	}
>  }
>  
> @@ -1133,17 +1096,17 @@ no_path_retry_helper(vector strvec, void *ptr)
>  }
>  
>  int
> -print_no_path_retry(char * buff, int len, long v)
> +print_no_path_retry(struct strbuf *buff, long v)
>  {
>  	switch(v) {
>  	case NO_PATH_RETRY_UNDEF:
>  		return 0;
>  	case NO_PATH_RETRY_FAIL:
> -		return snprintf(buff, len, "\"fail\"");
> +		return append_strbuf_quoted(buff, "fail");
>  	case NO_PATH_RETRY_QUEUE:
> -		return snprintf(buff, len, "\"queue\"");
> +		return append_strbuf_quoted(buff, "queue");
>  	default:
> -		return snprintf(buff, len, "%li", v);
> +		return print_int(buff, v);
>  	}
>  }
>  
> @@ -1176,12 +1139,12 @@ def_log_checker_err_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_def_log_checker_err (struct config *conf, char * buff, int len,
> -			     const void * data)
> +snprint_def_log_checker_err(struct config *conf, struct strbuf *buff,
> +			    const void * data)
>  {
>  	if (conf->log_checker_err == LOG_CHKR_ERR_ONCE)
> -		return snprintf(buff, len, "once");
> -	return snprintf(buff, len, "always");
> +		return append_strbuf_quoted(buff, "once");
> +	return append_strbuf_quoted(buff, "always");
>  }
>  
>  static int
> @@ -1216,18 +1179,17 @@ set_reservation_key(vector strvec, struct be64 *be64_ptr, uint8_t *flags_ptr,
>  }
>  
>  int
> -print_reservation_key(char * buff, int len, struct be64 key, uint8_t flags,
> -		      int source)
> +print_reservation_key(struct strbuf *buff,
> +		      struct be64 key, uint8_t flags, int source)
>  {
>  	char *flagstr = "";
>  	if (source == PRKEY_SOURCE_NONE)
>  		return 0;
>  	if (source == PRKEY_SOURCE_FILE)
> -		return snprintf(buff, len, "file");
> +		return append_strbuf_quoted(buff, "file");
>  	if (flags & MPATH_F_APTPL_MASK)
>  		flagstr = ":aptpl";
> -	return snprintf(buff, len, "0x%" PRIx64 "%s", get_be64(key),
> -			flagstr);
> +	return print_strbuf(buff, "0x%" PRIx64 "%s", get_be64(key), flagstr);
>  }
>  
>  static int
> @@ -1239,12 +1201,11 @@ def_reservation_key_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_def_reservation_key (struct config *conf, char * buff, int len,
> +snprint_def_reservation_key (struct config *conf, struct strbuf *buff,
>  			     const void * data)
>  {
> -	return print_reservation_key(buff, len, conf->reservation_key,
> -				     conf->sa_flags,
> -				     conf->prkey_source);
> +	return print_reservation_key(buff, conf->reservation_key,
> +				     conf->sa_flags, conf->prkey_source);
>  }
>  
>  static int
> @@ -1259,13 +1220,12 @@ mp_reservation_key_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_mp_reservation_key (struct config *conf, char * buff, int len,
> -			    const void * data)
> +snprint_mp_reservation_key (struct config *conf, struct strbuf *buff,
> +			    const void *data)
>  {
>  	const struct mpentry * mpe = (const struct mpentry *)data;
> -	return print_reservation_key(buff, len, mpe->reservation_key,
> -				     mpe->sa_flags,
> -				     mpe->prkey_source);
> +	return print_reservation_key(buff, mpe->reservation_key,
> +				     mpe->sa_flags, mpe->prkey_source);
>  }
>  
>  static int
> @@ -1288,15 +1248,15 @@ set_off_int_undef(vector strvec, void *ptr)
>  }
>  
>  int
> -print_off_int_undef(char * buff, int len, long v)
> +print_off_int_undef(struct strbuf *buff, long v)
>  {
>  	switch(v) {
>  	case NU_UNDEF:
>  		return 0;
>  	case NU_NO:
> -		return snprintf(buff, len, "\"no\"");
> +		return append_strbuf_quoted(buff, "no");
>  	default:
> -		return snprintf(buff, len, "%li", v);
> +		return print_int(buff, v);
>  	}
>  }
>  
> @@ -1455,13 +1415,13 @@ out:
>  }
>  
>  static int
> -snprint_hw_vpd_vendor(struct config *conf, char * buff, int len,
> +snprint_hw_vpd_vendor(struct config *conf, struct strbuf *buff,
>  		      const void * data)
>  {
>  	const struct hwentry * hwe = (const struct hwentry *)data;
>  
>  	if (hwe->vpd_vendor_id > 0 && hwe->vpd_vendor_id < VPD_VP_ARRAY_SIZE)
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_quoted(buff,
>  				vpd_vendor_pages[hwe->vpd_vendor_id].name);
>  	return 0;
>  }
> @@ -1561,19 +1521,18 @@ declare_ble_handler(blist_protocol)
>  declare_ble_handler(elist_protocol)
>  
>  static int
> -snprint_def_uxsock_timeout(struct config *conf, char * buff, int len,
> -			   const void * data)
> +snprint_def_uxsock_timeout(struct config *conf, struct strbuf *buff,
> +			   const void *data)
>  {
> -	return snprintf(buff, len, "%u", conf->uxsock_timeout);
> +	return print_strbuf(buff, "%u", conf->uxsock_timeout);
>  }
>  
>  static int
> -snprint_ble_simple (struct config *conf, char * buff, int len,
> -		    const void * data)
> +snprint_ble_simple (struct config *conf, struct strbuf *buff, const void *data)
>  {
> -	const struct blentry * ble = (const struct blentry *)data;
> +	const struct blentry *ble = (const struct blentry *)data;
>  
> -	return snprintf(buff, len, "\"%s\"", ble->str);
> +	return print_str(buff, ble->str);
>  }
>  
>  static int
> @@ -1593,24 +1552,22 @@ declare_ble_device_handler(vendor, elist_device, buff, NULL)
>  declare_ble_device_handler(product, blist_device, NULL, buff)
>  declare_ble_device_handler(product, elist_device, NULL, buff)
>  
> -static int
> -snprint_bled_vendor (struct config *conf, char * buff, int len,
> -		     const void * data)
> +static int snprint_bled_vendor(struct config *conf, struct strbuf *buff,
> +			       const void * data)
>  {
>  	const struct blentry_device * bled =
>  		(const struct blentry_device *)data;
>  
> -	return snprintf(buff, len, "\"%s\"", bled->vendor);
> +	return print_str(buff, bled->vendor);
>  }
>  
> -static int
> -snprint_bled_product (struct config *conf, char * buff, int len,
> -		      const void * data)
> +static int snprint_bled_product(struct config *conf, struct strbuf *buff,
> +				const void *data)
>  {
>  	const struct blentry_device * bled =
>  		(const struct blentry_device *)data;
>  
> -	return snprintf(buff, len, "\"%s\"", bled->product);
> +	return print_str(buff, bled->product);
>  }
>  
>  /*
> @@ -1738,8 +1695,7 @@ deprecated_handler(struct config *conf, vector strvec)
>  }
>  
>  static int
> -snprint_deprecated (struct config *conf, char * buff, int len,
> -		    const void * data)
> +snprint_deprecated (struct config *conf, struct strbuf *buff, const void * data)
>  {
>  	return 0;
>  }
> diff --git a/libmultipath/dict.h b/libmultipath/dict.h
> index a917e1c..d80f990 100644
> --- a/libmultipath/dict.h
> +++ b/libmultipath/dict.h
> @@ -6,16 +6,17 @@
>  #endif
>  
>  #include "byteorder.h"
> +struct strbuf;
>  
>  void init_keywords(vector keywords);
>  int get_sys_max_fds(int *);
> -int print_rr_weight(char *buff, int len, long v);
> -int print_pgfailback(char *buff, int len, long v);
> -int print_pgpolicy(char *buff, int len, long v);
> -int print_no_path_retry(char *buff, int len, long v);
> -int print_undef_off_zero(char *buff, int len, long v);
> -int print_dev_loss(char *buff, int len, unsigned long v);
> -int print_reservation_key(char * buff, int len, struct be64 key, uint8_t
> -			  flags, int source);
> -int print_off_int_undef(char *buff, int len, long v);
> +int print_rr_weight(struct strbuf *buff, long v);
> +int print_pgfailback(struct strbuf *buff, long v);
> +int print_pgpolicy(struct strbuf *buff, long v);
> +int print_no_path_retry(struct strbuf *buff, long v);
> +int print_undef_off_zero(struct strbuf *buff, long v);
> +int print_dev_loss(struct strbuf *buff, unsigned long v);
> +int print_reservation_key(struct strbuf *buff,
> +			  struct be64 key, uint8_t flags, int source);
> +int print_off_int_undef(struct strbuf *buff, long v);
>  #endif /* _DICT_H */
> diff --git a/libmultipath/parser.c b/libmultipath/parser.c
> index c70243c..88c7b59 100644
> --- a/libmultipath/parser.c
> +++ b/libmultipath/parser.c
> @@ -25,6 +25,7 @@
>  #include "parser.h"
>  #include "memory.h"
>  #include "debug.h"
> +#include "strbuf.h"
>  
>  /* local vars */
>  static int sublevel = 0;
> @@ -33,7 +34,7 @@ static int line_nr;
>  int
>  keyword_alloc(vector keywords, char *string,
>  	      int (*handler) (struct config *, vector),
> -	      int (*print) (struct config *, char *, int, const void*),
> +	      print_fn *print,
>  	      int unique)
>  {
>  	struct keyword *keyword;
> @@ -72,7 +73,7 @@ install_sublevel_end(void)
>  int
>  _install_keyword(vector keywords, char *string,
>  		 int (*handler) (struct config *, vector),
> -		 int (*print) (struct config *, char *, int, const void*),
> +		 print_fn *print,
>  		 int unique)
>  {
>  	int i = 0;
> @@ -153,42 +154,44 @@ snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
>  		const void *data)
>  {
>  	int r;
> -	int fwd = 0;
> -	char *f = fmt;
> +	char *f;
>  	struct config *conf;
> +	STRBUF_ON_STACK(sbuf);
>  
>  	if (!kw || !kw->print)
>  		return 0;
>  
>  	do {
> -		if (fwd == len || *f == '\0')
> -			break;
> -		if (*f != '%') {
> -			*(buff + fwd) = *f;
> -			fwd++;
> -			continue;
> +		f = strchr(fmt, '%');
> +		if (f == NULL) {
> +			r = append_strbuf_str(&sbuf, fmt);
> +			goto out;
>  		}
> -		f++;
> -		switch(*f) {
> +		if (f != fmt &&
> +		    (r = __append_strbuf_str(&sbuf, fmt, f - fmt)) < 0)
> +			goto out;
> +		fmt = f + 1;
> +		switch(*fmt) {
>  		case 'k':
> -			fwd += snprintf(buff + fwd, len - fwd, "%s", kw->string);
> +			if ((r = append_strbuf_str(&sbuf, kw->string)) < 0)
> +				goto out;
>  			break;
>  		case 'v':
>  			conf = get_multipath_config();
>  			pthread_cleanup_push(put_multipath_config, conf);
> -			r = kw->print(conf, buff + fwd, len - fwd, data);
> +			r = kw->print(conf, &sbuf, data);
>  			pthread_cleanup_pop(1);
> -			if (!r) { /* no output if no value */
> -				buff[0] = '\0';
> -				return 0;
> +			if (r < 0)
> +				goto out;
> +			else if (r == 0) {/* no output if no value */
> +				reset_strbuf(&sbuf);
> +				goto out;
>  			}
> -			fwd += r;
>  			break;
>  		}
> -		if (fwd > len)
> -			fwd = len;
> -	} while (*f++);
> -	return fwd;
> +	} while (*fmt++);
> +out:
> +	return snprintf(buff, len, "%s", get_strbuf_str(&sbuf));
>  }
>  
>  static const char quote_marker[] = { '\0', '"', '\0' };
> diff --git a/libmultipath/parser.h b/libmultipath/parser.h
> index 06666cc..e8b6eb2 100644
> --- a/libmultipath/parser.h
> +++ b/libmultipath/parser.h
> @@ -34,16 +34,20 @@
>  /* local includes */
>  #include "vector.h"
>  #include "config.h"
> +struct strbuf;
>  
>  /* Global definitions */
>  #define EOB  "}"
>  #define MAXBUF	1024
>  
> -/* ketword definition */
> +
> +/* keyword definition */
> +typedef int print_fn(struct config *, struct strbuf *, const void *);
> +
>  struct keyword {
>  	char *string;
>  	int (*handler) (struct config *, vector);
> -	int (*print) (struct config *, char *, int, const void *);
> +	print_fn *print;
>  	vector sub;
>  	int unique;
>  };
> @@ -60,16 +64,15 @@ struct keyword {
>  /* Prototypes */
>  extern int keyword_alloc(vector keywords, char *string,
>  			 int (*handler) (struct config *, vector),
> -			 int (*print) (struct config *, char *, int,
> -				       const void *),
> +			 print_fn *print,
>  			 int unique);
>  #define install_keyword_root(str, h) keyword_alloc(keywords, str, h, NULL, 1)
>  extern void install_sublevel(void);
>  extern void install_sublevel_end(void);
> +
>  extern int _install_keyword(vector keywords, char *string,
>  			    int (*handler) (struct config *, vector),
> -			    int (*print) (struct config *, char *, int,
> -					  const void *),
> +			    print_fn *print,
>  			    int unique);
>  #define install_keyword(str, vec, pri) _install_keyword(keywords, str, vec, pri, 1)
>  #define install_keyword_multi(str, vec, pri) _install_keyword(keywords, str, vec, pri, 0)
> diff --git a/libmultipath/propsel.c b/libmultipath/propsel.c
> index b7b3379..b287667 100644
> --- a/libmultipath/propsel.c
> +++ b/libmultipath/propsel.c
> @@ -24,6 +24,7 @@
>  #include "prioritizers/alua_rtpg.h"
>  #include "prkey.h"
>  #include "propsel.h"
> +#include "strbuf.h"
>  #include <inttypes.h>
>  #include <libudev.h>
>  
> @@ -191,7 +192,7 @@ out:
>  int select_rr_weight(struct config *conf, struct multipath * mp)
>  {
>  	const char *origin;
> -	char buff[13];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(rr_weight);
>  	mp_set_ovr(rr_weight);
> @@ -199,15 +200,16 @@ int select_rr_weight(struct config *conf, struct multipath * mp)
>  	mp_set_conf(rr_weight);
>  	mp_set_default(rr_weight, DEFAULT_RR_WEIGHT);
>  out:
> -	print_rr_weight(buff, 13, mp->rr_weight);
> -	condlog(3, "%s: rr_weight = %s %s", mp->alias, buff, origin);
> +	print_rr_weight(&buff, mp->rr_weight);
> +	condlog(3, "%s: rr_weight = %s %s", mp->alias,
> +		get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_pgfailback(struct config *conf, struct multipath * mp)
>  {
>  	const char *origin;
> -	char buff[13];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(pgfailback);
>  	mp_set_ovr(pgfailback);
> @@ -215,8 +217,9 @@ int select_pgfailback(struct config *conf, struct multipath * mp)
>  	mp_set_conf(pgfailback);
>  	mp_set_default(pgfailback, DEFAULT_FAILBACK);
>  out:
> -	print_pgfailback(buff, 13, mp->pgfailback);
> -	condlog(3, "%s: failback = %s %s", mp->alias, buff, origin);
> +	print_pgfailback(&buff, mp->pgfailback);
> +	condlog(3, "%s: failback = %s %s", mp->alias,
> +		get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
> @@ -339,7 +342,7 @@ void reconcile_features_with_options(const char *id, char **features, int* no_pa
>  {
>  	static const char q_i_n_p[] = "queue_if_no_path";
>  	static const char r_a_h_h[] = "retain_attached_hw_handler";
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	if (*features == NULL)
>  		return;
> @@ -360,17 +363,15 @@ void reconcile_features_with_options(const char *id, char **features, int* no_pa
>  			id, q_i_n_p);
>  		if (*no_path_retry == NO_PATH_RETRY_UNDEF) {
>  			*no_path_retry = NO_PATH_RETRY_QUEUE;
> -			print_no_path_retry(buff, sizeof(buff),
> -					    *no_path_retry);
> +			print_no_path_retry(&buff, *no_path_retry);
>  			condlog(3, "%s: no_path_retry = %s (inherited setting from feature '%s')",
> -				id, buff, q_i_n_p);
> +				id, get_strbuf_str(&buff), q_i_n_p);
>  		};
>  		/* Warn only if features string is overridden */
>  		if (*no_path_retry != NO_PATH_RETRY_QUEUE) {
> -			print_no_path_retry(buff, sizeof(buff),
> -					    *no_path_retry);
> +			print_no_path_retry(&buff, *no_path_retry);
>  			condlog(2, "%s: ignoring feature '%s' because no_path_retry is set to '%s'",
> -				id, q_i_n_p, buff);
> +				id, q_i_n_p, get_strbuf_str(&buff));
>  		}
>  		remove_feature(features, q_i_n_p);
>  	}
> @@ -704,7 +705,7 @@ out:
>  int select_no_path_retry(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin = NULL;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	if (mp->disable_queueing) {
>  		condlog(0, "%s: queueing disabled", mp->alias);
> @@ -716,10 +717,10 @@ int select_no_path_retry(struct config *conf, struct multipath *mp)
>  	mp_set_hwe(no_path_retry);
>  	mp_set_conf(no_path_retry);
>  out:
> -	print_no_path_retry(buff, 12, mp->no_path_retry);
> +	print_no_path_retry(&buff, mp->no_path_retry);
>  	if (origin)
> -		condlog(3, "%s: no_path_retry = %s %s", mp->alias, buff,
> -			origin);
> +		condlog(3, "%s: no_path_retry = %s %s", mp->alias,
> +			get_strbuf_str(&buff), origin);
>  	else
>  		condlog(3, "%s: no_path_retry = undef %s",
>  			mp->alias, default_origin);
> @@ -770,22 +771,23 @@ int select_minio(struct config *conf, struct multipath *mp)
>  int select_fast_io_fail(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_ovr(fast_io_fail);
>  	mp_set_hwe(fast_io_fail);
>  	mp_set_conf(fast_io_fail);
>  	mp_set_default(fast_io_fail, DEFAULT_FAST_IO_FAIL);
>  out:
> -	print_undef_off_zero(buff, 12, mp->fast_io_fail);
> -	condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias, buff, origin);
> +	print_undef_off_zero(&buff, mp->fast_io_fail);
> +	condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias,
> +		get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_dev_loss(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_ovr(dev_loss);
>  	mp_set_hwe(dev_loss);
> @@ -793,15 +795,16 @@ int select_dev_loss(struct config *conf, struct multipath *mp)
>  	mp->dev_loss = DEV_LOSS_TMO_UNSET;
>  	return 0;
>  out:
> -	print_dev_loss(buff, 12, mp->dev_loss);
> -	condlog(3, "%s: dev_loss_tmo = %s %s", mp->alias, buff, origin);
> +	print_dev_loss(&buff, mp->dev_loss);
> +	condlog(3, "%s: dev_loss_tmo = %s %s", mp->alias,
> +		get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_eh_deadline(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_ovr(eh_deadline);
>  	mp_set_hwe(eh_deadline);
> @@ -810,8 +813,9 @@ int select_eh_deadline(struct config *conf, struct multipath *mp)
>  	/* not changing sysfs in default cause, so don't print anything */
>  	return 0;
>  out:
> -	print_undef_off_zero(buff, 12, mp->eh_deadline);
> -	condlog(3, "%s: eh_deadline = %s %s", mp->alias, buff, origin);
> +	print_undef_off_zero(&buff, mp->eh_deadline);
> +	condlog(3, "%s: eh_deadline = %s %s", mp->alias,
> +		get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
> @@ -833,7 +837,7 @@ out:
>  int select_reservation_key(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[PRKEY_SIZE];
> +	STRBUF_ON_STACK(buff);
>  	char *from_file = "";
>  	uint64_t prkey = 0;
>  
> @@ -851,10 +855,10 @@ out:
>  		else
>  			put_be64(mp->reservation_key, prkey);
>  	}
> -	print_reservation_key(buff, PRKEY_SIZE, mp->reservation_key,
> +	print_reservation_key(&buff, mp->reservation_key,
>  			      mp->sa_flags, mp->prkey_source);
> -	condlog(3, "%s: reservation_key = %s %s%s", mp->alias, buff, origin,
> -		from_file);
> +	condlog(3, "%s: reservation_key = %s %s%s", mp->alias,
> +		get_strbuf_str(&buff), origin, from_file);
>  	return 0;
>  }
>  
> @@ -951,16 +955,16 @@ use_delay_watch_checks(struct config *conf, struct multipath *mp)
>  {
>  	int value = NU_UNDEF;
>  	const char *origin = default_origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	do_set(delay_watch_checks, mp->mpe, value, multipaths_origin);
>  	do_set(delay_watch_checks, conf->overrides, value, overrides_origin);
>  	do_set_from_hwe(delay_watch_checks, mp, value, hwe_origin);
>  	do_set(delay_watch_checks, conf, value, conf_origin);
>  out:
> -	if (print_off_int_undef(buff, 12, value) != 0)
> -		condlog(3, "%s: delay_watch_checks = %s %s", mp->alias, buff,
> -			origin);
> +	if (print_off_int_undef(&buff, value) > 0)
> +		condlog(3, "%s: delay_watch_checks = %s %s", mp->alias,
> +			get_strbuf_str(&buff), origin);
>  	return value;
>  }
>  
> @@ -969,23 +973,23 @@ use_delay_wait_checks(struct config *conf, struct multipath *mp)
>  {
>  	int value = NU_UNDEF;
>  	const char *origin = default_origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	do_set(delay_wait_checks, mp->mpe, value, multipaths_origin);
>  	do_set(delay_wait_checks, conf->overrides, value, overrides_origin);
>  	do_set_from_hwe(delay_wait_checks, mp, value, hwe_origin);
>  	do_set(delay_wait_checks, conf, value, conf_origin);
>  out:
> -	if (print_off_int_undef(buff, 12, value) != 0)
> -		condlog(3, "%s: delay_wait_checks = %s %s", mp->alias, buff,
> -			origin);
> +	if (print_off_int_undef(&buff, value) > 0)
> +		condlog(3, "%s: delay_wait_checks = %s %s", mp->alias,
> +			get_strbuf_str(&buff), origin);
>  	return value;
>  }
>  
>  int select_delay_checks(struct config *conf, struct multipath *mp)
>  {
>  	int watch_checks, wait_checks;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	watch_checks = use_delay_watch_checks(conf, mp);
>  	wait_checks = use_delay_wait_checks(conf, mp);
> @@ -1001,16 +1005,17 @@ int select_delay_checks(struct config *conf, struct multipath *mp)
>  		(watch_checks > 0)? delay_watch_origin : delay_wait_origin);
>  	if (watch_checks > 0) {
>  		mp->san_path_err_forget_rate = watch_checks;
> -		print_off_int_undef(buff, 12, mp->san_path_err_forget_rate);
> +		print_off_int_undef(&buff, mp->san_path_err_forget_rate);
>  		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
> -			buff, delay_watch_origin);
> +			get_strbuf_str(&buff), delay_watch_origin);
> +		reset_strbuf(&buff);
>  	}
>  	if (wait_checks > 0) {
>  		mp->san_path_err_recovery_time = wait_checks *
>  						 conf->max_checkint;
> -		print_off_int_undef(buff, 12, mp->san_path_err_recovery_time);
> +		print_off_int_undef(&buff, mp->san_path_err_recovery_time);
>  		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
> -			buff, delay_wait_origin);
> +			get_strbuf_str(&buff), delay_wait_origin);
>  	}
>  	return 0;
>  }
> @@ -1029,7 +1034,7 @@ static int san_path_deprecated_warned;
>  int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	if (marginal_path_check_enabled(mp)) {
>  		mp->san_path_err_threshold = NU_NO;
> @@ -1042,9 +1047,9 @@ int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
>  	mp_set_conf(san_path_err_threshold);
>  	mp_set_default(san_path_err_threshold, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->san_path_err_threshold) != 0)
> +	if (print_off_int_undef(&buff, mp->san_path_err_threshold) > 0)
>  		condlog(3, "%s: san_path_err_threshold = %s %s",
> -			mp->alias, buff, origin);
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	warn_san_path_deprecated(mp, san_path_err_threshold);
>  	return 0;
>  }
> @@ -1052,7 +1057,7 @@ out:
>  int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	if (marginal_path_check_enabled(mp)) {
>  		mp->san_path_err_forget_rate = NU_NO;
> @@ -1065,9 +1070,9 @@ int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
>  	mp_set_conf(san_path_err_forget_rate);
>  	mp_set_default(san_path_err_forget_rate, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->san_path_err_forget_rate) != 0)
> -		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
> -			buff, origin);
> +	if (print_off_int_undef(&buff, mp->san_path_err_forget_rate) > 0)
> +		condlog(3, "%s: san_path_err_forget_rate = %s %s",
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	warn_san_path_deprecated(mp, san_path_err_forget_rate);
>  	return 0;
>  
> @@ -1076,7 +1081,7 @@ out:
>  int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	if (marginal_path_check_enabled(mp)) {
>  		mp->san_path_err_recovery_time = NU_NO;
> @@ -1089,9 +1094,9 @@ int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
>  	mp_set_conf(san_path_err_recovery_time);
>  	mp_set_default(san_path_err_recovery_time, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->san_path_err_recovery_time) != 0)
> +	if (print_off_int_undef(&buff, mp->san_path_err_recovery_time) != 0)
>  		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
> -			buff, origin);
> +			get_strbuf_str(&buff), origin);
>  	warn_san_path_deprecated(mp, san_path_err_recovery_time);
>  	return 0;
>  
> @@ -1100,7 +1105,7 @@ out:
>  int select_marginal_path_err_sample_time(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(marginal_path_err_sample_time);
>  	mp_set_ovr(marginal_path_err_sample_time);
> @@ -1114,17 +1119,16 @@ out:
>  			mp->alias, 2 * IOTIMEOUT_SEC);
>  			mp->marginal_path_err_sample_time = 2 * IOTIMEOUT_SEC;
>  	}
> -	if (print_off_int_undef(buff, 12, mp->marginal_path_err_sample_time)
> -	    != 0)
> +	if (print_off_int_undef(&buff, mp->marginal_path_err_sample_time) > 0)
>  		condlog(3, "%s: marginal_path_err_sample_time = %s %s",
> -			mp->alias, buff, origin);
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(marginal_path_err_rate_threshold);
>  	mp_set_ovr(marginal_path_err_rate_threshold);
> @@ -1132,17 +1136,16 @@ int select_marginal_path_err_rate_threshold(struct config *conf, struct multipat
>  	mp_set_conf(marginal_path_err_rate_threshold);
>  	mp_set_default(marginal_path_err_rate_threshold, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->marginal_path_err_rate_threshold)
> -	    != 0)
> +	if (print_off_int_undef(&buff, mp->marginal_path_err_rate_threshold) > 0)
>  		condlog(3, "%s: marginal_path_err_rate_threshold = %s %s",
> -			mp->alias, buff, origin);
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(marginal_path_err_recheck_gap_time);
>  	mp_set_ovr(marginal_path_err_recheck_gap_time);
> @@ -1150,17 +1153,17 @@ int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multip
>  	mp_set_conf(marginal_path_err_recheck_gap_time);
>  	mp_set_default(marginal_path_err_recheck_gap_time, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12,
> -				mp->marginal_path_err_recheck_gap_time) != 0)
> +	if (print_off_int_undef(&buff,
> +				mp->marginal_path_err_recheck_gap_time) > 0)
>  		condlog(3, "%s: marginal_path_err_recheck_gap_time = %s %s",
> -			mp->alias, buff, origin);
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
>  int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(marginal_path_double_failed_time);
>  	mp_set_ovr(marginal_path_double_failed_time);
> @@ -1168,10 +1171,9 @@ int select_marginal_path_double_failed_time(struct config *conf, struct multipat
>  	mp_set_conf(marginal_path_double_failed_time);
>  	mp_set_default(marginal_path_double_failed_time, DEFAULT_ERR_CHECKS);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->marginal_path_double_failed_time)
> -	    != 0)
> +	if (print_off_int_undef(&buff, mp->marginal_path_double_failed_time) > 0)
>  		condlog(3, "%s: marginal_path_double_failed_time = %s %s",
> -			mp->alias, buff, origin);
> +			mp->alias, get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
> @@ -1215,7 +1217,7 @@ out:
>  int select_ghost_delay (struct config *conf, struct multipath * mp)
>  {
>  	const char *origin;
> -	char buff[12];
> +	STRBUF_ON_STACK(buff);
>  
>  	mp_set_mpe(ghost_delay);
>  	mp_set_ovr(ghost_delay);
> @@ -1223,8 +1225,9 @@ int select_ghost_delay (struct config *conf, struct multipath * mp)
>  	mp_set_conf(ghost_delay);
>  	mp_set_default(ghost_delay, DEFAULT_GHOST_DELAY);
>  out:
> -	if (print_off_int_undef(buff, 12, mp->ghost_delay) != 0)
> -		condlog(3, "%s: ghost_delay = %s %s", mp->alias, buff, origin);
> +	if (print_off_int_undef(&buff, mp->ghost_delay) != 0)
> +		condlog(3, "%s: ghost_delay = %s %s", mp->alias,
> +			get_strbuf_str(&buff), origin);
>  	return 0;
>  }
>  
> -- 
> 2.32.0

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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [dm-devel] [PATCH v2 5/9] libmultipath: use strbuf in print.c
  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
  0 siblings, 0 replies; 17+ messages in thread
From: Benjamin Marzinski @ 2021-08-30 20:47 UTC (permalink / raw)
  To: mwilck; +Cc: dm-devel

On Wed, Aug 11, 2021 at 05:41:46PM +0200, mwilck@suse.com wrote:
> From: Martin Wilck <mwilck@suse.com>
> 
> Use the strbuf API in print.c, wherever growing string buffers are
> appropriate. This requires a large amount of changes in print.c itself,
> and in other files that use functions from print.c. It makes no sense
> to separate this into smaller patches though, as the various snprint_xyz()
> functions depend on each other, and need to use similar prototypes.
> 
> libmultipath version must be bumped, as all the printing functions have
> changed prototypes. Also, add the required strbuf functions to the
> libmultipath API.
> 
> Change the libmultipath calls in prioritizers, foreign libs,
> and in multipathd according to the strbuf-related API changes.
> 
> While working on print.c, I made a couple of other other minor changes:
>  - all snprint_xyz() functions return a negative error in case of failure
>    (most likely is -ENOMEM). In case of success, the number of printed
>    characters is returned.
>  - snprint_progress(): use fill_strbuf() rather than repeated iterations
>  - _snprint_multipath(), _snprint_path(), snprint_multipath_header(),
>    snprint_path_header(), _snprint_pathgroup(): use fill_strbuf()
>    instead of the PAD/NOPAD logic.
>  - _snprint_multipath_topology(): simplified the ASCII art generation for
>    the topology tree somewhat.
>  - snprint_json_xyz(): use fill_strbuf() rather than printing the indent
>    repeatedly.
>  - snprint_blacklist_group(), snprint_blacklist_devgroup(): combined two
>    print statements into one.
> 
> In cli_handlers.c, fixed the returned values for the "len" parameter in
> some functions (it's supposed to be the strlen of the reply + 1).
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>

Reviewed-by: Benjamin Marzinski <bmarzins@redhat.com>

> ---
>  libmultipath/blacklist.c                 |   13 +-
>  libmultipath/discovery.c                 |   13 +-
>  libmultipath/foreign.c                   |   78 +-
>  libmultipath/foreign.h                   |   13 +-
>  libmultipath/foreign/nvme.c              |  100 +-
>  libmultipath/generic.c                   |   26 +-
>  libmultipath/generic.h                   |   17 +-
>  libmultipath/libmultipath.version        |   14 +-
>  libmultipath/parser.c                    |    5 +-
>  libmultipath/parser.h                    |    2 +-
>  libmultipath/print.c                     | 1696 +++++++++-------------
>  libmultipath/print.h                     |   67 +-
>  libmultipath/prioritizers/weightedpath.c |   71 +-
>  multipathd/cli_handlers.c                |  345 ++---
>  14 files changed, 1000 insertions(+), 1460 deletions(-)
> 
> diff --git a/libmultipath/blacklist.c b/libmultipath/blacklist.c
> index 6c6a597..4e315c9 100644
> --- a/libmultipath/blacklist.c
> +++ b/libmultipath/blacklist.c
> @@ -14,6 +14,7 @@
>  #include "blacklist.h"
>  #include "structs_vec.h"
>  #include "print.h"
> +#include "strbuf.h"
>  
>  char *check_invert(char *str, bool *invert)
>  {
> @@ -341,19 +342,21 @@ int
>  filter_protocol(const struct _vector *blist, const struct _vector *elist,
>  		const struct path *pp)
>  {
> -	char buf[PROTOCOL_BUF_SIZE];
> +	STRBUF_ON_STACK(buf);
> +	const char *prot;
>  	int r = MATCH_NOTHING;
>  
>  	if (pp) {
> -		snprint_path_protocol(buf, sizeof(buf), pp);
> +		snprint_path_protocol(&buf, pp);
> +		prot = get_strbuf_str(&buf);
>  
> -		if (match_reglist(elist, buf))
> +		if (match_reglist(elist, prot))
>  			r = MATCH_PROTOCOL_BLIST_EXCEPT;
> -		else if (match_reglist(blist, buf))
> +		else if (match_reglist(blist, prot))
>  			r = MATCH_PROTOCOL_BLIST;
> +		log_filter(pp->dev, NULL, NULL, NULL, NULL, prot, r, 3);
>  	}
>  
> -	log_filter(pp->dev, NULL, NULL, NULL, NULL, buf, r, 3);
>  	return r;
>  }
>  
> diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c
> index e9f5703..f25fe9e 100644
> --- a/libmultipath/discovery.c
> +++ b/libmultipath/discovery.c
> @@ -35,6 +35,7 @@
>  #include "foreign.h"
>  #include "configure.h"
>  #include "print.h"
> +#include "strbuf.h"
>  
>  struct vpd_vendor_page vpd_vendor_pages[VPD_VP_ARRAY_SIZE] = {
>  	[VPD_VP_UNDEF]	= { 0x00, "undef" },
> @@ -895,11 +896,11 @@ sysfs_set_scsi_tmo (struct multipath *mpp, unsigned int checkint)
>  	}
>  
>  	if (err_path) {
> -		char proto_buf[32];
> +		STRBUF_ON_STACK(proto_buf);
>  
> -		snprint_path_protocol(proto_buf, sizeof(proto_buf), err_path);
> +		snprint_path_protocol(&proto_buf, err_path);
>  		condlog(2, "%s: setting dev_loss_tmo is unsupported for protocol %s",
> -			mpp->alias, proto_buf);
> +			mpp->alias, get_strbuf_str(&proto_buf));
>  	}
>  	return 0;
>  }
> @@ -2380,11 +2381,11 @@ int pathinfo(struct path *pp, struct config *conf, int mask)
>  				 * It's likely that this path is not fit for
>  				 * multipath use.
>  				 */
> -				char buf[16];
> +				STRBUF_ON_STACK(buf);
>  
> -				snprint_path(buf, sizeof(buf), "%T", pp, 0);
> +				snprint_path(&buf, "%T", pp, 0);
>  				condlog(1, "%s: no WWID in state \"%s\", giving up",
> -					pp->dev, buf);
> +					pp->dev, get_strbuf_str(&buf));
>  				return PATHINFO_SKIPPED;
>  			}
>  			return PATHINFO_OK;
> diff --git a/libmultipath/foreign.c b/libmultipath/foreign.c
> index fce1934..e091a1d 100644
> --- a/libmultipath/foreign.c
> +++ b/libmultipath/foreign.c
> @@ -34,6 +34,7 @@
>  #include "structs.h"
>  #include "structs_vec.h"
>  #include "print.h"
> +#include "strbuf.h"
>  
>  static vector foreigns;
>  
> @@ -497,11 +498,11 @@ void foreign_multipath_layout(void)
>  	pthread_cleanup_pop(1);
>  }
>  
> -int snprint_foreign_topology(char *buf, int len, int verbosity)
> +int snprint_foreign_topology(struct strbuf *buf, int verbosity)
>  {
>  	struct foreign *fgn;
>  	int i;
> -	char *c = buf;
> +	size_t initial_len = get_strbuf_len(buf);
>  
>  	rdlock_foreigns();
>  	if (foreigns == NULL) {
> @@ -521,58 +522,32 @@ int snprint_foreign_topology(char *buf, int len, int verbosity)
>  		vec = fgn->get_multipaths(fgn->context);
>  		if (vec != NULL) {
>  			vector_foreach_slot(vec, gm, j) {
> -
> -				c += _snprint_multipath_topology(gm, c,
> -								 buf + len - c,
> -								 verbosity);
> -				if (c >= buf + len - 1)
> +				if (_snprint_multipath_topology(
> +					    gm, buf, verbosity) < 0)
>  					break;
>  			}
> -			if (c >= buf + len - 1)
> -				break;
>  		}
>  		fgn->release_multipaths(fgn->context, vec);
>  		pthread_cleanup_pop(1);
>  	}
>  
>  	pthread_cleanup_pop(1);
> -	return c - buf;
> +	return get_strbuf_len(buf) - initial_len;
>  }
>  
>  void print_foreign_topology(int verbosity)
>  {
> -	int buflen = MAX_LINE_LEN * MAX_LINES;
> -	char *buf = NULL, *tmp = NULL;
> -
> -	buf = calloc(1, buflen);
> -
> -	while (buf != NULL) {
> -		char *c = buf;
> -
> -		c += snprint_foreign_topology(buf, buflen,
> -						   verbosity);
> -		if (c < buf + buflen - 1)
> -			break;
> -
> -		buflen *= 2;
> -		tmp = buf;
> -		buf = realloc(buf, buflen);
> -	}
> +	STRBUF_ON_STACK(buf);
>  
> -	if (buf == NULL && tmp != NULL)
> -		buf = tmp;
> -
> -	if (buf != NULL) {
> -		printf("%s", buf);
> -		free(buf);
> -	}
> +	snprint_foreign_topology(&buf, verbosity);
> +	printf("%s", get_strbuf_str(&buf));
>  }
>  
> -int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
> +int snprint_foreign_paths(struct strbuf *buf, const char *style, int pretty)
>  {
>  	struct foreign *fgn;
>  	int i;
> -	char *c = buf;
> +	size_t initial_len = get_strbuf_len(buf);
>  
>  	rdlock_foreigns();
>  	if (foreigns == NULL) {
> @@ -584,7 +559,7 @@ int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
>  	vector_foreach_slot(foreigns, fgn, i) {
>  		const struct _vector *vec;
>  		const struct gen_path *gp;
> -		int j;
> +		int j, ret = 0;
>  
>  		fgn->lock(fgn->context);
>  		pthread_cleanup_push(fgn->unlock, fgn->context);
> @@ -592,28 +567,27 @@ int snprint_foreign_paths(char *buf, int len, const char *style, int pretty)
>  		vec = fgn->get_paths(fgn->context);
>  		if (vec != NULL) {
>  			vector_foreach_slot(vec, gp, j) {
> -				c += _snprint_path(gp, c, buf + len - c,
> -						   style, pretty);
> -				if (c >= buf + len - 1)
> +				ret = _snprint_path(gp, buf, style, pretty);
> +				if (ret < 0)
>  					break;
>  			}
> -			if (c >= buf + len - 1)
> -				break;
>  		}
>  		fgn->release_paths(fgn->context, vec);
>  		pthread_cleanup_pop(1);
> +		if (ret < 0)
> +			break;
>  	}
>  
>  	pthread_cleanup_pop(1);
> -	return c - buf;
> +	return get_strbuf_len(buf) - initial_len;
>  }
>  
> -int snprint_foreign_multipaths(char *buf, int len,
> +int snprint_foreign_multipaths(struct strbuf *buf,
>  			       const char *style, int pretty)
>  {
>  	struct foreign *fgn;
>  	int i;
> -	char *c = buf;
> +	size_t initial_len = get_strbuf_len(buf);
>  
>  	rdlock_foreigns();
>  	if (foreigns == NULL) {
> @@ -625,7 +599,7 @@ int snprint_foreign_multipaths(char *buf, int len,
>  	vector_foreach_slot(foreigns, fgn, i) {
>  		const struct _vector *vec;
>  		const struct gen_multipath *gm;
> -		int j;
> +		int j, ret = 0;
>  
>  		fgn->lock(fgn->context);
>  		pthread_cleanup_push(fgn->unlock, fgn->context);
> @@ -633,18 +607,18 @@ int snprint_foreign_multipaths(char *buf, int len,
>  		vec = fgn->get_multipaths(fgn->context);
>  		if (vec != NULL) {
>  			vector_foreach_slot(vec, gm, j) {
> -				c += _snprint_multipath(gm, c, buf + len - c,
> -							style, pretty);
> -				if (c >= buf + len - 1)
> +				ret = _snprint_multipath(gm, buf,
> +							 style, pretty);
> +				if (ret < 0)
>  					break;
>  			}
> -			if (c >= buf + len - 1)
> -				break;
>  		}
>  		fgn->release_multipaths(fgn->context, vec);
>  		pthread_cleanup_pop(1);
> +		if (ret < 0)
> +			break;
>  	}
>  
>  	pthread_cleanup_pop(1);
> -	return c - buf;
> +	return get_strbuf_len(buf) - initial_len;
>  }
> diff --git a/libmultipath/foreign.h b/libmultipath/foreign.h
> index acd3360..77fc485 100644
> --- a/libmultipath/foreign.h
> +++ b/libmultipath/foreign.h
> @@ -18,9 +18,9 @@
>  #define _FOREIGN_H
>  #include <stdbool.h>
>  #include <libudev.h>
> +#define LIBMP_FOREIGN_API ((1 << 8) | 1)
>  
> -#define LIBMP_FOREIGN_API ((1 << 8) | 0)
> -
> +struct strbuf;
>  struct context;
>  
>  /* return codes of functions below returning "int" */
> @@ -267,35 +267,32 @@ void foreign_multipath_layout(void);
>   * prints topology information from foreign libraries into buffer,
>   * '\0' - terminated.
>   * @param buf: output buffer
> - * @param len: size of output buffer
>   * @param verbosity: verbosity level
>   * @returns: number of printed characters excluding trailing '\0'.
>   */
> -int snprint_foreign_topology(char *buf, int len, int verbosity);
> +int snprint_foreign_topology(struct strbuf *buf, int verbosity);
>  
>  /**
>   * snprint_foreign_paths(buf, len, style, pad);
>   * prints formatted path information from foreign libraries into buffer,
>   * '\0' - terminated.
>   * @param buf: output buffer
> - * @param len: size of output buffer
>   * @param style: format string
>   * @param pad: whether to pad field width
>   * @returns: number of printed characters excluding trailing '\0'.
>   */
> -int snprint_foreign_paths(char *buf, int len, const char *style, int pad);
> +int snprint_foreign_paths(struct strbuf *buf, const char *style, int pad);
>  
>  /**
>   * snprint_foreign_multipaths(buf, len, style, pad);
>   * prints formatted map information from foreign libraries into buffer,
>   * '\0' - terminated.
>   * @param buf: output buffer
> - * @param len: size of output buffer
>   * @param style: format string
>   * @param pad: whether to pad field width
>   * @returns: number of printed characters excluding trailing '\0'.
>   */
> -int snprint_foreign_multipaths(char *buf, int len,
> +int snprint_foreign_multipaths(struct strbuf *buf,
>  			       const char *style, int pretty);
>  
>  /**
> diff --git a/libmultipath/foreign/nvme.c b/libmultipath/foreign/nvme.c
> index b726be2..d40c086 100644
> --- a/libmultipath/foreign/nvme.c
> +++ b/libmultipath/foreign/nvme.c
> @@ -37,6 +37,7 @@
>  #include "debug.h"
>  #include "structs.h"
>  #include "sysfs.h"
> +#include "strbuf.h"
>  
>  static const char nvme_vendor[] = "NVMe";
>  static const char N_A[] = "n/a";
> @@ -138,7 +139,7 @@ static void rstrip(char *str)
>  }
>  
>  static int snprint_nvme_map(const struct gen_multipath *gmp,
> -			    char *buff, int len, char wildcard)
> +			    struct strbuf *buff, char wildcard)
>  {
>  	const struct nvme_map *nvm = const_gen_mp_to_nvme(gmp);
>  	char fld[NAME_LEN];
> @@ -146,26 +147,26 @@ static int snprint_nvme_map(const struct gen_multipath *gmp,
>  
>  	switch (wildcard) {
>  	case 'd':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysname(nvm->udev));
>  	case 'n':
> -		return snprintf(buff, len, "%s:nsid.%s",
> +		return print_strbuf(buff, "%s:nsid.%s",
>  				udev_device_get_sysattr_value(nvm->subsys,
>  							      "subsysnqn"),
>  				udev_device_get_sysattr_value(nvm->udev,
>  							      "nsid"));
>  	case 'w':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysattr_value(nvm->udev,
>  							      "wwid"));
>  	case 'N':
> -		return snprintf(buff, len, "%u", nvm->nr_live);
> +		return print_strbuf(buff, "%u", nvm->nr_live);
>  	case 'S':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysattr_value(nvm->udev,
>  							      "size"));
>  	case 'v':
> -		return snprintf(buff, len, "%s", nvme_vendor);
> +		return append_strbuf_str(buff, nvme_vendor);
>  	case 's':
>  	case 'p':
>  		snprintf(fld, sizeof(fld), "%s",
> @@ -173,30 +174,30 @@ static int snprint_nvme_map(const struct gen_multipath *gmp,
>  						      "model"));
>  		rstrip(fld);
>  		if (wildcard == 'p')
> -			return snprintf(buff, len, "%s", fld);
> -		return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
> +			return append_strbuf_str(buff, fld);
> +		return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld,
>  				udev_device_get_sysattr_value(nvm->subsys,
>  							      "firmware_rev"));
>  	case 'e':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysattr_value(nvm->subsys,
>  							      "firmware_rev"));
>  	case 'r':
>  		val = udev_device_get_sysattr_value(nvm->udev, "ro");
>  		if (val[0] == 1)
> -			return snprintf(buff, len, "%s", "ro");
> +			return append_strbuf_str(buff, "ro");
>  		else
> -			return snprintf(buff, len, "%s", "rw");
> +			return append_strbuf_str(buff, "rw");
>  	case 'G':
> -		return snprintf(buff, len, "%s", THIS);
> +		return append_strbuf_str(buff, THIS);
>  	case 'h':
>  		if (nvm->ana_supported == YNU_YES)
> -			return snprintf(buff, len, "ANA");
> +			return append_strbuf_str(buff, "ANA");
>  	default:
>  		break;
>  	}
>  
> -	return snprintf(buff, len, N_A);
> +	return append_strbuf_str(buff, N_A);
>  }
>  
>  static const struct _vector*
> @@ -214,7 +215,7 @@ nvme_pg_rel_paths(__attribute__((unused)) const struct gen_pathgroup *gpg,
>  	/* empty */
>  }
>  
> -static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
> +static int snprint_hcil(const struct nvme_path *np, struct strbuf *buf)
>  {
>  	unsigned int nvmeid, ctlid, nsid;
>  	int rc;
> @@ -223,14 +224,13 @@ static int snprint_hcil(const struct nvme_path *np, char *buf, int len)
>  	rc = sscanf(sysname, "nvme%uc%un%u", &nvmeid, &ctlid, &nsid);
>  	if (rc != 3) {
>  		condlog(1, "%s: failed to scan %s", __func__, sysname);
> -		rc = snprintf(buf, len, "(ERR:%s)", sysname);
> +		return print_strbuf(buf, "(ERR:%s)", sysname);
>  	} else
> -		rc = snprintf(buf, len, "%u:%u:%u", nvmeid, ctlid, nsid);
> -	return (rc < len ? rc : len);
> +		return print_strbuf(buf, "%u:%u:%u", nvmeid, ctlid, nsid);
>  }
>  
>  static int snprint_nvme_path(const struct gen_path *gp,
> -			     char *buff, int len, char wildcard)
> +			     struct strbuf *buff, char wildcard)
>  {
>  	const struct nvme_path *np = const_gen_path_to_nvme(gp);
>  	dev_t devt;
> @@ -239,37 +239,37 @@ static int snprint_nvme_path(const struct gen_path *gp,
>  
>  	switch (wildcard) {
>  	case 'w':
> -		return snprintf(buff, len, "%s",
> -				udev_device_get_sysattr_value(np->udev,
> -							      "wwid"));
> +		return print_strbuf(buff, "%s",
> +				    udev_device_get_sysattr_value(np->udev,
> +								  "wwid"));
>  	case 'd':
> -		return snprintf(buff, len, "%s",
> -				udev_device_get_sysname(np->udev));
> +		return print_strbuf(buff, "%s",
> +				    udev_device_get_sysname(np->udev));
>  	case 'i':
> -		return snprint_hcil(np, buff, len);
> +		return snprint_hcil(np, buff);
>  	case 'D':
>  		devt = udev_device_get_devnum(np->udev);
> -		return snprintf(buff, len, "%u:%u", major(devt), minor(devt));
> +		return print_strbuf(buff, "%u:%u", major(devt), minor(devt));
>  	case 'o':
>  		if (sysfs_attr_get_value(np->ctl, "state",
>  					 fld, sizeof(fld)) > 0)
> -			return snprintf(buff, len, "%s", fld);
> +			return append_strbuf_str(buff, fld);
>  		break;
>  	case 'T':
>  		if (sysfs_attr_get_value(np->udev, "ana_state", fld,
>  					 sizeof(fld)) > 0)
> -			return snprintf(buff, len, "%s", fld);
> +			return append_strbuf_str(buff, fld);
>  		break;
>  	case 'p':
>  		if (sysfs_attr_get_value(np->udev, "ana_state", fld,
>  					 sizeof(fld)) > 0) {
>  			rstrip(fld);
>  			if (!strcmp(fld, "optimized"))
> -				return snprintf(buff, len, "%d", 50);
> +				return print_strbuf(buff, "%d", 50);
>  			else if (!strcmp(fld, "non-optimized"))
> -				return snprintf(buff, len, "%d", 10);
> +				return print_strbuf(buff, "%d", 10);
>  			else
> -				return snprintf(buff, len, "%d", 0);
> +				return print_strbuf(buff, "%d", 0);
>  		}
>  		break;
>  	case 's':
> @@ -277,46 +277,45 @@ static int snprint_nvme_path(const struct gen_path *gp,
>  			 udev_device_get_sysattr_value(np->ctl,
>  						      "model"));
>  		rstrip(fld);
> -		return snprintf(buff, len, "%s,%s,%s", nvme_vendor, fld,
> -				udev_device_get_sysattr_value(np->ctl,
> +		return print_strbuf(buff, "%s,%s,%s", nvme_vendor, fld,
> +				    udev_device_get_sysattr_value(np->ctl,
>  							      "firmware_rev"));
>  	case 'S':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  			udev_device_get_sysattr_value(np->udev,
>  						      "size"));
>  	case 'z':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysattr_value(np->ctl,
>  							      "serial"));
>  	case 'm':
> -		return snprintf(buff, len, "%s",
> +		return append_strbuf_str(buff,
>  				udev_device_get_sysname(np->map->udev));
>  	case 'N':
>  	case 'R':
> -		return snprintf(buff, len, "%s:%s",
> +		return print_strbuf(buff, "%s:%s",
>  			udev_device_get_sysattr_value(np->ctl,
>  						      "transport"),
>  			udev_device_get_sysattr_value(np->ctl,
>  						      "address"));
>  	case 'G':
> -		return snprintf(buff, len, "[%s]", THIS);
> +		return print_strbuf(buff, "[%s]", THIS);
>  	case 'a':
>  		pci = udev_device_get_parent_with_subsystem_devtype(np->ctl,
>  								    "pci",
>  								    NULL);
>  		if (pci != NULL)
> -			return snprintf(buff, len, "PCI:%s",
> -					udev_device_get_sysname(pci));
> +			return print_strbuf(buff, "PCI:%s",
> +					    udev_device_get_sysname(pci));
>  		/* fall through */
>  	default:
>  		break;
>  	}
> -	return snprintf(buff, len, "%s", N_A);
> -	return 0;
> +	return append_strbuf_str(buff, N_A);
>  }
>  
>  static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
> -			   char *buff, int len, char wildcard)
> +			   struct strbuf *buff, char wildcard)
>  {
>  	const struct nvme_pathgroup *pg = const_gen_pg_to_nvme(gmp);
>  	const struct nvme_path *path = nvme_pg_to_path(pg);
> @@ -324,22 +323,19 @@ static int snprint_nvme_pg(const struct gen_pathgroup *gmp,
>  	switch (wildcard) {
>  	case 't':
>  		return snprint_nvme_path(nvme_path_to_gen(path),
> -					 buff, len, 'T');
> +					 buff, 'T');
>  	case 'p':
>  		return snprint_nvme_path(nvme_path_to_gen(path),
> -					 buff, len, 'p');
> +					 buff, 'p');
>  	default:
> -		return snprintf(buff, len, N_A);
> +		return append_strbuf_str(buff, N_A);
>  	}
>  }
>  
>  static int nvme_style(__attribute__((unused)) const struct gen_multipath* gm,
> -		      char *buf, int len,
> -		      __attribute__((unused)) int verbosity)
> +		      struct strbuf *buf, __attribute__((unused)) int verbosity)
>  {
> -	int n = snprintf(buf, len, "%%w [%%G]:%%d %%s");
> -
> -	return (n < len ? n : len - 1);
> +	return append_strbuf_str(buf, "%%w [%%G]:%%d %%s");
>  }
>  
>  static const struct gen_multipath_ops nvme_map_ops = {
> diff --git a/libmultipath/generic.c b/libmultipath/generic.c
> index 5f03b9e..e7cf297 100644
> --- a/libmultipath/generic.c
> +++ b/libmultipath/generic.c
> @@ -15,23 +15,23 @@
>    along with this program.  If not, see <https://www.gnu.org/licenses/>.
>   */
>  
> -
> -#include <string.h>
>  #include "generic.h"
>  #include "structs.h"
> +#include "util.h"
> +#include "strbuf.h"
>  
> -int generic_style(const struct gen_multipath* gm,
> -		  char *buf, int len, __attribute__((unused)) int verbosity)
> +int generic_style(const struct gen_multipath* gm, struct strbuf *buf,
> +		  __attribute__((unused)) int verbosity)
>  {
> -	char alias_buf[WWID_SIZE];
> -	char wwid_buf[WWID_SIZE];
> -	int n = 0;
> -
> -	gm->ops->snprint(gm, alias_buf, sizeof(alias_buf), 'n');
> -	gm->ops->snprint(gm, wwid_buf, sizeof(wwid_buf), 'w');
> +	STRBUF_ON_STACK(tmp);
> +	char *alias_buf __attribute__((cleanup(cleanup_charp)));
> +	const char *wwid_buf;
>  
> -	n += snprintf(buf, len, "%%n %s[%%G]:%%d %%s",
> -		      strcmp(alias_buf, wwid_buf) ? "(%w) " : "");
> +	gm->ops->snprint(gm, &tmp, 'n');
> +	alias_buf = steal_strbuf_str(&tmp);
> +	gm->ops->snprint(gm, &tmp, 'w');
> +	wwid_buf = get_strbuf_str(&tmp);
>  
> -	return (n < len ? n : len - 1);
> +	return print_strbuf(buf, "%%n %s[%%G]:%%d %%s",
> +			    strcmp(alias_buf, wwid_buf) ? "(%w) " : "");
>  }
> diff --git a/libmultipath/generic.h b/libmultipath/generic.h
> index 6346ffe..57c123c 100644
> --- a/libmultipath/generic.h
> +++ b/libmultipath/generic.h
> @@ -18,6 +18,7 @@
>  #define _GENERIC_H
>  #include "vector.h"
>  
> +struct strbuf;
>  struct gen_multipath;
>  struct gen_pathgroup;
>  struct gen_path;
> @@ -50,26 +51,24 @@ struct gen_multipath_ops {
>  	 * 0-terminated, no more than "len" characters including trailing '\0'.
>  	 *
>  	 * @param gmp: generic multipath object to act on
> -	 * @param buf: output buffer
> -	 * @param buflen: buffer size
> +	 * @param buf: output struct strbuf
>  	 * @param wildcard: the multipath wildcard (see print.c)
>  	 * @returns the number of characters printed (without trailing '\0').
>  	 */
>  	int (*snprint)(const struct gen_multipath*,
> -		       char *buf, int len, char wildcard);
> +		       struct strbuf *buf, char wildcard);
>  	/**
>  	 * method: style(gmp, buf, len, verbosity)
>  	 * returns the format string to be used for the multipath object,
>  	 * defined with the wildcards as defined in print.c
>  	 * generic_style() should work well in most cases.
>  	 * @param gmp: generic multipath object to act on
> -	 * @param buf: output buffer
> -	 * @param buflen: buffer size
> +	 * @param buf: output strbuf
>  	 * @param verbosity: verbosity level
>  	 * @returns number of format chars printed
>  	 */
>  	int (*style)(const struct gen_multipath*,
> -		     char *buf, int len, int verbosity);
> +		     struct strbuf *buf, int verbosity);
>  };
>  
>  /**
> @@ -95,7 +94,7 @@ struct gen_pathgroup_ops {
>  	 * see gen_multipath_ops->snprint() above
>  	 */
>  	int (*snprint)(const struct gen_pathgroup*,
> -		       char *buf, int len, char wildcard);
> +		       struct strbuf *buf, char wildcard);
>  };
>  
>  struct gen_path_ops {
> @@ -104,7 +103,7 @@ struct gen_path_ops {
>  	 * see gen_multipath_ops->snprint() above
>  	 */
>  	int (*snprint)(const struct gen_path*,
> -		       char *buf, int len, char wildcard);
> +		       struct strbuf *buf, char wildcard);
>  };
>  
>  struct gen_multipath {
> @@ -129,6 +128,6 @@ struct gen_path {
>   * foreign libraries.
>   */
>  int generic_style(const struct gen_multipath*,
> -		  char *buf, int len, int verbosity);
> +		  struct strbuf *buf, int verbosity);
>  
>  #endif /* _GENERIC_H */
> diff --git a/libmultipath/libmultipath.version b/libmultipath/libmultipath.version
> index 6dd52c2..1d84d97 100644
> --- a/libmultipath/libmultipath.version
> +++ b/libmultipath/libmultipath.version
> @@ -31,7 +31,7 @@
>   *   The new version inherits the previous ones.
>   */
>  
> -LIBMULTIPATH_7.0.0 {
> +LIBMULTIPATH_8.0.0 {
>  global:
>  	/* symbols referenced by multipath and multipathd */
>  	add_foreign;
> @@ -273,3 +273,15 @@ global:
>  local:
>  	*;
>  };
> +
> +LIBMULTIPATH_8.1.0 {
> +global:
> +	reset_strbuf;
> +	append_strbuf_str;
> +	get_strbuf_len;
> +	get_strbuf_str;
> +	steal_strbuf_str;
> +	fill_strbuf;
> +	print_strbuf;
> +	truncate_strbuf;
> +} LIBMULTIPATH_8.0.0;
> diff --git a/libmultipath/parser.c b/libmultipath/parser.c
> index 88c7b59..8ca91bf 100644
> --- a/libmultipath/parser.c
> +++ b/libmultipath/parser.c
> @@ -150,7 +150,7 @@ find_keyword(vector keywords, vector v, char * name)
>  }
>  
>  int
> -snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
> +snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw,
>  		const void *data)
>  {
>  	int r;
> @@ -191,7 +191,8 @@ snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
>  		}
>  	} while (*fmt++);
>  out:
> -	return snprintf(buff, len, "%s", get_strbuf_str(&sbuf));
> +	return __append_strbuf_str(buff, get_strbuf_str(&sbuf),
> +				   get_strbuf_len(&sbuf));
>  }
>  
>  static const char quote_marker[] = { '\0', '"', '\0' };
> diff --git a/libmultipath/parser.h b/libmultipath/parser.h
> index e8b6eb2..b43d46f 100644
> --- a/libmultipath/parser.h
> +++ b/libmultipath/parser.h
> @@ -82,7 +82,7 @@ extern vector alloc_strvec(char *string);
>  extern void *set_value(vector strvec);
>  extern int process_file(struct config *conf, const char *conf_file);
>  extern struct keyword * find_keyword(vector keywords, vector v, char * name);
> -int snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw,
> +int snprint_keyword(struct strbuf *buff, const char *fmt, struct keyword *kw,
>  		    const void *data);
>  bool is_quote(const char* token);
>  
> diff --git a/libmultipath/print.c b/libmultipath/print.c
> index 29ce499..dd04dea 100644
> --- a/libmultipath/print.c
> +++ b/libmultipath/print.c
> @@ -1,4 +1,4 @@
> - /*
> +/*
>   * Copyright (c) 2005 Christophe Varoqui
>   */
>  #include <stdio.h>
> @@ -31,59 +31,33 @@
>  #include "discovery.h"
>  #include "util.h"
>  #include "foreign.h"
> +#include "strbuf.h"
>  
>  #define MAX(x,y) (((x) > (y)) ? (x) : (y))
>  #define MIN(x,y) (((x) > (y)) ? (y) : (x))
> -#define TAIL     (line + len - 1 - c)
> -#define NOPAD    s = c
> -#define PAD(x) \
> -do { \
> -	while (c < (s + x) && (c < (line + len - 1))) \
> -		*c++ = ' '; \
> -	s = c; \
> -} while (0)
> -
> -static char *
> -__endline(char *line, size_t len, char *c)
> -{
> -	if (c > line) {
> -		if (c >= line + len)
> -			c = line + len - 1;
> -		*(c - 1) = '\n';
> -		*c = '\0';
> -	}
> -	return c;
> -}
> -
> -#define PRINT(var, size, format, args...) \
> -do { \
> -	fwd = snprintf(var, size, format, ##args); \
> -	c += (fwd >= size) ? size : fwd; \
> -} while (0)
> -
>  /*
>   * information printing helpers
>   */
>  static int
> -snprint_str (char * buff, size_t len, const char * str)
> +snprint_str(struct strbuf *buff, const char *str)
>  {
> -	return snprintf(buff, len, "%s", str);
> +	return append_strbuf_str(buff, str);
>  }
>  
>  static int
> -snprint_int (char * buff, size_t len, int val)
> +snprint_int (struct strbuf *buff, int val)
>  {
> -	return snprintf(buff, len, "%i", val);
> +	return print_strbuf(buff, "%i", val);
>  }
>  
>  static int
> -snprint_uint (char * buff, size_t len, unsigned int val)
> +snprint_uint (struct strbuf *buff, unsigned int val)
>  {
> -	return snprintf(buff, len, "%u", val);
> +	return print_strbuf(buff, "%u", val);
>  }
>  
>  static int
> -snprint_size (char * buff, size_t len, unsigned long long size)
> +snprint_size (struct strbuf *buff, unsigned long long size)
>  {
>  	float s = (float)(size >> 1); /* start with KB */
>  	char units[] = {'K','M','G','T','P'};
> @@ -94,184 +68,177 @@ snprint_size (char * buff, size_t len, unsigned long long size)
>  		u++;
>  	}
>  
> -	return snprintf(buff, len, "%.*f%c", s < 10, s, *u);
> +	return print_strbuf(buff, "%.*f%c", s < 10, s, *u);
>  }
>  
>  /*
>   * multipath info printing functions
>   */
>  static int
> -snprint_name (char * buff, size_t len, const struct multipath * mpp)
> +snprint_name (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (mpp->alias)
> -		return snprintf(buff, len, "%s", mpp->alias);
> +		return append_strbuf_str(buff, mpp->alias);
>  	else
> -		return snprintf(buff, len, "%s", mpp->wwid);
> +		return append_strbuf_str(buff, mpp->wwid);
>  }
>  
>  static int
> -snprint_sysfs (char * buff, size_t len, const struct multipath * mpp)
> +snprint_sysfs (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (mpp->dmi)
> -		return snprintf(buff, len, "dm-%i", mpp->dmi->minor);
> +		return print_strbuf(buff, "dm-%i", mpp->dmi->minor);
>  	else
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  }
>  
>  static int
> -snprint_ro (char * buff, size_t len, const struct multipath * mpp)
> +snprint_ro (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (!mpp->dmi)
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  	if (mpp->dmi->read_only)
> -		return snprintf(buff, len, "ro");
> +		return append_strbuf_str(buff, "ro");
>  	else
> -		return snprintf(buff, len, "rw");
> +		return append_strbuf_str(buff, "rw");
>  }
>  
>  static int
> -snprint_progress (char * buff, size_t len, int cur, int total)
> +snprint_progress (struct strbuf *buff, int cur, int total)
>  {
> -	char * c = buff;
> -	char * end = buff + len;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc;
>  
>  	if (total > 0) {
>  		int i = PROGRESS_LEN * cur / total;
>  		int j = PROGRESS_LEN - i;
>  
> -		while (i-- > 0) {
> -			c += snprintf(c, len, "X");
> -			if ((len = (end - c)) <= 1) goto out;
> -		}
> -
> -		while (j-- > 0) {
> -			c += snprintf(c, len,  ".");
> -			if ((len = (end - c)) <= 1) goto out;
> +		if ((rc = fill_strbuf(buff, 'X', i)) < 0 ||
> +		    (rc = fill_strbuf(buff, '.', j) < 0)) {
> +			truncate_strbuf(buff, initial_len);
> +			return rc;
>  		}
>  	}
>  
> -	c += snprintf(c, len, " %i/%i", cur, total);
> -
> -out:
> -	buff[c - buff + 1] = '\0';
> -	return (c - buff + 1);
> +	if ((rc = print_strbuf(buff, " %i/%i", cur, total)) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  static int
> -snprint_failback (char * buff, size_t len, const struct multipath * mpp)
> +snprint_failback (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (mpp->pgfailback == -FAILBACK_IMMEDIATE)
> -		return snprintf(buff, len, "immediate");
> +		return append_strbuf_str(buff, "immediate");
>  	if (mpp->pgfailback == -FAILBACK_FOLLOWOVER)
> -		return snprintf(buff, len, "followover");
> +		return append_strbuf_str(buff, "followover");
>  
>  	if (!mpp->failback_tick)
> -		return snprintf(buff, len, "-");
> +		return append_strbuf_str(buff, "-");
>  	else
> -		return snprint_progress(buff, len, mpp->failback_tick,
> +		return snprint_progress(buff, mpp->failback_tick,
>  					mpp->pgfailback);
>  }
>  
>  static int
> -snprint_queueing (char * buff, size_t len, const struct multipath * mpp)
> +snprint_queueing (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (mpp->no_path_retry == NO_PATH_RETRY_FAIL)
> -		return snprintf(buff, len, "off");
> +		return append_strbuf_str(buff, "off");
>  	else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE)
> -		return snprintf(buff, len, "on");
> +		return append_strbuf_str(buff, "on");
>  	else if (mpp->no_path_retry == NO_PATH_RETRY_UNDEF)
> -		return snprintf(buff, len, "-");
> +		return append_strbuf_str(buff, "-");
>  	else if (mpp->no_path_retry > 0) {
>  		if (mpp->retry_tick > 0)
>  
> -			return snprintf(buff, len, "%i sec",
> -					mpp->retry_tick);
> +			return print_strbuf(buff, "%i sec", mpp->retry_tick);
>  		else if (mpp->retry_tick == 0 && count_active_paths(mpp) > 0)
> -			return snprintf(buff, len, "%i chk",
> -					mpp->no_path_retry);
> +			return print_strbuf(buff, "%i chk",
> +					    mpp->no_path_retry);
>  		else
> -			return snprintf(buff, len, "off");
> +			return append_strbuf_str(buff, "off");
>  	}
>  	return 0;
>  }
>  
>  static int
> -snprint_nb_paths (char * buff, size_t len, const struct multipath * mpp)
> +snprint_nb_paths (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_int(buff, len, count_active_paths(mpp));
> +	return snprint_int(buff, count_active_paths(mpp));
>  }
>  
>  static int
> -snprint_dm_map_state (char * buff, size_t len, const struct multipath * mpp)
> +snprint_dm_map_state (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	if (mpp->dmi && mpp->dmi->suspended)
> -		return snprintf(buff, len, "suspend");
> +		return append_strbuf_str(buff, "suspend");
>  	else
> -		return snprintf(buff, len, "active");
> +		return append_strbuf_str(buff, "active");
>  }
>  
>  static int
> -snprint_multipath_size (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_size (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_size(buff, len, mpp->size);
> +	return snprint_size(buff, mpp->size);
>  }
>  
>  static int
> -snprint_features (char * buff, size_t len, const struct multipath * mpp)
> +snprint_features (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_str(buff, len, mpp->features);
> +	return snprint_str(buff, mpp->features);
>  }
>  
>  static int
> -snprint_hwhandler (char * buff, size_t len, const struct multipath * mpp)
> +snprint_hwhandler (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_str(buff, len, mpp->hwhandler);
> +	return snprint_str(buff, mpp->hwhandler);
>  }
>  
>  static int
> -snprint_path_faults (char * buff, size_t len, const struct multipath * mpp)
> +snprint_path_faults (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_path_failures);
> +	return snprint_uint(buff, mpp->stat_path_failures);
>  }
>  
>  static int
> -snprint_switch_grp (char * buff, size_t len, const struct multipath * mpp)
> +snprint_switch_grp (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_switchgroup);
> +	return snprint_uint(buff, mpp->stat_switchgroup);
>  }
>  
>  static int
> -snprint_map_loads (char * buff, size_t len, const struct multipath * mpp)
> +snprint_map_loads (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_map_loads);
> +	return snprint_uint(buff, mpp->stat_map_loads);
>  }
>  
>  static int
> -snprint_total_q_time (char * buff, size_t len, const struct multipath * mpp)
> +snprint_total_q_time (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_total_queueing_time);
> +	return snprint_uint(buff, mpp->stat_total_queueing_time);
>  }
>  
>  static int
> -snprint_q_timeouts (char * buff, size_t len, const struct multipath * mpp)
> +snprint_q_timeouts (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_queueing_timeouts);
> +	return snprint_uint(buff, mpp->stat_queueing_timeouts);
>  }
>  
>  static int
> -snprint_map_failures (char * buff, size_t len, const struct multipath * mpp)
> +snprint_map_failures (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_uint(buff, len, mpp->stat_map_failures);
> +	return snprint_uint(buff, mpp->stat_map_failures);
>  }
>  
>  static int
> -snprint_multipath_uuid (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_uuid (struct strbuf *buff, const struct multipath * mpp)
>  {
> -	return snprint_str(buff, len, mpp->wwid);
> +	return snprint_str(buff, mpp->wwid);
>  }
>  
>  static int
> -snprint_multipath_vpr (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_vpr (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	struct pathgroup * pgp;
>  	struct path * pp;
> @@ -280,16 +247,16 @@ snprint_multipath_vpr (char * buff, size_t len, const struct multipath * mpp)
>  	vector_foreach_slot(mpp->pg, pgp, i) {
>  		vector_foreach_slot(pgp->paths, pp, j) {
>  			if (strlen(pp->vendor_id) && strlen(pp->product_id))
> -				return snprintf(buff, len, "%s,%s",
> -						pp->vendor_id, pp->product_id);
> +				return print_strbuf(buff, "%s,%s",
> +						    pp->vendor_id, pp->product_id);
>  		}
>  	}
> -	return snprintf(buff, len, "##,##");
> +	return append_strbuf_str(buff, "##,##");
>  }
>  
>  
>  static int
> -snprint_multipath_vend (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_vend (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	struct pathgroup * pgp;
>  	struct path * pp;
> @@ -298,14 +265,14 @@ snprint_multipath_vend (char * buff, size_t len, const struct multipath * mpp)
>  	vector_foreach_slot(mpp->pg, pgp, i) {
>  		vector_foreach_slot(pgp->paths, pp, j) {
>  			if (strlen(pp->vendor_id))
> -				return snprintf(buff, len, "%s", pp->vendor_id);
> +				return append_strbuf_str(buff, pp->vendor_id);
>  		}
>  	}
> -	return snprintf(buff, len, "##");
> +	return append_strbuf_str(buff, "##");
>  }
>  
>  static int
> -snprint_multipath_prod (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_prod (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	struct pathgroup * pgp;
>  	struct path * pp;
> @@ -314,14 +281,14 @@ snprint_multipath_prod (char * buff, size_t len, const struct multipath * mpp)
>  	vector_foreach_slot(mpp->pg, pgp, i) {
>  		vector_foreach_slot(pgp->paths, pp, j) {
>  			if (strlen(pp->product_id))
> -				return snprintf(buff, len, "%s", pp->product_id);
> +				return append_strbuf_str(buff, pp->product_id);
>  		}
>  	}
> -	return snprintf(buff, len, "##");
> +	return append_strbuf_str(buff, "##");
>  }
>  
>  static int
> -snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp)
> +snprint_multipath_rev (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	struct pathgroup * pgp;
>  	struct path * pp;
> @@ -330,40 +297,40 @@ snprint_multipath_rev (char * buff, size_t len, const struct multipath * mpp)
>  	vector_foreach_slot(mpp->pg, pgp, i) {
>  		vector_foreach_slot(pgp->paths, pp, j) {
>  			if (strlen(pp->rev))
> -				return snprintf(buff, len, "%s", pp->rev);
> +				return append_strbuf_str(buff, pp->rev);
>  		}
>  	}
> -	return snprintf(buff, len, "##");
> +	return append_strbuf_str(buff, "##");
>  }
>  
>  static int
> -snprint_multipath_foreign (char * buff, size_t len,
> +snprint_multipath_foreign (struct strbuf *buff,
>  			   __attribute__((unused)) const struct multipath * pp)
>  {
> -	return snprintf(buff, len, "%s", "--");
> +	return append_strbuf_str(buff, "--");
>  }
>  
>  static int
> -snprint_action (char * buff, size_t len, const struct multipath * mpp)
> +snprint_action (struct strbuf *buff, const struct multipath * mpp)
>  {
>  	switch (mpp->action) {
>  	case ACT_REJECT:
> -		return snprint_str(buff, len, ACT_REJECT_STR);
> +		return snprint_str(buff, ACT_REJECT_STR);
>  	case ACT_RENAME:
> -		return snprint_str(buff, len, ACT_RENAME_STR);
> +		return snprint_str(buff, ACT_RENAME_STR);
>  	case ACT_RELOAD:
> -		return snprint_str(buff, len, ACT_RELOAD_STR);
> +		return snprint_str(buff, ACT_RELOAD_STR);
>  	case ACT_CREATE:
> -		return snprint_str(buff, len, ACT_CREATE_STR);
> +		return snprint_str(buff, ACT_CREATE_STR);
>  	case ACT_SWITCHPG:
> -		return snprint_str(buff, len, ACT_SWITCHPG_STR);
> +		return snprint_str(buff, ACT_SWITCHPG_STR);
>  	default:
>  		return 0;
>  	}
>  }
>  
>  static int
> -snprint_multipath_vpd_data(char * buff, size_t len,
> +snprint_multipath_vpd_data(struct strbuf *buff,
>  			   const struct multipath * mpp)
>  {
>  	struct pathgroup * pgp;
> @@ -373,26 +340,26 @@ snprint_multipath_vpd_data(char * buff, size_t len,
>  	vector_foreach_slot(mpp->pg, pgp, i)
>  		vector_foreach_slot(pgp->paths, pp, j)
>  			if (pp->vpd_data)
> -				return snprintf(buff, len, "%s", pp->vpd_data);
> -	return snprintf(buff, len, "[undef]");
> +				return append_strbuf_str(buff, pp->vpd_data);
> +	return append_strbuf_str(buff, "[undef]");
>  }
>  
>  /*
>   * path info printing functions
>   */
>  static int
> -snprint_path_uuid (char * buff, size_t len, const struct path * pp)
> +snprint_path_uuid (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_str(buff, len, pp->wwid);
> +	return snprint_str(buff, pp->wwid);
>  }
>  
>  static int
> -snprint_hcil (char * buff, size_t len, const struct path * pp)
> +snprint_hcil (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || pp->sg_id.host_no < 0)
> -		return snprintf(buff, len, "#:#:#:#");
> +		return append_strbuf_str(buff, "#:#:#:#");
>  
> -	return snprintf(buff, len, "%i:%i:%i:%" PRIu64,
> +	return print_strbuf(buff, "%i:%i:%i:%" PRIu64,
>  			pp->sg_id.host_no,
>  			pp->sg_id.channel,
>  			pp->sg_id.scsi_id,
> @@ -400,159 +367,158 @@ snprint_hcil (char * buff, size_t len, const struct path * pp)
>  }
>  
>  static int
> -snprint_dev (char * buff, size_t len, const struct path * pp)
> +snprint_dev (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || !strlen(pp->dev))
> -		return snprintf(buff, len, "-");
> +		return append_strbuf_str(buff, "-");
>  	else
> -		return snprint_str(buff, len, pp->dev);
> +		return snprint_str(buff, pp->dev);
>  }
>  
>  static int
> -snprint_dev_t (char * buff, size_t len, const struct path * pp)
> +snprint_dev_t (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || !strlen(pp->dev))
> -		return snprintf(buff, len, "#:#");
> +		return append_strbuf_str(buff, "#:#");
>  	else
> -		return snprint_str(buff, len, pp->dev_t);
> +		return snprint_str(buff, pp->dev_t);
>  }
>  
>  static int
> -snprint_offline (char * buff, size_t len, const struct path * pp)
> +snprint_offline (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || !pp->mpp)
> -		return snprintf(buff, len, "unknown");
> +		return append_strbuf_str(buff, "unknown");
>  	else if (pp->offline)
> -		return snprintf(buff, len, "offline");
> +		return append_strbuf_str(buff, "offline");
>  	else
> -		return snprintf(buff, len, "running");
> +		return append_strbuf_str(buff, "running");
>  }
>  
>  static int
> -snprint_chk_state (char * buff, size_t len, const struct path * pp)
> +snprint_chk_state (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || !pp->mpp)
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  
>  	switch (pp->state) {
>  	case PATH_UP:
> -		return snprintf(buff, len, "ready");
> +		return append_strbuf_str(buff, "ready");
>  	case PATH_DOWN:
> -		return snprintf(buff, len, "faulty");
> +		return append_strbuf_str(buff, "faulty");
>  	case PATH_SHAKY:
> -		return snprintf(buff, len, "shaky");
> +		return append_strbuf_str(buff, "shaky");
>  	case PATH_GHOST:
> -		return snprintf(buff, len, "ghost");
> +		return append_strbuf_str(buff, "ghost");
>  	case PATH_PENDING:
> -		return snprintf(buff, len, "i/o pending");
> +		return append_strbuf_str(buff, "i/o pending");
>  	case PATH_TIMEOUT:
> -		return snprintf(buff, len, "i/o timeout");
> +		return append_strbuf_str(buff, "i/o timeout");
>  	case PATH_DELAYED:
> -		return snprintf(buff, len, "delayed");
> +		return append_strbuf_str(buff, "delayed");
>  	default:
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  	}
>  }
>  
>  static int
> -snprint_dm_path_state (char * buff, size_t len, const struct path * pp)
> +snprint_dm_path_state (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp)
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  
>  	switch (pp->dmstate) {
>  	case PSTATE_ACTIVE:
> -		return snprintf(buff, len, "active");
> +		return append_strbuf_str(buff, "active");
>  	case PSTATE_FAILED:
> -		return snprintf(buff, len, "failed");
> +		return append_strbuf_str(buff, "failed");
>  	default:
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  	}
>  }
>  
>  static int
> -snprint_vpr (char * buff, size_t len, const struct path * pp)
> +snprint_vpr (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprintf(buff, len, "%s,%s",
> -			pp->vendor_id, pp->product_id);
> +	return print_strbuf(buff, "%s,%s", pp->vendor_id, pp->product_id);
>  }
>  
>  static int
> -snprint_next_check (char * buff, size_t len, const struct path * pp)
> +snprint_next_check (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp || !pp->mpp)
> -		return snprintf(buff, len, "orphan");
> +		return append_strbuf_str(buff, "orphan");
>  
> -	return snprint_progress(buff, len, pp->tick, pp->checkint);
> +	return snprint_progress(buff, pp->tick, pp->checkint);
>  }
>  
>  static int
> -snprint_pri (char * buff, size_t len, const struct path * pp)
> +snprint_pri (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_int(buff, len, pp ? pp->priority : -1);
> +	return snprint_int(buff, pp ? pp->priority : -1);
>  }
>  
>  static int
> -snprint_pg_selector (char * buff, size_t len, const struct pathgroup * pgp)
> +snprint_pg_selector (struct strbuf *buff, const struct pathgroup * pgp)
>  {
>  	const char *s = pgp->mpp->selector;
>  
> -	return snprint_str(buff, len, s ? s : "");
> +	return snprint_str(buff, s ? s : "");
>  }
>  
>  static int
> -snprint_pg_pri (char * buff, size_t len, const struct pathgroup * pgp)
> +snprint_pg_pri (struct strbuf *buff, const struct pathgroup * pgp)
>  {
> -	return snprint_int(buff, len, pgp->priority);
> +	return snprint_int(buff, pgp->priority);
>  }
>  
>  static int
> -snprint_pg_state (char * buff, size_t len, const struct pathgroup * pgp)
> +snprint_pg_state (struct strbuf *buff, const struct pathgroup * pgp)
>  {
>  	switch (pgp->status) {
>  	case PGSTATE_ENABLED:
> -		return snprintf(buff, len, "enabled");
> +		return append_strbuf_str(buff, "enabled");
>  	case PGSTATE_DISABLED:
> -		return snprintf(buff, len, "disabled");
> +		return append_strbuf_str(buff, "disabled");
>  	case PGSTATE_ACTIVE:
> -		return snprintf(buff, len, "active");
> +		return append_strbuf_str(buff, "active");
>  	default:
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  	}
>  }
>  
>  static int
> -snprint_pg_marginal (char * buff, size_t len, const struct pathgroup * pgp)
> +snprint_pg_marginal (struct strbuf *buff, const struct pathgroup * pgp)
>  {
>  	if (pgp->marginal)
> -		return snprintf(buff, len, "marginal");
> -	return snprintf(buff, len, "normal");
> +		return append_strbuf_str(buff, "marginal");
> +	return append_strbuf_str(buff, "normal");
>  }
>  
>  static int
> -snprint_path_size (char * buff, size_t len, const struct path * pp)
> +snprint_path_size (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_size(buff, len, pp->size);
> +	return snprint_size(buff, pp->size);
>  }
>  
>  int
> -snprint_path_serial (char * buff, size_t len, const struct path * pp)
> +snprint_path_serial (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_str(buff, len, pp->serial);
> +	return snprint_str(buff, pp->serial);
>  }
>  
>  static int
> -snprint_path_mpp (char * buff, size_t len, const struct path * pp)
> +snprint_path_mpp (struct strbuf *buff, const struct path * pp)
>  {
>  	if (!pp->mpp)
> -		return snprintf(buff, len, "[orphan]");
> +		return append_strbuf_str(buff, "[orphan]");
>  	if (!pp->mpp->alias)
> -		return snprintf(buff, len, "[unknown]");
> -	return snprint_str(buff, len, pp->mpp->alias);
> +		return append_strbuf_str(buff, "[unknown]");
> +	return snprint_str(buff, pp->mpp->alias);
>  }
>  
>  static int
> -snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
> +snprint_host_attr (struct strbuf *buff, const struct path * pp, char *attr)
>  {
>  	struct udev_device *host_dev = NULL;
>  	char host_id[32];
> @@ -560,7 +526,7 @@ snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
>  	int ret;
>  
>  	if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP)
> -		return snprintf(buff, len, "[undef]");
> +		return append_strbuf_str(buff, "[undef]");
>  	sprintf(host_id, "host%d", pp->sg_id.host_no);
>  	host_dev = udev_device_new_from_subsystem_sysname(udev, "fc_host",
>  							  host_id);
> @@ -570,28 +536,28 @@ snprint_host_attr (char * buff, size_t len, const struct path * pp, char *attr)
>  	}
>  	value = udev_device_get_sysattr_value(host_dev, attr);
>  	if (value)
> -		ret = snprint_str(buff, len, value);
> +		ret = snprint_str(buff, value);
>  	udev_device_unref(host_dev);
>  out:
>  	if (!value)
> -		ret = snprintf(buff, len, "[unknown]");
> +		ret = append_strbuf_str(buff, "[unknown]");
>  	return ret;
>  }
>  
>  int
> -snprint_host_wwnn (char * buff, size_t len, const struct path * pp)
> +snprint_host_wwnn (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_host_attr(buff, len, pp, "node_name");
> +	return snprint_host_attr(buff, pp, "node_name");
>  }
>  
>  int
> -snprint_host_wwpn (char * buff, size_t len, const struct path * pp)
> +snprint_host_wwpn (struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_host_attr(buff, len, pp, "port_name");
> +	return snprint_host_attr(buff, pp, "port_name");
>  }
>  
>  int
> -snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
> +snprint_tgt_wwpn (struct strbuf *buff, const struct path * pp)
>  {
>  	struct udev_device *rport_dev = NULL;
>  	char rport_id[42];
> @@ -599,7 +565,7 @@ snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
>  	int ret;
>  
>  	if (pp->sg_id.proto_id != SCSI_PROTOCOL_FCP)
> -		return snprintf(buff, len, "[undef]");
> +		return append_strbuf_str(buff, "[undef]");
>  	sprintf(rport_id, "rport-%d:%d-%d",
>  		pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id);
>  	rport_dev = udev_device_new_from_subsystem_sysname(udev,
> @@ -611,111 +577,111 @@ snprint_tgt_wwpn (char * buff, size_t len, const struct path * pp)
>  	}
>  	value = udev_device_get_sysattr_value(rport_dev, "port_name");
>  	if (value)
> -		ret = snprint_str(buff, len, value);
> +		ret = snprint_str(buff, value);
>  	udev_device_unref(rport_dev);
>  out:
>  	if (!value)
> -		ret = snprintf(buff, len, "[unknown]");
> +		ret = append_strbuf_str(buff, "[unknown]");
>  	return ret;
>  }
>  
>  
>  int
> -snprint_tgt_wwnn (char * buff, size_t len, const struct path * pp)
> +snprint_tgt_wwnn (struct strbuf *buff, const struct path * pp)
>  {
>  	if (pp->tgt_node_name[0] == '\0')
> -		return snprintf(buff, len, "[undef]");
> -	return snprint_str(buff, len, pp->tgt_node_name);
> +		return append_strbuf_str(buff, "[undef]");
> +	return snprint_str(buff, pp->tgt_node_name);
>  }
>  
>  static int
> -snprint_host_adapter (char * buff, size_t len, const struct path * pp)
> +snprint_host_adapter (struct strbuf *buff, const struct path * pp)
>  {
>  	char adapter[SLOT_NAME_SIZE];
>  
>  	if (sysfs_get_host_adapter_name(pp, adapter))
> -		return snprintf(buff, len, "[undef]");
> -	return snprint_str(buff, len, adapter);
> +		return append_strbuf_str(buff, "[undef]");
> +	return snprint_str(buff, adapter);
>  }
>  
>  static int
> -snprint_path_checker (char * buff, size_t len, const struct path * pp)
> +snprint_path_checker (struct strbuf *buff, const struct path * pp)
>  {
>  	const struct checker * c = &pp->checker;
> -	return snprint_str(buff, len, checker_name(c));
> +	return snprint_str(buff, checker_name(c));
>  }
>  
>  static int
> -snprint_path_foreign (char * buff, size_t len,
> +snprint_path_foreign (struct strbuf *buff,
>  		      __attribute__((unused)) const struct path * pp)
>  {
> -	return snprintf(buff, len, "%s", "--");
> +	return append_strbuf_str(buff, "--");
>  }
>  
>  static int
> -snprint_path_failures(char * buff, size_t len, const struct path * pp)
> +snprint_path_failures(struct strbuf *buff, const struct path * pp)
>  {
> -	return snprint_int(buff, len, pp->failcount);
> +	return snprint_int(buff, pp->failcount);
>  }
>  
>  /* if you add a protocol string bigger than "scsi:unspec" you must
>   * also change PROTOCOL_BUF_SIZE */
>  int
> -snprint_path_protocol(char * buff, size_t len, const struct path * pp)
> +snprint_path_protocol(struct strbuf *buff, const struct path * pp)
>  {
>  	switch (pp->bus) {
>  	case SYSFS_BUS_SCSI:
>  		switch (pp->sg_id.proto_id) {
>  		case SCSI_PROTOCOL_FCP:
> -			return snprintf(buff, len, "scsi:fcp");
> +			return append_strbuf_str(buff, "scsi:fcp");
>  		case SCSI_PROTOCOL_SPI:
> -			return snprintf(buff, len, "scsi:spi");
> +			return append_strbuf_str(buff, "scsi:spi");
>  		case SCSI_PROTOCOL_SSA:
> -			return snprintf(buff, len, "scsi:ssa");
> +			return append_strbuf_str(buff, "scsi:ssa");
>  		case SCSI_PROTOCOL_SBP:
> -			return snprintf(buff, len, "scsi:sbp");
> +			return append_strbuf_str(buff, "scsi:sbp");
>  		case SCSI_PROTOCOL_SRP:
> -			return snprintf(buff, len, "scsi:srp");
> +			return append_strbuf_str(buff, "scsi:srp");
>  		case SCSI_PROTOCOL_ISCSI:
> -			return snprintf(buff, len, "scsi:iscsi");
> +			return append_strbuf_str(buff, "scsi:iscsi");
>  		case SCSI_PROTOCOL_SAS:
> -			return snprintf(buff, len, "scsi:sas");
> +			return append_strbuf_str(buff, "scsi:sas");
>  		case SCSI_PROTOCOL_ADT:
> -			return snprintf(buff, len, "scsi:adt");
> +			return append_strbuf_str(buff, "scsi:adt");
>  		case SCSI_PROTOCOL_ATA:
> -			return snprintf(buff, len, "scsi:ata");
> +			return append_strbuf_str(buff, "scsi:ata");
>  		case SCSI_PROTOCOL_USB:
> -			return snprintf(buff, len, "scsi:usb");
> +			return append_strbuf_str(buff, "scsi:usb");
>  		case SCSI_PROTOCOL_UNSPEC:
>  		default:
> -			return snprintf(buff, len, "scsi:unspec");
> +			return append_strbuf_str(buff, "scsi:unspec");
>  		}
>  	case SYSFS_BUS_CCW:
> -		return snprintf(buff, len, "ccw");
> +		return append_strbuf_str(buff, "ccw");
>  	case SYSFS_BUS_CCISS:
> -		return snprintf(buff, len, "cciss");
> +		return append_strbuf_str(buff, "cciss");
>  	case SYSFS_BUS_NVME:
> -		return snprintf(buff, len, "nvme");
> +		return append_strbuf_str(buff, "nvme");
>  	case SYSFS_BUS_UNDEF:
>  	default:
> -		return snprintf(buff, len, "undef");
> +		return append_strbuf_str(buff, "undef");
>  	}
>  }
>  
>  int
> -snprint_path_marginal(char * buff, size_t len, const struct path * pp)
> +snprint_path_marginal(struct strbuf *buff, const struct path * pp)
>  {
>  	if (pp->marginal)
> -		return snprintf(buff, len, "marginal");
> -	return snprintf(buff, len, "normal");
> +		return append_strbuf_str(buff, "marginal");
> +	return append_strbuf_str(buff, "normal");
>  }
>  
>  static int
> -snprint_path_vpd_data(char * buff, size_t len, const struct path * pp)
> +snprint_path_vpd_data(struct strbuf *buff, const struct path * pp)
>  {
>  	if (pp->vpd_data)
> -		return snprintf(buff, len, "%s", pp->vpd_data);
> -	return snprintf(buff, len, "[undef]");
> +		return append_strbuf_str(buff, pp->vpd_data);
> +	return append_strbuf_str(buff, "[undef]");
>  }
>  
>  struct multipath_data mpd[] = {
> @@ -782,24 +748,33 @@ struct pathgroup_data pgd[] = {
>  	{0, NULL, 0 , NULL}
>  };
>  
> -int
> -snprint_wildcards (char * buff, int len)
> +int snprint_wildcards(struct strbuf *buff)
>  {
> -	int i, fwd = 0;
> +	int initial_len = get_strbuf_len(buff);
> +	int i, rc;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "multipath format wildcards:\n");
> +	if ((rc = append_strbuf_str(buff, "multipath format wildcards:\n")) < 0)
> +		return rc;
>  	for (i = 0; mpd[i].header; i++)
> -		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
> -				mpd[i].wildcard, mpd[i].header);
> -	fwd += snprintf(buff + fwd, len - fwd, "\npath format wildcards:\n");
> +		if ((rc = print_strbuf(buff, "%%%c  %s\n",
> +				       mpd[i].wildcard, mpd[i].header)) < 0)
> +			return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "\npath format wildcards:\n")) < 0)
> +		return rc;
>  	for (i = 0; pd[i].header; i++)
> -		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
> -				pd[i].wildcard, pd[i].header);
> -	fwd += snprintf(buff + fwd, len - fwd, "\npathgroup format wildcards:\n");
> +		if ((rc = print_strbuf(buff, "%%%c  %s\n",
> +				       pd[i].wildcard, pd[i].header)) < 0)
> +			return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "\npathgroup format wildcards:\n")) < 0)
> +		return rc;
>  	for (i = 0; pgd[i].header; i++)
> -		fwd += snprintf(buff + fwd, len - fwd, "%%%c  %s\n",
> -				pgd[i].wildcard, pgd[i].header);
> -	return fwd;
> +		if ((rc = print_strbuf(buff, "%%%c  %s\n",
> +				       pgd[i].wildcard, pgd[i].header)) < 0)
> +			return rc;
> +
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  void
> @@ -832,10 +807,10 @@ void
>  _get_path_layout (const struct _vector *gpvec, enum layout_reset reset)
>  {
>  	int i, j;
> -	char buff[MAX_FIELD_LEN];
>  	const struct gen_path *gp;
>  
>  	for (j = 0; pd[j].header; j++) {
> +		STRBUF_ON_STACK(buff);
>  
>  		reset_width(&pd[j].width, reset, pd[j].header);
>  
> @@ -843,9 +818,9 @@ _get_path_layout (const struct _vector *gpvec, enum layout_reset reset)
>  			continue;
>  
>  		vector_foreach_slot (gpvec, gp, i) {
> -			gp->ops->snprint(gp, buff, MAX_FIELD_LEN,
> -					 pd[j].wildcard);
> -			pd[j].width = MAX(pd[j].width, strlen(buff));
> +			gp->ops->snprint(gp, &buff, pd[j].wildcard);
> +			pd[j].width = MAX(pd[j].width, get_strbuf_len(&buff));
> +			truncate_strbuf(&buff, 0);
>  		}
>  	}
>  }
> @@ -873,10 +848,10 @@ _get_multipath_layout (const struct _vector *gmvec,
>  			    enum layout_reset reset)
>  {
>  	int i, j;
> -	char buff[MAX_FIELD_LEN];
>  	const struct gen_multipath * gm;
>  
>  	for (j = 0; mpd[j].header; j++) {
> +		STRBUF_ON_STACK(buff);
>  
>  		reset_width(&mpd[j].width, reset, mpd[j].header);
>  
> @@ -884,9 +859,9 @@ _get_multipath_layout (const struct _vector *gmvec,
>  			continue;
>  
>  		vector_foreach_slot (gmvec, gm, i) {
> -			gm->ops->snprint(gm, buff, MAX_FIELD_LEN,
> -					 mpd[j].wildcard);
> -			mpd[j].width = MAX(mpd[j].width, strlen(buff));
> +			gm->ops->snprint(gm, &buff, mpd[j].wildcard);
> +			mpd[j].width = MAX(mpd[j].width, get_strbuf_len(&buff));
> +			truncate_strbuf(&buff, 0);
>  		}
>  		condlog(4, "%s: width %d", mpd[j].header, mpd[j].width);
>  	}
> @@ -905,14 +880,14 @@ mpd_lookup(char wildcard)
>  }
>  
>  int snprint_multipath_attr(const struct gen_multipath* gm,
> -			   char *buf, int len, char wildcard)
> +			   struct strbuf *buf, char wildcard)
>  {
>  	const struct multipath *mpp = gen_multipath_to_dm(gm);
>  	struct multipath_data *mpd = mpd_lookup(wildcard);
>  
>  	if (mpd == NULL)
>  		return 0;
> -	return mpd->snprint(buf, len, mpp);
> +	return mpd->snprint(buf, mpp);
>  }
>  
>  static struct path_data *
> @@ -928,14 +903,14 @@ pd_lookup(char wildcard)
>  }
>  
>  int snprint_path_attr(const struct gen_path* gp,
> -		      char *buf, int len, char wildcard)
> +		      struct strbuf *buf, char wildcard)
>  {
>  	const struct path *pp = gen_path_to_dm(gp);
>  	struct path_data *pd = pd_lookup(wildcard);
>  
>  	if (pd == NULL)
>  		return 0;
> -	return pd->snprint(buf, len, pp);
> +	return pd->snprint(buf, pp);
>  }
>  
>  static struct pathgroup_data *
> @@ -951,220 +926,168 @@ pgd_lookup(char wildcard)
>  }
>  
>  int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
> -			   char *buf, int len, char wildcard)
> +			   struct strbuf *buf, char wildcard)
>  {
>  	const struct pathgroup *pg = gen_pathgroup_to_dm(gpg);
>  	struct pathgroup_data *pdg = pgd_lookup(wildcard);
>  
>  	if (pdg == NULL)
>  		return 0;
> -	return pdg->snprint(buf, len, pg);
> +	return pdg->snprint(buf, pg);
>  }
>  
> -int
> -snprint_multipath_header (char * line, int len, const char * format)
> +int snprint_multipath_header(struct strbuf *line, const char *format)
>  {
> -	char * c = line;   /* line cursor */
> -	char * s = line;   /* for padding */
> -	const char * f = format; /* format string cursor */
> -	int fwd;
> +	int initial_len = get_strbuf_len(line);
> +	const char *f;
>  	struct multipath_data * data;
> +	int rc;
>  
> -	do {
> -		if (TAIL <= 0)
> -			break;
> +	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
> +		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
> +			return rc;
>  
> -		if (*f != '%') {
> -			*c++ = *f;
> -			NOPAD;
> -			continue;
> -		}
> -		f++;
> -
> -		if (!(data = mpd_lookup(*f)))
> +		format = f + 1;
> +		if (!(data = mpd_lookup(*format)))
>  			continue; /* unknown wildcard */
>  
> -		PRINT(c, TAIL, "%s", data->header);
> -		PAD(data->width);
> -	} while (*f++);
> +		if ((rc = append_strbuf_str(line, data->header)) < 0)
> +			return rc;
> +		else if ((unsigned int)rc < data->width)
> +			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
> +				return rc;
> +	}
>  
> -	__endline(line, len, c);
> -	return (c - line);
> +	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
> +		return rc;
> +	return get_strbuf_len(line) - initial_len;
>  }
>  
> -int
> -_snprint_multipath (const struct gen_multipath * gmp,
> -		    char * line, int len, const char * format, int pad)
> +int _snprint_multipath(const struct gen_multipath *gmp,
> +		       struct strbuf *line, const char *format, int pad)
>  {
> -	char * c = line;   /* line cursor */
> -	char * s = line;   /* for padding */
> -	const char * f = format; /* format string cursor */
> -	int fwd;
> +	int initial_len = get_strbuf_len(line);
> +	const char *f;
>  	struct multipath_data * data;
> -	char buff[MAX_FIELD_LEN] = {};
> +	int rc;
>  
> -	do {
> -		if (TAIL <= 0)
> -			break;
> +	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
> +		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
> +			return rc;
>  
> -		if (*f != '%') {
> -			*c++ = *f;
> -			NOPAD;
> -			continue;
> -		}
> -		f++;
> -
> -		if (!(data = mpd_lookup(*f)))
> -			continue;
> +		format = f + 1;
> +		if (!(data = mpd_lookup(*format)))
> +			continue; /* unknown wildcard */
>  
> -		gmp->ops->snprint(gmp, buff, MAX_FIELD_LEN, *f);
> -		PRINT(c, TAIL, "%s", buff);
> -		if (pad)
> -			PAD(data->width);
> -		buff[0] = '\0';
> -	} while (*f++);
> +		if ((rc = gmp->ops->snprint(gmp, line, *format)) < 0)
> +			return rc;
> +		else if (pad && (unsigned int)rc < data->width)
> +			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
> +				return rc;
> +	}
>  
> -	__endline(line, len, c);
> -	return (c - line);
> +	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
> +		return rc;
> +	return get_strbuf_len(line) - initial_len;
>  }
>  
> -int
> -snprint_path_header (char * line, int len, const char * format)
> +int snprint_path_header(struct strbuf *line, const char *format)
>  {
> -	char * c = line;   /* line cursor */
> -	char * s = line;   /* for padding */
> -	const char * f = format; /* format string cursor */
> -	int fwd;
> -	struct path_data * data;
> -
> -	do {
> -		if (TAIL <= 0)
> -			break;
> +	int initial_len = get_strbuf_len(line);
> +	const char *f;
> +	struct path_data *data;
> +	int rc;
>  
> -		if (*f != '%') {
> -			*c++ = *f;
> -			NOPAD;
> -			continue;
> -		}
> -		f++;
> +	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
> +		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
> +			return rc;
>  
> -		if (!(data = pd_lookup(*f)))
> +		format = f + 1;
> +		if (!(data = pd_lookup(*format)))
>  			continue; /* unknown wildcard */
>  
> -		PRINT(c, TAIL, "%s", data->header);
> -		PAD(data->width);
> -	} while (*f++);
> +		if ((rc = append_strbuf_str(line, data->header)) < 0)
> +			return rc;
> +		else if ((unsigned int)rc < data->width)
> +			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
> +				return rc;
> +	}
>  
> -	__endline(line, len, c);
> -	return (c - line);
> +	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
> +		return rc;
> +	return get_strbuf_len(line) - initial_len;
>  }
>  
> -int
> -_snprint_path (const struct gen_path * gp, char * line, int len,
> -	       const char * format, int pad)
> +int _snprint_path(const struct gen_path *gp, struct strbuf *line,
> +		  const char *format, int pad)
>  {
> -	char * c = line;   /* line cursor */
> -	char * s = line;   /* for padding */
> -	const char * f = format; /* format string cursor */
> -	int fwd;
> +	int initial_len = get_strbuf_len(line);
> +	const char *f;
>  	struct path_data * data;
> -	char buff[MAX_FIELD_LEN];
> +	int rc;
>  
> -	do {
> -		if (TAIL <= 0)
> -			break;
> -
> -		if (*f != '%') {
> -			*c++ = *f;
> -			NOPAD;
> -			continue;
> -		}
> -		f++;
> +	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
> +		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
> +			return rc;
>  
> -		if (!(data = pd_lookup(*f)))
> -			continue;
> +		format = f + 1;
> +		if (!(data = pd_lookup(*format)))
> +			continue; /* unknown wildcard */
>  
> -		gp->ops->snprint(gp, buff, MAX_FIELD_LEN, *f);
> -		PRINT(c, TAIL, "%s", buff);
> -		if (pad)
> -			PAD(data->width);
> -	} while (*f++);
> +		if ((rc = gp->ops->snprint(gp, line, *format)) < 0)
> +			return rc;
> +		else if (pad && (unsigned int)rc < data->width)
> +			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
> +				return rc;
> +	}
>  
> -	__endline(line, len, c);
> -	return (c - line);
> +	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
> +		return rc;
> +	return get_strbuf_len(line) - initial_len;
>  }
>  
> -int
> -_snprint_pathgroup (const struct gen_pathgroup * ggp, char * line, int len,
> -		    char * format)
> -{
> -	char * c = line;   /* line cursor */
> -	char * s = line;   /* for padding */
> -	char * f = format; /* format string cursor */
> -	int fwd;
> -	struct pathgroup_data * data;
> -	char buff[MAX_FIELD_LEN];
> -
> -	do {
> -		if (TAIL <= 0)
> -			break;
> +int _snprint_pathgroup(const struct gen_pathgroup *ggp, struct strbuf *line,
> +		       const char *format)
> +{
> +	int initial_len = get_strbuf_len(line);
> +	const char *f;
> +	struct pathgroup_data *data;
> +	int rc;
>  
> -		if (*f != '%') {
> -			*c++ = *f;
> -			NOPAD;
> -			continue;
> -		}
> -		f++;
> +	for (f = strchr(format, '%'); f; f = strchr(++format, '%')) {
> +		if ((rc = __append_strbuf_str(line, format, f - format)) < 0)
> +			return rc;
>  
> -		if (!(data = pgd_lookup(*f)))
> -			continue;
> +		format = f + 1;
> +		if (!(data = pgd_lookup(*format)))
> +			continue; /* unknown wildcard */
>  
> -		ggp->ops->snprint(ggp, buff, MAX_FIELD_LEN, *f);
> -		PRINT(c, TAIL, "%s", buff);
> -		PAD(data->width);
> -	} while (*f++);
> +		if ((rc = ggp->ops->snprint(ggp, line, *format)) < 0)
> +			return rc;
> +		else if ((unsigned int)rc < data->width)
> +			if ((rc = fill_strbuf(line, ' ', data->width - rc)) < 0)
> +				return rc;
> +	}
>  
> -	__endline(line, len, c);
> -	return (c - line);
> +	if ((rc = print_strbuf(line, "%s\n", format)) < 0)
> +		return rc;
> +	return get_strbuf_len(line) - initial_len;
>  }
> -#define snprint_pathgroup(line, len, fmt, pgp) \
> -	_snprint_pathgroup(dm_pathgroup_to_gen(pgp), line, len, fmt)
> +
> +#define snprint_pathgroup(line, fmt, pgp)				\
> +	_snprint_pathgroup(dm_pathgroup_to_gen(pgp), line, fmt)
>  
>  void _print_multipath_topology(const struct gen_multipath *gmp, int verbosity)
>  {
> -	int resize;
> -	char *buff = NULL;
> -	char *old = NULL;
> -	int len, maxlen = MAX_LINE_LEN * MAX_LINES;
> -
> -	buff = MALLOC(maxlen);
> -	do {
> -		if (!buff) {
> -			if (old)
> -				FREE(old);
> -			condlog(0, "couldn't allocate memory for list: %s\n",
> -				strerror(errno));
> -			return;
> -		}
> +	STRBUF_ON_STACK(buff);
>  
> -		len = _snprint_multipath_topology(gmp, buff, maxlen, verbosity);
> -		resize = (len == maxlen - 1);
> -
> -		if (resize) {
> -			maxlen *= 2;
> -			old = buff;
> -			buff = REALLOC(buff, maxlen);
> -		}
> -	} while (resize);
> -	printf("%s", buff);
> -	FREE(buff);
> +	_snprint_multipath_topology(gmp, &buff, verbosity);
> +	printf("%s", get_strbuf_str(&buff));
>  }
>  
> -int
> -snprint_multipath_style(const struct gen_multipath *gmp, char *style, int len,
> -			int verbosity)
> +int snprint_multipath_style(const struct gen_multipath *gmp,
> +			    struct strbuf *style, int verbosity)
>  {
> -	int n;
>  	const struct multipath *mpp = gen_multipath_to_dm(gmp);
>  	bool need_action = (verbosity > 1 &&
>  			    mpp->action != ACT_NOTHING &&
> @@ -1172,268 +1095,203 @@ snprint_multipath_style(const struct gen_multipath *gmp, char *style, int len,
>  			    mpp->action != ACT_IMPOSSIBLE);
>  	bool need_wwid = (strncmp(mpp->alias, mpp->wwid, WWID_SIZE));
>  
> -	n = snprintf(style, len, "%s%s%s%s",
> -		     need_action ? "%A: " : "", "%n",
> -		     need_wwid ? " (%w)" : "", " %d %s");
> -	return MIN(n, len - 1);
> +	return print_strbuf(style, "%s%s%s%s",
> +			    need_action ? "%A: " : "", "%n",
> +			    need_wwid ? " (%w)" : "", " %d %s");
>  }
>  
>  int _snprint_multipath_topology(const struct gen_multipath *gmp,
> -				char *buff, int len, int verbosity)
> +				struct strbuf *buff, int verbosity)
>  {
> -	int j, i, fwd = 0;
> +	int j, i, rc;
>  	const struct _vector *pgvec;
>  	const struct gen_pathgroup *gpg;
> -	char style[64];
> -	char * c = style;
> -	char fmt[64];
> -	char * f;
> +	STRBUF_ON_STACK(style);
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	if (verbosity <= 0)
> -		return fwd;
> +		return 0;
>  
>  	reset_multipath_layout();
>  
>  	if (verbosity == 1)
> -		return _snprint_multipath(gmp, buff, len, "%n", 1);
> -
> -	if(isatty(1))
> -		c += sprintf(c, "%c[%dm", 0x1B, 1); /* bold on */
> +		return _snprint_multipath(gmp, buff, "%n", 1);
>  
> -	c += gmp->ops->style(gmp, c, sizeof(style) - (c - style),
> -			     verbosity);
> -	if(isatty(1))
> -		c += sprintf(c, "%c[%dm", 0x1B, 0); /* bold off */
> +	if(isatty(1) &&
> +	   (rc = print_strbuf(&style, "%c[%dm", 0x1B, 1)) < 0) /* bold on */
> +		return rc;
> +	if ((rc = gmp->ops->style(gmp, &style, verbosity)) < 0)
> +		return rc;
> +	if(isatty(1) &&
> +	   (rc = print_strbuf(&style, "%c[%dm", 0x1B, 0)) < 0) /* bold off */
> +		return rc;
>  
> -	fwd += _snprint_multipath(gmp, buff + fwd, len - fwd, style, 1);
> -	if (fwd >= len)
> -		return len;
> -	fwd += _snprint_multipath(gmp, buff + fwd, len - fwd,
> -				  PRINT_MAP_PROPS, 1);
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = _snprint_multipath(gmp, buff, get_strbuf_str(&style), 1)) < 0
> +	    || (rc = _snprint_multipath(gmp, buff, PRINT_MAP_PROPS, 1)) < 0)
> +		return rc;
>  
>  	pgvec = gmp->ops->get_pathgroups(gmp);
>  	if (pgvec == NULL)
> -		return fwd;
> +		goto out;
>  
>  	vector_foreach_slot (pgvec, gpg, j) {
>  		const struct _vector *pathvec;
>  		struct gen_path *gp;
> +		bool last_group = j + 1 == VECTOR_SIZE(pgvec);
>  
> -		f=fmt;
> -
> -		if (j + 1 < VECTOR_SIZE(pgvec)) {
> -			strcpy(f, "|-+- " PRINT_PG_INDENT);
> -		} else
> -			strcpy(f, "`-+- " PRINT_PG_INDENT);
> -		fwd += _snprint_pathgroup(gpg, buff + fwd, len - fwd, fmt);
> -
> -		if (fwd >= len) {
> -			fwd = len;
> -			break;
> -		}
> +		if ((rc = print_strbuf(buff, "%c-+- ",
> +				       last_group ? '`' : '|')) < 0 ||
> +		    (rc = _snprint_pathgroup(gpg, buff, PRINT_PG_INDENT)) < 0)
> +			return rc;
>  
>  		pathvec = gpg->ops->get_paths(gpg);
>  		if (pathvec == NULL)
>  			continue;
>  
>  		vector_foreach_slot (pathvec, gp, i) {
> -			f=fmt;
> -			if (*f != '|')
> -				*f=' ';
> -			f++;
> -			if (i + 1 < VECTOR_SIZE(pathvec))
> -				strcpy(f, " |- " PRINT_PATH_INDENT);
> -			else
> -				strcpy(f, " `- " PRINT_PATH_INDENT);
> -			fwd += _snprint_path(gp, buff + fwd, len - fwd, fmt, 1);
> -			if (fwd >= len) {
> -				fwd = len;
> -				break;
> -			}
> +			if ((rc = print_strbuf(buff, "%c %c- ",
> +					       last_group ? ' ' : '|',
> +					       i + 1 == VECTOR_SIZE(pathvec) ?
> +					       '`': '|')) < 0 ||
> +			    (rc = _snprint_path(gp, buff,
> +						PRINT_PATH_INDENT, 1)) < 0)
> +				return rc;
>  		}
>  		gpg->ops->rel_paths(gpg, pathvec);
> -
> -		if (fwd == len)
> -			break;
>  	}
> +
>  	gmp->ops->rel_pathgroups(gmp, pgvec);
> -	return fwd;
> +out:
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  
>  static int
> -snprint_json (char * buff, int len, int indent, char *json_str)
> +snprint_json(struct strbuf *buff, int indent, const char *json_str)
>  {
> -	int fwd = 0, i;
> +	int rc;
>  
> -	for (i = 0; i < indent; i++) {
> -		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_INDENT);
> -		if (fwd >= len)
> -			return fwd;
> -	}
> +	if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0)
> +		return rc;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "%s", json_str);
> -	return fwd;
> +	return append_strbuf_str(buff, json_str);
>  }
>  
> -static int
> -snprint_json_header (char * buff, int len)
> +static int snprint_json_header(struct strbuf *buff)
>  {
> -	int fwd = 0;
> -
> -	fwd +=  snprint_json(buff, len, 0, PRINT_JSON_START_ELEM);
> -	if (fwd >= len)
> -		return fwd;
> +	int rc;
>  
> -	fwd +=  snprintf(buff + fwd, len  - fwd, PRINT_JSON_START_VERSION,
> -			PRINT_JSON_MAJOR_VERSION, PRINT_JSON_MINOR_VERSION);
> -	return fwd;
> +	if ((rc = snprint_json(buff, 0, PRINT_JSON_START_ELEM)) < 0)
> +		return rc;
> +	return print_strbuf(buff, PRINT_JSON_START_VERSION,
> +			    PRINT_JSON_MAJOR_VERSION, PRINT_JSON_MINOR_VERSION);
>  }
>  
> -static int
> -snprint_json_elem_footer (char * buff, int len, int indent, int last)
> +static int snprint_json_elem_footer(struct strbuf *buff, int indent, bool last)
>  {
> -	int fwd = 0, i;
> +	int rc;
>  
> -	for (i = 0; i < indent; i++) {
> -		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_INDENT);
> -		if (fwd >= len)
> -			return fwd;
> -	}
> +	if ((rc = fill_strbuf(buff, ' ', indent * PRINT_JSON_INDENT_N)) < 0)
> +		return rc;
>  
> -	if (last == 1)
> -		fwd += snprintf(buff + fwd, len - fwd, "%s", PRINT_JSON_END_LAST_ELEM);
> +	if (last)
> +		return append_strbuf_str(buff, PRINT_JSON_END_LAST_ELEM);
>  	else
> -		fwd += snprintf(buff + fwd, len - fwd, "%s", PRINT_JSON_END_ELEM);
> -	return fwd;
> +		return append_strbuf_str(buff, PRINT_JSON_END_ELEM);
>  }
>  
> -static int
> -snprint_multipath_fields_json (char * buff, int len,
> -		const struct multipath * mpp, int last)
> +static int snprint_multipath_fields_json(struct strbuf *buff,
> +					 const struct multipath *mpp, int last)
>  {
> -	int i, j, fwd = 0;
> +	int i, j, rc;
>  	struct path *pp;
>  	struct pathgroup *pgp;
> +	size_t initial_len = get_strbuf_len(buff);
>  
> -	fwd += snprint_multipath(buff, len, PRINT_JSON_MAP, mpp, 0);
> -	if (fwd >= len)
> -		return fwd;
> -
> -	fwd += snprint_json(buff + fwd, len - fwd, 2, PRINT_JSON_START_GROUPS);
> -	if (fwd >= len)
> -		return fwd;
> +	if ((rc = snprint_multipath(buff, PRINT_JSON_MAP, mpp, 0)) < 0 ||
> +	    (rc = snprint_json(buff, 2, PRINT_JSON_START_GROUPS)) < 0)
> +		return rc;
>  
>  	vector_foreach_slot (mpp->pg, pgp, i) {
>  
> -		fwd += snprint_pathgroup(buff + fwd, len - fwd, PRINT_JSON_GROUP, pgp);
> -		if (fwd >= len)
> -			return fwd;
> -
> -		fwd += snprintf(buff + fwd, len - fwd, PRINT_JSON_GROUP_NUM, i + 1);
> -		if (fwd >= len)
> -			return fwd;
> -
> -		fwd += snprint_json(buff + fwd, len - fwd, 3, PRINT_JSON_START_PATHS);
> -		if (fwd >= len)
> -			return fwd;
> +		if ((rc = snprint_pathgroup(buff, PRINT_JSON_GROUP, pgp)) < 0 ||
> +		    (rc = print_strbuf(buff, PRINT_JSON_GROUP_NUM, i + 1)) < 0 ||
> +		    (rc = snprint_json(buff, 3, PRINT_JSON_START_PATHS)) < 0)
> +			return rc;
>  
>  		vector_foreach_slot (pgp->paths, pp, j) {
> -			fwd += snprint_path(buff + fwd, len - fwd, PRINT_JSON_PATH, pp, 0);
> -			if (fwd >= len)
> -				return fwd;
> -
> -			fwd += snprint_json_elem_footer(buff + fwd,
> -					len - fwd, 3, j + 1 == VECTOR_SIZE(pgp->paths));
> -			if (fwd >= len)
> -				return fwd;
> +			if ((rc = snprint_path(buff, PRINT_JSON_PATH,
> +					       pp, 0)) < 0 ||
> +			    (rc = snprint_json_elem_footer(
> +				    buff, 3,
> +				    j + 1 == VECTOR_SIZE(pgp->paths))) < 0)
> +				return rc;
>  		}
> -		fwd += snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
> -		if (fwd >= len)
> -			return fwd;
> -
> -		fwd +=  snprint_json_elem_footer(buff + fwd,
> -				len - fwd, 2, i + 1 == VECTOR_SIZE(mpp->pg));
> -		if (fwd >= len)
> -			return fwd;
> +		if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
> +		    (rc = snprint_json_elem_footer(
> +			    buff, 2, i + 1 == VECTOR_SIZE(mpp->pg))) < 0)
> +			return rc;
>  	}
>  
> -	fwd += snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
> -	if (fwd >= len)
> -		return fwd;
> +	if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
> +	    (rc = snprint_json_elem_footer(buff, 1, last)) < 0)
> +		return rc;
>  
> -	fwd += snprint_json_elem_footer(buff + fwd, len - fwd, 1, last);
> -	return fwd;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -int
> -snprint_multipath_map_json (char * buff, int len, const struct multipath * mpp)
> +int snprint_multipath_map_json(struct strbuf *buff, const struct multipath * mpp)
>  {
> -	int fwd = 0;
> -
> -	fwd +=  snprint_json_header(buff, len);
> -	if (fwd >= len)
> -		return len;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc;
>  
> -	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_START_MAP);
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = snprint_json_header(buff)) < 0 ||
> +	    (rc = snprint_json(buff, 0, PRINT_JSON_START_MAP)) < 0)
> +		return rc;
>  
> -	fwd += snprint_multipath_fields_json(buff + fwd, len - fwd, mpp, 1);
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = snprint_multipath_fields_json(buff, mpp, 1)) < 0)
> +		return rc;
>  
> -	fwd +=  snprint_json(buff + fwd, len - fwd, 0, "\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = snprint_json(buff, 0, "\n")) < 0 ||
> +	    (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0)
> +		return rc;
>  
> -	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_LAST);
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -int
> -snprint_multipath_topology_json (char * buff, int len, const struct vectors * vecs)
> +int snprint_multipath_topology_json (struct strbuf *buff,
> +				     const struct vectors * vecs)
>  {
> -	int i, fwd = 0;
> +	int i;
>  	struct multipath * mpp;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc;
>  
> -	fwd +=  snprint_json_header(buff, len);
> -	if (fwd >= len)
> -		return len;
> -
> -	fwd +=  snprint_json(buff + fwd, len  - fwd, 1, PRINT_JSON_START_MAPS);
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = snprint_json_header(buff)) < 0 ||
> +	    (rc = snprint_json(buff, 1, PRINT_JSON_START_MAPS)) < 0)
> +		return rc;
>  
>  	vector_foreach_slot(vecs->mpvec, mpp, i) {
> -		fwd += snprint_multipath_fields_json(buff + fwd, len - fwd,
> -				mpp, i + 1 == VECTOR_SIZE(vecs->mpvec));
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_multipath_fields_json(
> +			     buff, mpp, i + 1 == VECTOR_SIZE(vecs->mpvec))) < 0)
> +			return rc;
>  	}
>  
> -	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_ARRAY);
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = snprint_json(buff, 0, PRINT_JSON_END_ARRAY)) < 0 ||
> +	    (rc = snprint_json(buff, 0, PRINT_JSON_END_LAST)) < 0)
> +		return rc;
>  
> -	fwd +=  snprint_json(buff + fwd, len - fwd, 0, PRINT_JSON_END_LAST);
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  static int
>  snprint_hwentry (const struct config *conf,
> -		 char * buff, int len, const struct hwentry * hwe)
> +		 struct strbuf *buff, const struct hwentry * hwe)
>  {
> -	int i;
> -	int fwd = 0;
> +	int i, rc;
>  	struct keyword * kw;
>  	struct keyword * rootkw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "devices");
>  
> @@ -1445,57 +1303,53 @@ snprint_hwentry (const struct config *conf,
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
> +		return rc;
> +
>  	iterate_sub_keywords(rootkw, kw, i) {
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				kw, hwe);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, hwe)) < 0)
> +			return rc;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -static int snprint_hwtable(const struct config *conf,
> -			   char *buff, int len,
> +static int snprint_hwtable(const struct config *conf, struct strbuf *buff,
>  			   const struct _vector *hwtable)
>  {
> -	int fwd = 0;
> -	int i;
> +	int i, rc;
>  	struct hwentry * hwe;
>  	struct keyword * rootkw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "devices");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "devices {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "devices {\n")) < 0)
> +		return rc;
> +
>  	vector_foreach_slot (hwtable, hwe, i) {
> -		fwd += snprint_hwentry(conf, buff + fwd, len - fwd, hwe);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_hwentry(conf, buff, hwe)) < 0)
> +			return rc;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  static int
> -snprint_mpentry (const struct config *conf, char * buff, int len,
> +snprint_mpentry (const struct config *conf, struct strbuf *buff,
>  		 const struct mpentry * mpe, const struct _vector *mpvec)
>  {
> -	int i;
> -	int fwd = 0;
> +	int i, rc;
>  	struct keyword * kw;
>  	struct keyword * rootkw;
>  	struct multipath *mpp = NULL;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	if (mpvec != NULL && (mpp = find_mp_by_wwid(mpvec, mpe->wwid)) == NULL)
>  		return 0;
> @@ -1504,49 +1358,44 @@ snprint_mpentry (const struct config *conf, char * buff, int len,
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "\tmultipath {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "\tmultipath {\n")) < 0)
> +		return rc;
> +
>  	iterate_sub_keywords(rootkw, kw, i) {
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				kw, mpe);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, mpe)) < 0)
> +			return rc;
>  	}
>  	/*
>  	 * This mpp doesn't have alias defined. Add the alias in a comment.
>  	 */
> -	if (mpp != NULL && strcmp(mpp->alias, mpp->wwid)) {
> -		fwd += snprintf(buff + fwd, len - fwd, "\t\t# alias \"%s\"\n",
> -				mpp->alias);
> -		if (fwd >= len)
> -			return len;
> -	}
> -	fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	if (mpp != NULL && strcmp(mpp->alias, mpp->wwid) &&
> +	    (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n", mpp->alias)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
> +		return rc;
> +
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -static int snprint_mptable(const struct config *conf,
> -			   char *buff, int len, const struct _vector *mpvec)
> +static int snprint_mptable(const struct config *conf, struct strbuf *buff,
> +			   const struct _vector *mpvec)
>  {
> -	int fwd = 0;
> -	int i;
> +	int i, rc;
>  	struct mpentry * mpe;
>  	struct keyword * rootkw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "multipaths");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "multipaths {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "multipaths {\n")) < 0)
> +		return rc;
> +
>  	vector_foreach_slot (conf->mptable, mpe, i) {
> -		fwd += snprint_mpentry(conf, buff + fwd, len - fwd, mpe, mpvec);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_mpentry(conf, buff, mpe, mpvec)) < 0)
> +			return rc;
>  	}
>  	if (mpvec != NULL) {
>  		struct multipath *mpp;
> @@ -1555,496 +1404,397 @@ static int snprint_mptable(const struct config *conf,
>  			if (find_mpe(conf->mptable, mpp->wwid) != NULL)
>  				continue;
>  
> -			fwd += snprintf(buff + fwd, len - fwd,
> -					"\tmultipath {\n");
> -			if (fwd >= len)
> -				return len;
> -			fwd += snprintf(buff + fwd, len - fwd,
> -					"\t\twwid \"%s\"\n", mpp->wwid);
> -			if (fwd >= len)
> -				return len;
> +			if ((rc = print_strbuf(buff,
> +					       "\tmultipath {\n\t\twwid \"%s\"\n",
> +					       mpp->wwid)) < 0)
> +				return rc;
>  			/*
>  			 * This mpp doesn't have alias defined in
>  			 * multipath.conf - otherwise find_mpe would have
>  			 * found it. Add the alias in a comment.
>  			 */
> -			if (strcmp(mpp->alias, mpp->wwid)) {
> -				fwd += snprintf(buff + fwd, len - fwd,
> -						"\t\t# alias \"%s\"\n",
> -						mpp->alias);
> -				if (fwd >= len)
> -					return len;
> -			}
> -			fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
> -			if (fwd >= len)
> -				return len;
> +			if (strcmp(mpp->alias, mpp->wwid) &&
> +			    (rc = print_strbuf(buff, "\t\t# alias \"%s\"\n",
> +					       mpp->alias)) < 0)
> +				return rc;
> +			if ((rc = append_strbuf_str(buff, "\t}\n")) < 0)
> +				return rc;
>  		}
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -static int snprint_overrides(const struct config *conf, char * buff, int len,
> +static int snprint_overrides(const struct config *conf, struct strbuf *buff,
>  			     const struct hwentry *overrides)
>  {
> -	int fwd = 0;
> -	int i;
> +	int i, rc;
>  	struct keyword *rootkw;
>  	struct keyword *kw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "overrides");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "overrides {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "overrides {\n")) < 0)
> +		return rc;
>  	if (!overrides)
>  		goto out;
> +
>  	iterate_sub_keywords(rootkw, kw, i) {
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, NULL);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0)
> +			return rc;
>  	}
>  out:
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -static int snprint_defaults(const struct config *conf, char *buff, int len)
> +static int snprint_defaults(const struct config *conf, struct strbuf *buff)
>  {
> -	int fwd = 0;
> -	int i;
> +	int i, rc;
>  	struct keyword *rootkw;
>  	struct keyword *kw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "defaults");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "defaults {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "defaults {\n")) < 0)
> +		return rc;
>  
>  	iterate_sub_keywords(rootkw, kw, i) {
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				kw, NULL);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, NULL)) < 0)
> +			return rc;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -static int
> -snprint_blacklist_group (char *buff, int len, int *fwd, vector *vec)
> +static int snprint_blacklist_group(struct strbuf *buff, vector *vec)
>  {
> -	int threshold = MAX_LINE_LEN;
>  	struct blentry * ble;
> -	int pos;
> -	int i;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc, i;
>  
> -	pos = *fwd;
>  	if (!VECTOR_SIZE(*vec)) {
> -		if ((len - pos - threshold) <= 0)
> -			return 0;
> -		pos += snprintf(buff + pos, len - pos, "        <empty>\n");
> +		if ((rc = append_strbuf_str(buff, "        <empty>\n")) < 0)
> +			return rc;
>  	} else vector_foreach_slot (*vec, ble, i) {
> -		if ((len - pos - threshold) <= 0)
> -			return 0;
> -		if (ble->origin == ORIGIN_CONFIG)
> -			pos += snprintf(buff + pos, len - pos, "        (config file rule) ");
> -		else if (ble->origin == ORIGIN_DEFAULT)
> -			pos += snprintf(buff + pos, len - pos, "        (default rule)     ");
> -		pos += snprintf(buff + pos, len - pos, "%s\n", ble->str);
> +		rc = print_strbuf(buff, "        %s %s\n",
> +				   ble->origin == ORIGIN_CONFIG ?
> +				   "(config file rule)" :
> +				   "(default rule)    ", ble->str);
> +		if (rc < 0)
> +			return rc;
>  	}
>  
> -	*fwd = pos;
> -	return pos;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  static int
> -snprint_blacklist_devgroup (char *buff, int len, int *fwd, vector *vec)
> +snprint_blacklist_devgroup (struct strbuf *buff, vector *vec)
>  {
> -	int threshold = MAX_LINE_LEN;
>  	struct blentry_device * bled;
> -	int pos;
> -	int i;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc, i;
>  
> -	pos = *fwd;
>  	if (!VECTOR_SIZE(*vec)) {
> -		if ((len - pos - threshold) <= 0)
> -			return 0;
> -		pos += snprintf(buff + pos, len - pos, "        <empty>\n");
> +		if ((rc = append_strbuf_str(buff, "        <empty>\n")) < 0)
> +			return rc;
>  	} else vector_foreach_slot (*vec, bled, i) {
> -		if ((len - pos - threshold) <= 0)
> -			return 0;
> -		if (bled->origin == ORIGIN_CONFIG)
> -			pos += snprintf(buff + pos, len - pos, "        (config file rule) ");
> -		else if (bled->origin == ORIGIN_DEFAULT)
> -			pos += snprintf(buff + pos, len - pos, "        (default rule)     ");
> -		pos += snprintf(buff + pos, len - pos, "%s:%s\n", bled->vendor, bled->product);
> +		rc = print_strbuf(buff, "        %s %s:%s\n",
> +				  bled->origin == ORIGIN_CONFIG ?
> +				  "(config file rule)" :
> +				  "(default rule)    ",
> +				  bled->vendor, bled->product);
> +		if (rc < 0)
> +			return rc;
>  	}
>  
> -	*fwd = pos;
> -	return pos;
> -}
> -
> -int snprint_blacklist_report(struct config *conf, char *buff, int len)
> -{
> -	int threshold = MAX_LINE_LEN;
> -	int fwd = 0;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "device node rules:\n"
> -					       "- blacklist:\n");
> -	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_devnode))
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
> -	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_devnode) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "udev property rules:\n"
> -					       "- blacklist:\n");
> -	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_property))
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
> -	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_property) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "protocol rules:\n"
> -					       "- blacklist:\n");
> -	if (!snprint_blacklist_group(buff, len, &fwd, &conf->blist_protocol))
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
> -	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_protocol) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "wwid rules:\n"
> -					       "- blacklist:\n");
> -	if (snprint_blacklist_group(buff, len, &fwd, &conf->blist_wwid) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
> -	if (snprint_blacklist_group(buff, len, &fwd, &conf->elist_wwid) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "device rules:\n"
> -					       "- blacklist:\n");
> -	if (snprint_blacklist_devgroup(buff, len, &fwd, &conf->blist_device) == 0)
> -		return len;
> -
> -	if ((len - fwd - threshold) <= 0)
> -		return len;
> -	fwd += snprintf(buff + fwd, len - fwd, "- exceptions:\n");
> -	if (snprint_blacklist_devgroup(buff, len, &fwd, &conf->elist_device) == 0)
> -		return len;
> -
> -	if (fwd > len)
> -		return len;
> -	return fwd;
> -}
> -
> -static int snprint_blacklist(const struct config *conf, char *buff, int len)
> +	return get_strbuf_len(buff) - initial_len;
> +}
> +
> +int snprint_blacklist_report(struct config *conf, struct strbuf *buff)
>  {
> -	int i;
> +	size_t initial_len = get_strbuf_len(buff);
> +	int rc;
> +
> +	if ((rc = append_strbuf_str(buff, "device node rules:\n- blacklist:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->blist_devnode)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->elist_devnode)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "udev property rules:\n- blacklist:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->blist_property)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->elist_property)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "protocol rules:\n- blacklist:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->blist_protocol)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->elist_protocol)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "wwid rules:\n- blacklist:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->blist_wwid)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_group(buff, &conf->elist_wwid)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "device rules:\n- blacklist:\n")) < 0)
> +		return rc;
> +	if ((rc = snprint_blacklist_devgroup(buff, &conf->blist_device)) < 0)
> +		return rc;
> +
> +	if ((rc = append_strbuf_str(buff, "- exceptions:\n")) < 0)
> +	     return rc;
> +	if ((rc = snprint_blacklist_devgroup(buff, &conf->elist_device)) < 0)
> +		return rc;
> +
> +	return get_strbuf_len(buff) - initial_len;
> +}
> +
> +static int snprint_blacklist(const struct config *conf, struct strbuf *buff)
> +{
> +	int i, rc;
>  	struct blentry * ble;
>  	struct blentry_device * bled;
> -	int fwd = 0;
>  	struct keyword *rootkw;
>  	struct keyword *kw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "blacklist");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "blacklist {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "blacklist {\n")) < 0)
> +		return rc;
>  
>  	vector_foreach_slot (conf->blist_devnode, ble, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ble);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->blist_wwid, ble, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ble);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->blist_property, ble, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "property");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ble);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->blist_protocol, ble, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ble);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ble)) < 0)
> +			return rc;
>  	}
> +
>  	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
>  	if (!rootkw)
>  		return 0;
>  
>  	vector_foreach_slot (conf->blist_device, bled, i) {
> -		fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
> +			return rc;
> +
>  		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				       kw, bled);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, bled)) < 0)
> +			return rc;
>  		kw = find_keyword(conf->keywords, rootkw->sub, "product");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				       kw, bled);
> -		if (fwd >= len)
> -			return len;
> -		fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff,
> +					  "\t\t%k %v\n\t}\n", kw, bled)) < 0)
> +			return rc;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  static int snprint_blacklist_except(const struct config *conf,
> -				    char *buff, int len)
> +				    struct strbuf *buff)
>  {
> -	int i;
> +	int i, rc;
>  	struct blentry * ele;
>  	struct blentry_device * eled;
> -	int fwd = 0;
>  	struct keyword *rootkw;
>  	struct keyword *kw;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	rootkw = find_keyword(conf->keywords, NULL, "blacklist_exceptions");
>  	if (!rootkw)
>  		return 0;
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "blacklist_exceptions {\n");
> -	if (fwd >= len)
> -		return len;
> +	if ((rc = append_strbuf_str(buff, "blacklist_exceptions {\n")) < 0)
> +		return rc;
>  
>  	vector_foreach_slot (conf->elist_devnode, ele, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "devnode");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ele);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->elist_wwid, ele, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "wwid");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ele);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->elist_property, ele, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "property");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ele);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
> +			return rc;
>  	}
>  	vector_foreach_slot (conf->elist_protocol, ele, i) {
>  		kw = find_keyword(conf->keywords, rootkw->sub, "protocol");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t%k %v\n",
> -				       kw, ele);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t%k %v\n", kw, ele)) < 0)
> +			return rc;
>  	}
> +
>  	rootkw = find_keyword(conf->keywords, rootkw->sub, "device");
>  	if (!rootkw)
>  		return 0;
>  
>  	vector_foreach_slot (conf->elist_device, eled, i) {
> -		fwd += snprintf(buff + fwd, len - fwd, "\tdevice {\n");
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = append_strbuf_str(buff, "\tdevice {\n")) < 0)
> +			return rc;
> +
>  		kw = find_keyword(conf->keywords, rootkw->sub, "vendor");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				       kw, eled);
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t\t%k %v\n", kw, eled)) < 0)
> +			return rc;
>  		kw = find_keyword(conf->keywords, rootkw->sub, "product");
>  		if (!kw)
>  			return 0;
> -		fwd += snprint_keyword(buff + fwd, len - fwd, "\t\t%k %v\n",
> -				       kw, eled);
> -		if (fwd >= len)
> -			return len;
> -		fwd += snprintf(buff + fwd, len - fwd, "\t}\n");
> -		if (fwd >= len)
> -			return len;
> +		if ((rc = snprint_keyword(buff, "\t\t%k %v\n\t}\n",
> +					  kw, eled)) < 0)
> +			return rc;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "}\n");
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +
> +	if ((rc = append_strbuf_str(buff, "}\n")) < 0)
> +		return rc;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  char *snprint_config(const struct config *conf, int *len,
>  		     const struct _vector *hwtable, const struct _vector *mpvec)
>  {
> +	STRBUF_ON_STACK(buff);
>  	char *reply;
> -	/* built-in config is >20kB already */
> -	unsigned int maxlen = 32768;
> -
> -	for (reply = NULL; maxlen <= UINT_MAX/2; maxlen *= 2) {
> -		char *c, *tmp = reply;
> -
> -		reply = REALLOC(reply, maxlen);
> -		if (!reply) {
> -			if (tmp)
> -				free(tmp);
> +	int rc;
> +
> +	if ((rc = snprint_defaults(conf, &buff)) < 0 ||
> +	    (rc = snprint_blacklist(conf, &buff)) < 0 ||
> +	    (rc = snprint_blacklist_except(conf, &buff)) < 0 ||
> +	    (rc = snprint_hwtable(conf, &buff,
> +				  hwtable ? hwtable : conf->hwtable)) < 0 ||
> +	    (rc = snprint_overrides(conf, &buff, conf->overrides)) < 0)
> +		return NULL;
> +	if (VECTOR_SIZE(conf->mptable) > 0 ||
> +	    (mpvec != NULL && VECTOR_SIZE(mpvec) > 0))
> +		if ((rc = snprint_mptable(conf, &buff, mpvec)) < 0)
>  			return NULL;
> -		}
> -
> -		c = reply + snprint_defaults(conf, reply, maxlen);
> -		if (c == reply + maxlen)
> -			continue;
>  
> -		c += snprint_blacklist(conf, c, reply + maxlen - c);
> -		if (c == reply + maxlen)
> -			continue;
> -
> -		c += snprint_blacklist_except(conf, c, reply + maxlen - c);
> -		if (c == reply + maxlen)
> -			continue;
> -
> -		c += snprint_hwtable(conf, c, reply + maxlen - c,
> -				     hwtable ? hwtable : conf->hwtable);
> -		if (c == reply + maxlen)
> -			continue;
> +	if (len)
> +		*len = get_strbuf_len(&buff);
> +	reply = steal_strbuf_str(&buff);
>  
> -		c += snprint_overrides(conf, c, reply + maxlen - c,
> -				       conf->overrides);
> -		if (c == reply + maxlen)
> -			continue;
> -
> -		if (VECTOR_SIZE(conf->mptable) > 0 ||
> -		    (mpvec != NULL && VECTOR_SIZE(mpvec) > 0))
> -			c += snprint_mptable(conf, c, reply + maxlen - c,
> -					     mpvec);
> -
> -		if (c < reply + maxlen) {
> -			if (len)
> -				*len = c - reply;
> -			return reply;
> -		}
> -	}
> -
> -	free(reply);
> -	return NULL;
> +	return reply;
>  }
>  
> -int snprint_status(char *buff, int len, const struct vectors *vecs)
> +int snprint_status(struct strbuf *buff, const struct vectors *vecs)
>  {
> -	int fwd = 0;
> -	int i;
> +	int i, rc;
>  	unsigned int count[PATH_MAX_STATE] = {0};
> +	int monitored_count = 0;
>  	struct path * pp;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	vector_foreach_slot (vecs->pathvec, pp, i) {
>  		count[pp->state]++;
>  	}
> -	fwd += snprintf(buff + fwd, len - fwd, "path checker states:\n");
> -	for (i=0; i<PATH_MAX_STATE; i++) {
> +	if ((rc = append_strbuf_str(buff, "path checker states:\n")) < 0)
> +		return rc;
> +	for (i = 0; i < PATH_MAX_STATE; i++) {
>  		if (!count[i])
>  			continue;
> -		fwd += snprintf(buff + fwd, len - fwd, "%-20s%u\n",
> -				checker_state_name(i), count[i]);
> +		if ((rc = print_strbuf(buff, "%-20s%u\n",
> +				       checker_state_name(i), count[i])) < 0)
> +			return rc;
>  	}
>  
> -	int monitored_count = 0;
> -
>  	vector_foreach_slot(vecs->pathvec, pp, i)
>  		if (pp->fd >= 0)
>  			monitored_count++;
> -	fwd += snprintf(buff + fwd, len - fwd, "\npaths: %d\nbusy: %s\n",
> -			monitored_count, is_uevent_busy()? "True" : "False");
> +	if ((rc = print_strbuf(buff, "\npaths: %d\nbusy: %s\n",
> +			       monitored_count,
> +			       is_uevent_busy()? "True" : "False")) < 0)
> +		return rc;
>  
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
> -int snprint_devices(struct config *conf, char *buff, size_t len,
> +int snprint_devices(struct config *conf, struct strbuf *buff,
>  		    const struct vectors *vecs)
>  {
> -	size_t fwd = 0;
>  	int r;
>  	struct udev_enumerate *enm;
>  	struct udev_list_entry *item, *first;
> -
>  	struct path * pp;
> +	size_t initial_len = get_strbuf_len(buff);
>  
>  	enm = udev_enumerate_new(udev);
>  	if (!enm)
>  		return 1;
>  	udev_enumerate_add_match_subsystem(enm, "block");
>  
> -	fwd += snprintf(buff + fwd, len - fwd, "available block devices:\n");
> +	if ((r = append_strbuf_str(buff, "available block devices:\n")) < 0)
> +		goto out;
>  	r = udev_enumerate_scan_devices(enm);
>  	if (r < 0)
>  		goto out;
> @@ -2066,10 +1816,6 @@ int snprint_devices(struct config *conf, char *buff, size_t len,
>  			continue;
>  		}
>  
> -		fwd += snprintf(buff + fwd, len - fwd, "    %s", devname);
> -		if (fwd >= len)
> -			break;
> -
>  		pp = find_path_by_dev(vecs->pathvec, devname);
>  		if (!pp) {
>  			const char *hidden;
> @@ -2092,41 +1838,27 @@ int snprint_devices(struct config *conf, char *buff, size_t len,
>  		} else
>  			status = " devnode whitelisted, monitored";
>  
> -		fwd += snprintf(buff + fwd, len - fwd, " %s\n", status);
> +		r = print_strbuf(buff, "    %s %s\n", devname, status);
>  		udev_device_unref(u_dev);
> -		if (fwd >= len)
> +		if (r < 0)
>  			break;
>  	}
>  out:
>  	udev_enumerate_unref(enm);
> +	if (r < 0)
> +		return r;
>  
> -	if (fwd >= len)
> -		return len;
> -	return fwd;
> +	return get_strbuf_len(buff) - initial_len;
>  }
>  
>  /*
>   * stdout printing helpers
>   */
> -void print_path(struct path *pp, char *style)
> -{
> -	char line[MAX_LINE_LEN];
> -
> -	memset(&line[0], 0, MAX_LINE_LEN);
> -	snprint_path(&line[0], MAX_LINE_LEN, style, pp, 1);
> -	printf("%s", line);
> -}
> -
> -void print_all_paths(vector pathvec, int banner)
> -{
> -	print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
> -}
> -
> -void print_all_paths_custo(vector pathvec, int banner, char *fmt)
> +static void print_all_paths_custo(vector pathvec, int banner, const char *fmt)
>  {
>  	int i;
>  	struct path * pp;
> -	char line[MAX_LINE_LEN];
> +	STRBUF_ON_STACK(line);
>  
>  	if (!VECTOR_SIZE(pathvec)) {
>  		if (banner)
> @@ -2135,12 +1867,18 @@ void print_all_paths_custo(vector pathvec, int banner, char *fmt)
>  	}
>  
>  	if (banner)
> -		fprintf(stdout, "===== paths list =====\n");
> +		append_strbuf_str(&line, "===== paths list =====\n");
>  
>  	get_path_layout(pathvec, 1);
> -	snprint_path_header(line, MAX_LINE_LEN, fmt);
> -	fprintf(stdout, "%s", line);
> +	snprint_path_header(&line, fmt);
>  
>  	vector_foreach_slot (pathvec, pp, i)
> -		print_path(pp, fmt);
> +		snprint_path(&line, fmt, pp, 1);
> +
> +	printf("%s", get_strbuf_str(&line));
> +}
> +
> +void print_all_paths(vector pathvec, int banner)
> +{
> +	print_all_paths_custo(pathvec, banner, PRINT_PATH_LONG);
>  }
> diff --git a/libmultipath/print.h b/libmultipath/print.h
> index 0042cef..b922812 100644
> --- a/libmultipath/print.h
> +++ b/libmultipath/print.h
> @@ -2,6 +2,8 @@
>  #define _PRINT_H
>  #include "dm-generic.h"
>  
> +struct strbuf;
> +
>  #define PRINT_PATH_LONG      "%w %i %d %D %p %t %T %s %o"
>  #define PRINT_PATH_INDENT    "%i %d %D %t %T %o"
>  #define PRINT_PATH_CHECKER   "%i %d %D %p %t %T %o %C"
> @@ -25,7 +27,7 @@
>  #define PRINT_JSON_END_LAST_ELEM  "}"
>  #define PRINT_JSON_END_LAST       "}\n"
>  #define PRINT_JSON_END_ARRAY      "]\n"
> -#define PRINT_JSON_INDENT    "   "
> +#define PRINT_JSON_INDENT_N    3
>  #define PRINT_JSON_MAP       "{\n" \
>  			     "      \"name\" : \"%n\",\n" \
>  			     "      \"uuid\" : \"%w\",\n" \
> @@ -79,21 +81,21 @@ struct path_data {
>  	char wildcard;
>  	char * header;
>  	unsigned int width;
> -	int (*snprint)(char * buff, size_t len, const struct path * pp);
> +	int (*snprint)(struct strbuf *, const struct path * pp);
>  };
>  
>  struct multipath_data {
>  	char wildcard;
>  	char * header;
>  	unsigned int width;
> -	int (*snprint)(char * buff, size_t len, const struct multipath * mpp);
> +	int (*snprint)(struct strbuf *, const struct multipath * mpp);
>  };
>  
>  struct pathgroup_data {
>  	char wildcard;
>  	char * header;
>  	unsigned int width;
> -	int (*snprint)(char * buff, size_t len, const struct pathgroup * pgp);
> +	int (*snprint)(struct strbuf *, const struct pathgroup * pgp);
>  };
>  
>  enum layout_reset {
> @@ -106,37 +108,35 @@ void _get_path_layout (const struct _vector *gpvec, enum layout_reset);
>  void get_path_layout (vector pathvec, int header);
>  void _get_multipath_layout (const struct _vector *gmvec, enum layout_reset);
>  void get_multipath_layout (vector mpvec, int header);
> -int snprint_path_header (char *, int, const char *);
> -int snprint_multipath_header (char *, int, const char *);
> -int _snprint_path (const struct gen_path *, char *, int, const char *, int);
> -#define snprint_path(buf, len, fmt, pp, v) \
> -	_snprint_path(dm_path_to_gen(pp), buf, len, fmt,  v)
> -int _snprint_multipath (const struct gen_multipath *, char *, int,
> +int snprint_path_header(struct strbuf *, const char *);
> +int snprint_multipath_header(struct strbuf *, const char *);
> +int _snprint_path (const struct gen_path *, struct strbuf *, const char *, int);
> +#define snprint_path(buf, fmt, pp, v) \
> +	_snprint_path(dm_path_to_gen(pp), buf, fmt,  v)
> +int _snprint_multipath (const struct gen_multipath *, struct strbuf *,
>  			const char *, int);
> -#define snprint_multipath(buf, len, fmt, mp, v)				\
> -	_snprint_multipath(dm_multipath_to_gen(mp), buf, len, fmt,  v)
> -int _snprint_multipath_topology (const struct gen_multipath *, char *, int,
> +#define snprint_multipath(buf, fmt, mp, v)				\
> +	_snprint_multipath(dm_multipath_to_gen(mp), buf, fmt,  v)
> +int _snprint_multipath_topology (const struct gen_multipath *, struct strbuf *,
>  				 int verbosity);
> -#define snprint_multipath_topology(buf, len, mpp, v) \
> -	_snprint_multipath_topology (dm_multipath_to_gen(mpp), buf, len, v)
> -int snprint_multipath_topology_json (char * buff, int len,
> -				const struct vectors * vecs);
> +#define snprint_multipath_topology(buf, mpp, v) \
> +	_snprint_multipath_topology (dm_multipath_to_gen(mpp), buf, v)
> +int snprint_multipath_topology_json(struct strbuf *, const struct vectors *vecs);
>  char *snprint_config(const struct config *conf, int *len,
>  		     const struct _vector *hwtable,
>  		     const struct _vector *mpvec);
> -int snprint_multipath_map_json (char * buff, int len,
> -				const struct multipath * mpp);
> -int snprint_blacklist_report (struct config *, char *, int);
> -int snprint_wildcards (char *, int);
> -int snprint_status (char *, int, const struct vectors *);
> -int snprint_devices (struct config *, char *, size_t, const struct vectors *);
> -int snprint_path_serial (char *, size_t, const struct path *);
> -int snprint_host_wwnn (char *, size_t, const struct path *);
> -int snprint_host_wwpn (char *, size_t, const struct path *);
> -int snprint_tgt_wwnn (char *, size_t, const struct path *);
> -int snprint_tgt_wwpn (char *, size_t, const struct path *);
> +int snprint_multipath_map_json(struct strbuf *, const struct multipath *mpp);
> +int snprint_blacklist_report(struct config *, struct strbuf *);
> +int snprint_wildcards(struct strbuf *);
> +int snprint_status(struct strbuf *, const struct vectors *);
> +int snprint_devices(struct config *, struct strbuf *, const struct vectors *);
> +int snprint_path_serial(struct strbuf *, const struct path *);
> +int snprint_host_wwnn(struct strbuf *, const struct path *);
> +int snprint_host_wwpn(struct strbuf *, const struct path *);
> +int snprint_tgt_wwnn(struct strbuf *, const struct path *);
> +int snprint_tgt_wwpn(struct strbuf *, const struct path *);
>  #define PROTOCOL_BUF_SIZE sizeof("scsi:unspec")
> -int snprint_path_protocol(char *, size_t, const struct path *);
> +int snprint_path_protocol(struct strbuf *, const struct path *);
>  
>  void _print_multipath_topology (const struct gen_multipath * gmp,
>  				int verbosity);
> @@ -144,14 +144,13 @@ void _print_multipath_topology (const struct gen_multipath * gmp,
>  	_print_multipath_topology(dm_multipath_to_gen(mpp), v)
>  
>  void print_all_paths (vector pathvec, int banner);
> -void print_all_paths_custo (vector pathvec, int banner, char *fmt);
>  
>  int snprint_path_attr(const struct gen_path* gp,
> -		      char *buf, int len, char wildcard);
> +		      struct strbuf *buf, char wildcard);
>  int snprint_pathgroup_attr(const struct gen_pathgroup* gpg,
> -			   char *buf, int len, char wildcard);
> +			   struct strbuf *buf, char wildcard);
>  int snprint_multipath_attr(const struct gen_multipath* gm,
> -			   char *buf, int len, char wildcard);
> +			   struct strbuf *buf, char wildcard);
>  int snprint_multipath_style(const struct gen_multipath *gmp,
> -			    char *style, int len, int verbosity);
> +			    struct strbuf *style, int verbosity);
>  #endif /* _PRINT_H */
> diff --git a/libmultipath/prioritizers/weightedpath.c b/libmultipath/prioritizers/weightedpath.c
> index 650088b..ea03fc3 100644
> --- a/libmultipath/prioritizers/weightedpath.c
> +++ b/libmultipath/prioritizers/weightedpath.c
> @@ -35,52 +35,37 @@
>  #include "structs_vec.h"
>  #include "print.h"
>  #include "util.h"
> -
> -#define CHECK_LEN \
> -do { \
> -	if ((p - str) >= (len - 1)) { \
> -		condlog(0, "%s: %s - buffer size too small", pp->dev, pp->prio.name); \
> -		return -1; \
> -	} \
> -} while(0)
> +#include "strbuf.h"
>  
>  static int
> -build_serial_path(struct path *pp, char *str, int len)
> +build_serial_path(struct path *pp, struct strbuf *buf)
>  {
> -	char *p = str;
> +	int rc = snprint_path_serial(buf, pp);
>  
> -	p += snprint_path_serial(p, str + len - p, pp);
> -	CHECK_LEN;
> -	return 0;
> +	return rc < 0 ? rc : 0;
>  }
>  
>  static int
> -build_wwn_path(struct path *pp, char *str, int len)
> +build_wwn_path(struct path *pp, struct strbuf *buf)
>  {
> -	char *p = str;
> -
> -	p += snprint_host_wwnn(p, str + len - p, pp);
> -	CHECK_LEN;
> -	p += snprintf(p, str + len - p, ":");
> -	CHECK_LEN;
> -	p += snprint_host_wwpn(p, str + len - p, pp);
> -	CHECK_LEN;
> -	p += snprintf(p, str + len - p, ":");
> -	CHECK_LEN;
> -	p += snprint_tgt_wwnn(p, str + len - p, pp);
> -	CHECK_LEN;
> -	p += snprintf(p, str + len - p, ":");
> -	CHECK_LEN;
> -	p += snprint_tgt_wwpn(p, str + len - p, pp);
> -	CHECK_LEN;
> +	int rc;
> +
> +	if ((rc = snprint_host_wwnn(buf, pp)) < 0 ||
> +	    (rc = fill_strbuf(buf, ':', 1)) < 0 ||
> +	    (rc = snprint_host_wwpn(buf, pp)) < 0 ||
> +	    (rc = fill_strbuf(buf, ':', 1)) < 0 ||
> +	    (rc = snprint_tgt_wwnn(buf, pp) < 0) ||
> +	    (rc = fill_strbuf(buf, ':', 1) < 0) ||
> +	    (rc = snprint_tgt_wwpn(buf, pp) < 0))
> +		return rc;
>  	return 0;
>  }
>  
>  /* main priority routine */
>  int prio_path_weight(struct path *pp, char *prio_args)
>  {
> -	char path[FILE_NAME_SIZE];
> -	char *arg;
> +	STRBUF_ON_STACK(path);
> +	char *arg __attribute__((cleanup(cleanup_charp))) = NULL;
>  	char *temp, *regex, *prio;
>  	char split_char[] = " \t";
>  	int priority = DEFAULT_PRIORITY, path_found = 0;
> @@ -101,24 +86,22 @@ int prio_path_weight(struct path *pp, char *prio_args)
>  	}
>  
>  	if (!strcmp(regex, HBTL)) {
> -		sprintf(path, "%d:%d:%d:%" PRIu64, pp->sg_id.host_no,
> -			pp->sg_id.channel, pp->sg_id.scsi_id, pp->sg_id.lun);
> +		if (print_strbuf(&path, "%d:%d:%d:%" PRIu64, pp->sg_id.host_no,
> +				 pp->sg_id.channel, pp->sg_id.scsi_id,
> +				 pp->sg_id.lun) < 0)
> +			return priority;
>  	} else if (!strcmp(regex, DEV_NAME)) {
> -		strcpy(path, pp->dev);
> +		if (append_strbuf_str(&path, pp->dev) < 0)
> +			return priority;
>  	} else if (!strcmp(regex, SERIAL)) {
> -		if (build_serial_path(pp, path, FILE_NAME_SIZE) != 0) {
> -			FREE(arg);
> +		if (build_serial_path(pp, &path) != 0)
>  			return priority;
> -		}
>  	} else if (!strcmp(regex, WWN)) {
> -		if (build_wwn_path(pp, path, FILE_NAME_SIZE) != 0) {
> -			FREE(arg);
> +		if (build_wwn_path(pp, &path) != 0)
>  			return priority;
> -		}
>  	} else {
>  		condlog(0, "%s: %s - Invalid arguments", pp->dev,
>  			pp->prio.name);
> -		FREE(arg);
>  		return priority;
>  	}
>  
> @@ -131,7 +114,8 @@ int prio_path_weight(struct path *pp, char *prio_args)
>  			break;
>  
>  		if (!regcomp(&pathe, regex, REG_EXTENDED|REG_NOSUB)) {
> -			if (!regexec(&pathe, path, 0, NULL, 0)) {
> +			if (!regexec(&pathe, get_strbuf_str(&path), 0,
> +				     NULL, 0)) {
>  				path_found = 1;
>  				priority = atoi(prio);
>  			}
> @@ -139,7 +123,6 @@ int prio_path_weight(struct path *pp, char *prio_args)
>  		}
>  	}
>  
> -	FREE(arg);
>  	return priority;
>  }
>  
> diff --git a/multipathd/cli_handlers.c b/multipathd/cli_handlers.c
> index bce40b1..6d3a0ae 100644
> --- a/multipathd/cli_handlers.c
> +++ b/multipathd/cli_handlers.c
> @@ -30,6 +30,7 @@
>  #include "cli.h"
>  #include "uevent.h"
>  #include "foreign.h"
> +#include "strbuf.h"
>  #include "cli_handlers.h"
>  
>  #define SET_REPLY_AND_LEN(__rep, __len, string_literal)			\
> @@ -42,49 +43,30 @@ int
>  show_paths (char ** r, int * len, struct vectors * vecs, char * style,
>  	    int pretty)
>  {
> +	STRBUF_ON_STACK(reply);
>  	int i;
>  	struct path * pp;
> -	char * c;
> -	char * reply, * header;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	int hdr_len = 0;
>  
>  	get_path_layout(vecs->pathvec, 1);
>  	foreign_path_layout();
>  
> -	reply = MALLOC(maxlen);
> +	if (pretty && (hdr_len = snprint_path_header(&reply, style)) < 0)
> +		return 1;
>  
> -	while (again) {
> -		if (!reply)
> +	vector_foreach_slot(vecs->pathvec, pp, i) {
> +		if (snprint_path(&reply, style, pp, pretty) < 0)
>  			return 1;
> -
> -		c = reply;
> -
> -		if (pretty)
> -			c += snprint_path_header(c, reply + maxlen - c,
> -						 style);
> -		header = c;
> -
> -		vector_foreach_slot(vecs->pathvec, pp, i)
> -			c += snprint_path(c, reply + maxlen - c,
> -					  style, pp, pretty);
> -
> -		c += snprint_foreign_paths(c, reply + maxlen - c,
> -					   style, pretty);
> -
> -		again = (c == reply + maxlen - 1);
> -
> -		REALLOC_REPLY(reply, again, maxlen);
>  	}
> +	if (snprint_foreign_paths(&reply, style, pretty) < 0)
> +		return 1;
>  
> -	if (pretty && c == header) {
> +	if (pretty && get_strbuf_len(&reply) == (size_t)hdr_len)
>  		/* No output - clear header */
> -		*reply = '\0';
> -		c = reply;
> -	}
> +		truncate_strbuf(&reply, 0);
>  
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -92,28 +74,14 @@ int
>  show_path (char ** r, int * len, struct vectors * vecs, struct path *pp,
>  	   char * style)
>  {
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	STRBUF_ON_STACK(reply);
>  
>  	get_path_layout(vecs->pathvec, 1);
> -	reply = MALLOC(maxlen);
> -
> -	while (again) {
> -		if (!reply)
> -			return 1;
> -
> -		c = reply;
> -
> -		c += snprint_path(c, reply + maxlen - c, style, pp, 0);
> -
> -		again = (c == reply + maxlen - 1);
> +	if (snprint_path(&reply, style, pp, 0) < 0)
> +		return 1;
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
>  	return 0;
>  }
>  
> @@ -121,84 +89,51 @@ int
>  show_map_topology (char ** r, int * len, struct multipath * mpp,
>  		   struct vectors * vecs)
>  {
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	STRBUF_ON_STACK(reply);
>  
>  	if (update_multipath(vecs, mpp->alias, 0))
>  		return 1;
> -	reply = MALLOC(maxlen);
>  
> -	while (again) {
> -		if (!reply)
> -			return 1;
> -
> -		c = reply;
> -
> -		c += snprint_multipath_topology(c, reply + maxlen - c, mpp, 2);
> -		again = (c == reply + maxlen - 1);
> +	if (snprint_multipath_topology(&reply, mpp, 2) < 0)
> +		return 1;
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
>  	return 0;
>  }
>  
>  int
>  show_maps_topology (char ** r, int * len, struct vectors * vecs)
>  {
> +	STRBUF_ON_STACK(reply);
>  	int i;
>  	struct multipath * mpp;
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
>  
>  	get_path_layout(vecs->pathvec, 0);
>  	foreign_path_layout();
>  
> -	reply = MALLOC(maxlen);
> -
> -	while (again) {
> -		if (!reply)
> -			return 1;
> -
> -		c = reply;
> -
> -		vector_foreach_slot(vecs->mpvec, mpp, i) {
> -			if (update_multipath(vecs, mpp->alias, 0)) {
> -				i--;
> -				continue;
> -			}
> -			c += snprint_multipath_topology(c, reply + maxlen - c,
> -							mpp, 2);
> +	vector_foreach_slot(vecs->mpvec, mpp, i) {
> +		if (update_multipath(vecs, mpp->alias, 0)) {
> +			i--;
> +			continue;
>  		}
> -		c += snprint_foreign_topology(c, reply + maxlen - c, 2);
> -
> -		again = (c == reply + maxlen - 1);
> -
> -		REALLOC_REPLY(reply, again, maxlen);
> +		if (snprint_multipath_topology(&reply, mpp, 2) < 0)
> +			return 1;
>  	}
> +	if (snprint_foreign_topology(&reply, 2) < 0)
> +		return 1;
>  
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
>  int
>  show_maps_json (char ** r, int * len, struct vectors * vecs)
>  {
> +	STRBUF_ON_STACK(reply);
>  	int i;
>  	struct multipath * mpp;
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> -
> -	if (VECTOR_SIZE(vecs->mpvec) > 0)
> -		maxlen *= PRINT_JSON_MULTIPLIER * VECTOR_SIZE(vecs->mpvec);
>  
>  	vector_foreach_slot(vecs->mpvec, mpp, i) {
>  		if (update_multipath(vecs, mpp->alias, 0)) {
> @@ -206,21 +141,11 @@ show_maps_json (char ** r, int * len, struct vectors * vecs)
>  		}
>  	}
>  
> -	reply = MALLOC(maxlen);
> -
> -	while (again) {
> -		if (!reply)
> -			return 1;
> -
> -		c = reply;
> -
> -		c += snprint_multipath_topology_json(c, maxlen, vecs);
> -		again = (c == reply + maxlen);
> +	if (snprint_multipath_topology_json(&reply, vecs) < 0)
> +		return 1;
>  
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply);
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -228,28 +153,16 @@ int
>  show_map_json (char ** r, int * len, struct multipath * mpp,
>  		   struct vectors * vecs)
>  {
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	STRBUF_ON_STACK(reply);
>  
>  	if (update_multipath(vecs, mpp->alias, 0))
>  		return 1;
> -	reply = MALLOC(maxlen);
> -
> -	while (again) {
> -		if (!reply)
> -			return 1;
>  
> -		c = reply;
> -
> -		c += snprint_multipath_map_json(c, maxlen, mpp);
> -		again = (c == reply + maxlen);
> +	if (snprint_multipath_map_json(&reply, mpp) < 0)
> +		return 1;
>  
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply);
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -420,58 +333,40 @@ cli_list_maps_json (void * v, char ** reply, int * len, void * data)
>  int
>  cli_list_wildcards (void * v, char ** reply, int * len, void * data)
>  {
> -	char * c;
> -
> -	*reply = MALLOC(INITIAL_REPLY_LEN);
> +	STRBUF_ON_STACK(buf);
>  
> -	if (!*reply)
> +	if (snprint_wildcards(&buf) < 0)
>  		return 1;
>  
> -	c = *reply;
> -	c += snprint_wildcards(c, INITIAL_REPLY_LEN);
> -
> -	*len = INITIAL_REPLY_LEN;
> +	*len = get_strbuf_len(&buf) + 1;
> +	*reply = steal_strbuf_str(&buf);
>  	return 0;
>  }
>  
>  int
>  show_status (char ** r, int *len, struct vectors * vecs)
>  {
> -	char * c;
> -	char * reply;
> -
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	reply = MALLOC(maxlen);
> +	STRBUF_ON_STACK(reply);
>  
> -	if (!reply)
> +	if (snprint_status(&reply, vecs) < 0)
>  		return 1;
>  
> -	c = reply;
> -	c += snprint_status(c, reply + maxlen - c, vecs);
> -
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +	*len = get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
>  int
>  show_daemon (char ** r, int *len)
>  {
> -	char * c;
> -	char * reply;
> -
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	reply = MALLOC(maxlen);
> +	STRBUF_ON_STACK(reply);
>  
> -	if (!reply)
> +	if (print_strbuf(&reply, "pid %d %s\n",
> +			 daemon_pid, daemon_status()) < 0)
>  		return 1;
>  
> -	c = reply;
> -	c += snprintf(c, INITIAL_REPLY_LEN, "pid %d %s\n",
> -		      daemon_pid, daemon_status());
> -
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +	*len = get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -479,26 +374,13 @@ int
>  show_map (char ** r, int *len, struct multipath * mpp, char * style,
>  	  int pretty)
>  {
> -	char * c;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> -
> -	reply = MALLOC(maxlen);
> -	while (again) {
> -		if (!reply)
> -			return 1;
> +	STRBUF_ON_STACK(reply);
>  
> -		c = reply;
> -		c += snprint_multipath(c, reply + maxlen - c, style,
> -				       mpp, pretty);
> -
> -		again = (c == reply + maxlen - 1);
> +	if (snprint_multipath(&reply, style, mpp, pretty) < 0)
> +		return 1;
>  
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +	*len = get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -506,51 +388,34 @@ int
>  show_maps (char ** r, int *len, struct vectors * vecs, char * style,
>  	   int pretty)
>  {
> +	STRBUF_ON_STACK(reply);
>  	int i;
>  	struct multipath * mpp;
> -	char * c, *header;
> -	char * reply;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	int hdr_len = 0;
>  
>  	get_multipath_layout(vecs->mpvec, 1);
>  	foreign_multipath_layout();
>  
> -	reply = MALLOC(maxlen);
> -
> -	while (again) {
> -		if (!reply)
> -			return 1;
> -
> -		c = reply;
> -		if (pretty)
> -			c += snprint_multipath_header(c, reply + maxlen - c,
> -						      style);
> -		header = c;
> -
> -		vector_foreach_slot(vecs->mpvec, mpp, i) {
> -			if (update_multipath(vecs, mpp->alias, 0)) {
> -				i--;
> -				continue;
> -			}
> -			c += snprint_multipath(c, reply + maxlen - c,
> -					       style, mpp, pretty);
> +	if (pretty && (hdr_len = snprint_multipath_header(&reply, style)) < 0)
> +		return 1;
>  
> +	vector_foreach_slot(vecs->mpvec, mpp, i) {
> +		if (update_multipath(vecs, mpp->alias, 0)) {
> +			i--;
> +			continue;
>  		}
> -		c += snprint_foreign_multipaths(c, reply + maxlen - c,
> -						style, pretty);
> -		again = (c == reply + maxlen - 1);
> -
> -		REALLOC_REPLY(reply, again, maxlen);
> +		if (snprint_multipath(&reply, style, mpp, pretty) < 0)
> +			return 1;
>  	}
> +	if (snprint_foreign_multipaths(&reply, style, pretty) < 0)
> +		return 1;
>  
> -	if (pretty && c == header) {
> +	if (pretty && get_strbuf_len(&reply) == (size_t)hdr_len)
>  		/* No output - clear header */
> -		*reply = '\0';
> -		c = reply;
> -	}
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +		truncate_strbuf(&reply, 0);
> +
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -1366,34 +1231,20 @@ cli_fail(void * v, char ** reply, int * len, void * data)
>  int
>  show_blacklist (char ** r, int * len)
>  {
> -	char *c = NULL;
> -	char *reply = NULL;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	STRBUF_ON_STACK(reply);
>  	struct config *conf;
> -	int fail = 0;
> -
> -	reply = MALLOC(maxlen);
> +	bool fail;
>  
>  	conf = get_multipath_config();
>  	pthread_cleanup_push(put_multipath_config, conf);
> -	while (again) {
> -		if (!reply) {
> -			fail = 1;
> -			break;
> -		}
> -
> -		c = reply;
> -		c += snprint_blacklist_report(conf, c, maxlen);
> -		again = (c == reply + maxlen);
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> +	fail = snprint_blacklist_report(conf, &reply) < 0;
>  	pthread_cleanup_pop(1);
>  
>  	if (fail)
>  		return 1;
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  	return 0;
>  }
>  
> @@ -1408,34 +1259,20 @@ cli_list_blacklist (void * v, char ** reply, int * len, void * data)
>  int
>  show_devices (char ** r, int * len, struct vectors *vecs)
>  {
> -	char *c = NULL;
> -	char *reply = NULL;
> -	unsigned int maxlen = INITIAL_REPLY_LEN;
> -	int again = 1;
> +	STRBUF_ON_STACK(reply);
>  	struct config *conf;
> -	int fail = 0;
> -
> -	reply = MALLOC(maxlen);
> +	bool fail;
>  
>  	conf = get_multipath_config();
>  	pthread_cleanup_push(put_multipath_config, conf);
> -	while (again) {
> -		if (!reply) {
> -			fail = 1;
> -			break;
> -		}
> -
> -		c = reply;
> -		c += snprint_devices(conf, c, maxlen, vecs);
> -		again = (c == reply + maxlen);
> -		REALLOC_REPLY(reply, again, maxlen);
> -	}
> +	fail = snprint_devices(conf, &reply, vecs) < 0;
>  	pthread_cleanup_pop(1);
>  
>  	if (fail)
>  		return 1;
> -	*r = reply;
> -	*len = (int)(c - reply + 1);
> +
> +	*len = (int)get_strbuf_len(&reply) + 1;
> +	*r = steal_strbuf_str(&reply);
>  
>  	return 0;
>  }
> -- 
> 2.32.0

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


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2021-08-30 20:47 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [dm-devel] [PATCH v2 2/9] libmultipath: strbuf: simple api for growing string buffers mwilck
2021-08-11 16:08   ` 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

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).