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 <s...@chromium.org> --- include/abuf.h | 21 +++++++++++++++++ lib/abuf.c | 35 ++++++++++++++++++++++++++++ test/lib/abuf.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/include/abuf.h b/include/abuf.h index 64189a4e32c..e761e87b584 100644 --- a/include/abuf.h +++ b/include/abuf.h @@ -122,6 +122,27 @@ bool abuf_realloc_inc(struct abuf *abuf, size_t inc); */ 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 * diff --git a/lib/abuf.c b/lib/abuf.c index 503f5004ff8..816f92587eb 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) @@ -133,6 +136,38 @@ bool abuf_copy(const struct abuf *old, struct abuf *copy) 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 fa6b007248c..1b16d79cc03 100644 --- a/test/lib/abuf.c +++ b/test/lib/abuf.c @@ -442,3 +442,64 @@ static int lib_test_abuf_copy(struct unit_test_state *uts) return 0; } LIB_TEST(lib_test_abuf_copy, 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); -- 2.43.0