]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools/nolibc/printf: Special case 0 and add support for %#x
authorDavid Laight <david.laight.linux@gmail.com>
Sun, 8 Mar 2026 11:37:38 +0000 (11:37 +0000)
committerThomas Weißschuh <linux@weissschuh.net>
Fri, 20 Mar 2026 16:57:08 +0000 (17:57 +0100)
The output for %#x is almost the same as that for %p, both output in
hexadecimal with a leading "0x".
However for zero %#x should just output "0" (the same as decimal and ocal).
For %p match glibc and output "(nil)" rather than "0x0" or "0".

Add tests for "%#x", "% d", "%+d" and passing NULL to "%p".

Signed-off-by: David Laight <david.laight.linux@gmail.com>
Acked-by: Willy Tarreau <w@1wt.eu>
Link: https://patch.msgid.link/20260308113742.12649-14-david.laight.linux@gmail.com
[Thomas: fix up testcases for musl libc]
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
tools/include/nolibc/stdio.h
tools/testing/selftests/nolibc/nolibc-test.c

index 65e113f135d451baf97229e0d0f0b411d75c367d..db714b1ce27444ef98f65f9ad0257104db5cab9b 100644 (file)
@@ -296,7 +296,7 @@ int fseek(FILE *stream, long offset, int whence)
  *  - %% generates a single %
  *  - %m outputs strerror(errno).
  *  - %X outputs a..f the same as %x.
- *  - The modifiers [#-0] are currently ignored.
+ *  - The modifiers [-0] are currently ignored.
  *  - No support for precision or variable widths.
  *  - No support for floating point or wide characters.
  *  - Invalid formats are copied to the output buffer.
@@ -410,8 +410,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                 * so that 'X' can be allowed through.
                 * 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same
                 * value for both.
+                *
+                * We need to check for "%p" or "%#x" later, merging here gives better code.
+                * But '#' collides with 'c' so shift right.
                 */
-               ch_flag = _NOLIBC_PF_FLAG(ch);
+               ch_flag = _NOLIBC_PF_FLAG(ch) | (flags & _NOLIBC_PF_FLAG('#')) >> 1;
                if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
                    _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p', 's')) {
                        /* 'long' is needed for pointer/string conversions and ltz lengths.
@@ -471,17 +474,30 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                         */
                        out = outbuf + 2;
 
-                       /* Convert the number to ascii in the required base. */
-                       if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
-                               /* Base 10 */
-                               len = u64toa_r(v, out);
-                       } else {
-                               /* Base 16 */
+                       if (v == 0) {
+                               /* There are special rules for zero. */
                                if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
-                                       /* "%p" needs "0x" prepending. */
-                                       sign_prefix = '0' << 8 | 'x';
+                                       /* "%p" match glibc, precision is ignored */
+                                       outstr = "(nil)";
+                                       len = 5;
+                                       goto do_output;
+                               }
+                               /* All other formats (including "%#x") just output "0". */
+                               out[0] = '0';
+                               len = 1;
+                       } else {
+                               /* Convert the number to ascii in the required base. */
+                               if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
+                                       /* Base 10 */
+                                       len = u64toa_r(v, out);
+                               } else {
+                                       /* Base 16 */
+                                       if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
+                                               /* "%p" and "%#x" need "0x" prepending. */
+                                               sign_prefix = '0' << 8 | 'x';
+                                       }
+                                       len = u64toh_r(v, out);
                                }
-                               len = u64toh_r(v, out);
                        }
 
                        /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
index 6fa2346c655a741997bfd69ea2d9b20fe21c410c..9644087267d135e516c1e8e99ea6e6d66eec06e8 100644 (file)
@@ -1833,6 +1833,7 @@ static int run_printf(int min, int max)
                CASE_TEST(string);       EXPECT_VFPRINTF(1, "foo", "%s", "foo"); break;
                CASE_TEST(number);       EXPECT_VFPRINTF(1, "1234", "%d", 1234); break;
                CASE_TEST(negnumber);    EXPECT_VFPRINTF(1, "-1234", "%d", -1234); break;
+               CASE_TEST(num_sign);     EXPECT_VFPRINTF(1, "| 1|+2|+3|+4|5|", "|% d|%+d|% +d|%+ d|%#d|", 1, 2, 3, 4, 5); break;
                CASE_TEST(unsigned);     EXPECT_VFPRINTF(1, "12345", "%u", 12345); break;
                CASE_TEST(signed_max);   EXPECT_VFPRINTF(1, "2147483647", "%i", ~0u >> 1); break;
                CASE_TEST(signed_min);   EXPECT_VFPRINTF(1, "-2147483648", "%i", (~0u >> 1) + 1); break;
@@ -1840,7 +1841,9 @@ static int run_printf(int min, int max)
                CASE_TEST(char);         EXPECT_VFPRINTF(1, "c", "%c", 'c'); break;
                CASE_TEST(hex_nolibc);   EXPECT_VFPRINTF(is_nolibc, "|f|d|", "|%x|%X|", 0xf, 0xd); break;
                CASE_TEST(hex_libc);     EXPECT_VFPRINTF(!is_nolibc, "|f|D|", "|%x|%X|", 0xf, 0xd); break;
+               CASE_TEST(hex_alt);      EXPECT_VFPRINTF(1, "|0x1|  0x2|    0|", "|%#x|%#5x|%#5x|", 1, 2, 0); break;
                CASE_TEST(pointer);      EXPECT_VFPRINTF(1, "0x1", "%p", (void *) 0x1); break;
+               CASE_TEST(pointer_NULL); EXPECT_VFPRINTF(is_nolibc || is_glibc, "(nil)", "%p", (void *)0); break;
                CASE_TEST(percent);      EXPECT_VFPRINTF(1, "a%d42%69%", "a%%d%d%%%d%%", 42, 69); break;
                CASE_TEST(perc_qual);    EXPECT_VFPRINTF(is_nolibc || is_glibc, "a%d2", "a%-14l%d%d", 2); break;
                CASE_TEST(invalid);      EXPECT_VFPRINTF(is_nolibc || is_glibc, "a%12yx3%y42%P", "a%12yx%d%y%d%P", 3, 42); break;