From: Simon Glass <simon.glass@canonical.com> 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> --- doc/develop/printf.rst | 19 +++++++++++++++++ include/linux/printk.h | 5 +++++ lib/vsprintf.c | 12 +++++++++++ test/common/print.c | 48 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/doc/develop/printf.rst b/doc/develop/printf.rst index edda8b24a8e..42de22c8555 100644 --- a/doc/develop/printf.rst +++ b/doc/develop/printf.rst @@ -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 ----------- diff --git a/include/linux/printk.h b/include/linux/printk.h index 5e85513853c..e28cef0ac31 100644 --- a/include/linux/printk.h +++ b/include/linux/printk.h @@ -84,4 +84,9 @@ #define printk_once(fmt, ...) \ printk(fmt, ##__VA_ARGS__) +struct va_format { + const char *fmt; + va_list *va; +}; + #endif diff --git a/lib/vsprintf.c b/lib/vsprintf.c index c072b44140b..0f2c303b138 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -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; } diff --git a/test/common/print.c b/test/common/print.c index 3fe24dc3e9d..9bd409d4b66 100644 --- a/test/common/print.c +++ b/test/common/print.c @@ -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); -- 2.43.0