[Concept,06/21] vsprintf: Add support for the %pV format-specifier

Message ID 20251214175449.3799539-7-sjg@u-boot.org
State New
Headers
Series test: Add support for passing arguments to C unit tests |

Commit Message

Simon Glass Dec. 14, 2025, 5:54 p.m. UTC
  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(+)
  

Patch

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);