vsprintf: Add support for the %pV format-specifier

Add support for the %pV format-specifier which allows printing a
struct va_format. This is used by the Linux kernel for recursive
printf() formatting and is needed by the ext4l filesystem driver.

Add the struct to include/linux/printk.h to match the kernel location.

Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
This commit is contained in:
Simon Glass
2025-12-06 15:24:02 -07:00
parent d843258d52
commit 2ddc96ae88
4 changed files with 84 additions and 0 deletions

View File

@@ -258,6 +258,25 @@ Pointers
lower case (requires CONFIG_LIB_UUID), e.g. 'system' for a GUID
identifying an EFI system partition.
%pV
prints a struct va_format, which contains a format string and a va_list
pointer. This allows recursive printf formatting and is used for
implementing custom print functions that wrap printf.
.. code-block:: c
void my_print(const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
printf("prefix: %pV\n", &vaf);
va_end(args);
}
Tiny printf
-----------

View File

@@ -84,4 +84,9 @@
#define printk_once(fmt, ...) \
printk(fmt, ##__VA_ARGS__)
struct va_format {
const char *fmt;
va_list *va;
};
#endif

View File

@@ -25,6 +25,7 @@
#include <linux/err.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/printk.h>
/* we use this so that we can do without the ctype library */
#define is_digit(c) ((c) >= '0' && (c) <= '9')
@@ -508,6 +509,17 @@ static char *pointer(const char *fmt, char *buf, char *end, void *ptr,
return uuid_string(buf, end, ptr, field_width, precision,
flags, fmt);
#endif
case 'V':
{
const struct va_format *vaf = ptr;
va_list va;
va_copy(va, *vaf->va);
buf += vsnprintf(buf, end > buf ? end - buf : 0,
vaf->fmt, va);
va_end(va);
return buf;
}
default:
break;
}

View File

@@ -9,8 +9,10 @@
#include <log.h>
#include <mapmem.h>
#include <version_string.h>
#include <stdarg.h>
#include <stdio.h>
#include <vsprintf.h>
#include <linux/printk.h>
#include <test/common.h>
#include <test/test.h>
#include <test/ut.h>
@@ -376,3 +378,49 @@ static int snprint(struct unit_test_state *uts)
return 0;
}
COMMON_TEST(snprint, 0);
/* Helper function to test %pV format specifier */
static int print_with_va_format(char *buf, size_t size, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
int ret;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
ret = snprintf(buf, size, "prefix: %pV :suffix", &vaf);
va_end(args);
return ret;
}
/* Test printing with %pV (struct va_format) */
static int print_va_format(struct unit_test_state *uts)
{
char str[64];
int ret;
/* Basic string */
ret = print_with_va_format(str, sizeof(str), "hello");
ut_asserteq_str("prefix: hello :suffix", str);
ut_asserteq(21, ret);
/* String with arguments */
ret = print_with_va_format(str, sizeof(str), "value=%d", 42);
ut_asserteq_str("prefix: value=42 :suffix", str);
ut_asserteq(24, ret);
/* Multiple arguments */
ret = print_with_va_format(str, sizeof(str), "%s: %d/%d", "test", 1, 2);
ut_asserteq_str("prefix: test: 1/2 :suffix", str);
ut_asserteq(25, ret);
/* Truncation */
ret = print_with_va_format(str, 15, "hello world");
ut_asserteq_str("prefix: hello ", str);
ut_asserteq(27, ret);
return 0;
}
COMMON_TEST(print_va_format, 0);