abuf: Add a way to printf() into a buffer
It is useful to format a string into a buffer, with the sizing handled
automatically. Add a function for this.
Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/include/abuf.h b/include/abuf.h
index bbb3c51..7872e9c 100644
--- a/include/abuf.h
+++ b/include/abuf.h
@@ -123,6 +123,27 @@
bool abuf_copy(const struct abuf *old, struct abuf *new);
/**
+ * abuf_printf() - Format a string and place it in an abuf
+ *
+ * @buf: The buffer to place the result into
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ * Return: the number of characters writtenwhich would be
+ * generated for the given input, excluding the trailing null,
+ * as per ISO C99.
+ *
+ * The abuf is expanded as necessary to fit the formated string
+ *
+ * See the vsprintf() documentation for format string extensions over C99.
+ *
+ * Returns: number of characters written (excluding trailing nul) on success,
+ * -E2BIG if the size exceeds 4K, -ENOMEM if out of memory, -EFAULT if there is
+ * an internal bug in the vsnprintf() implementation
+ */
+int abuf_printf(struct abuf *buf, const char *fmt, ...)
+ __attribute__ ((format (__printf__, 2, 3)));
+
+/**
* abuf_uninit_move() - Return the allocated contents and uninit the abuf
*
* This returns the abuf data to the caller, allocating it if necessary, so that
diff --git a/lib/abuf.c b/lib/abuf.c
index 28c748a..3a2fd17 100644
--- a/lib/abuf.c
+++ b/lib/abuf.c
@@ -10,8 +10,11 @@
#include <malloc.h>
#include <mapmem.h>
#include <string.h>
+#include <vsprintf.h>
#endif
+#include <errno.h>
+#include <stdarg.h>
#include <abuf.h>
void abuf_set(struct abuf *abuf, void *data, size_t size)
@@ -142,6 +145,38 @@
return true;
}
+int abuf_printf(struct abuf *buf, const char *fmt, ...)
+{
+ int maxlen = buf->size;
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vsnprintf(buf->data, buf->size, fmt, args);
+ va_end(args);
+
+ /* add the terminator */
+ len++;
+
+ if (len > 4096)
+ return -E2BIG;
+ if (len > maxlen) {
+ /* make more space and try again */
+ maxlen = len;
+ if (!abuf_realloc(buf, maxlen))
+ return -ENOMEM;
+ va_start(args, fmt);
+ len = vsnprintf(buf->data, maxlen, fmt, args);
+ va_end(args);
+
+ /* check there isn't anything strange going on */
+ if (len > maxlen)
+ return -EFAULT;
+ }
+
+ return len;
+}
+
void abuf_init_const(struct abuf *abuf, const void *data, size_t size)
{
/* for now there is no flag indicating that the abuf data is constant */
diff --git a/test/lib/abuf.c b/test/lib/abuf.c
index 96c77ed..97b128c 100644
--- a/test/lib/abuf.c
+++ b/test/lib/abuf.c
@@ -463,3 +463,64 @@
return 0;
}
LIB_TEST(lib_test_abuf_init_size, 0);
+
+/* Test abuf_printf() */
+static int lib_test_abuf_printf(struct unit_test_state *uts)
+{
+ struct abuf buf, fmt;
+ ulong start;
+ char *ptr;
+
+ start = ut_check_free();
+
+ /* start with a fresh buffer */
+ abuf_init(&buf);
+
+ /* check handling of out-of-memory condition */
+ malloc_enable_testing(0);
+ ut_asserteq(-ENOMEM, abuf_printf(&buf, "%s", ""));
+ malloc_enable_testing(1);
+
+ ut_asserteq(0, abuf_printf(&buf, "%s", ""));
+ ut_asserteq(1, buf.size);
+ ut_asserteq(true, buf.alloced);
+ ut_asserteq_str("", buf.data);
+
+ /* check expanding it, initially failing */
+ ut_asserteq(-ENOMEM, abuf_printf(&buf, "%s", "testing"));
+ malloc_disable_testing();
+
+ ut_asserteq(7, abuf_printf(&buf, "%s", "testing"));
+ ut_asserteq(8, buf.size);
+ ut_asserteq_str("testing", buf.data);
+
+ ut_asserteq(11, abuf_printf(&buf, "testing %d", 123));
+ ut_asserteq(12, buf.size);
+ ut_asserteq_str("testing 123", buf.data);
+
+ /* make it smaller; buffer should not shrink */
+ ut_asserteq(9, abuf_printf(&buf, "test %d", 456));
+ ut_asserteq(12, buf.size);
+ ut_asserteq_str("test 456", buf.data);
+
+ /* test the maximum size */
+ abuf_init(&fmt);
+ ut_assert(abuf_realloc(&fmt, 4100));
+ memset(fmt.data, 'x', 4100);
+ ptr = fmt.data;
+ ptr[4096] = '\0';
+
+ /* we are allowed up to 4K including the terminator */
+ ut_asserteq(-E2BIG, abuf_printf(&buf, "%s", ptr));
+ ptr[4095] = '\0';
+ ut_asserteq(4095, abuf_printf(&buf, "%s", ptr));
+
+ abuf_uninit(&fmt);
+ abuf_uninit(&buf);
+
+ /* Check for memory leaks */
+ ut_assertok(ut_check_delta(start));
+
+ return 0;
+}
+LIB_TEST(lib_test_abuf_printf, 0);