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>
This commit is contained in:
Simon Glass
2025-05-02 08:46:04 -06:00
parent d58cebbbc7
commit 4f4b9477f4
3 changed files with 117 additions and 0 deletions

View File

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

View File

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

View File

@@ -463,3 +463,64 @@ static int lib_test_abuf_init_size(struct unit_test_state *uts)
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);