]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools/nolibc/printf: Add support for zero padding and field precision
authorDavid Laight <david.laight.linux@gmail.com>
Sun, 8 Mar 2026 11:37:40 +0000 (11:37 +0000)
committerThomas Weißschuh <linux@weissschuh.net>
Fri, 20 Mar 2026 16:58:10 +0000 (17:58 +0100)
Includes support for variable field widths (eg "%*.*d").

Zero padding is limited to 31 zero characters.
This is wider than the largest numeric field so shouldn't be a problem.

All the standard printf formats are now supported except octal
and floating point.

Add tests for new features

Signed-off-by: David Laight <david.laight.linux@gmail.com>
Acked-by: Willy Tarreau <w@1wt.eu>
Link: https://patch.msgid.link/20260308113742.12649-16-david.laight.linux@gmail.com
[Thomas: fixup 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 c8a463ed73c7e9b9deb88947ed19eb4fd94169e1..1294004afb300da176514a31516e0ff84d80b8be 100644 (file)
@@ -292,12 +292,10 @@ int fseek(FILE *stream, long offset, int whence)
 
 
 /* printf(). Supports most of the normal integer and string formats.
- *  - %[#-+ 0][width][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
+ *  - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%}
  *  - %% generates a single %
  *  - %m outputs strerror(errno).
  *  - %X outputs a..f the same as %x.
- *  - 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.
  *
@@ -343,9 +341,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
        char ch;
        unsigned long long v;
        long long signed_v;
-       int written, width, len;
+       int written, width, precision, len;
        unsigned int flags, ch_flag;
-       char outbuf[2 + 22 + 1];
+       char outbuf[2 + 31 + 22 + 1];
        char *out;
        const char *outstr;
        unsigned int sign_prefix;
@@ -378,12 +376,24 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                        flags |= ch_flag;
                }
 
-               /* width */
-               while (ch >= '0' && ch <= '9') {
-                       width *= 10;
-                       width += ch - '0';
-
-                       ch = *fmt++;
+               /* Width and precision */
+               for (;; ch = *fmt++) {
+                       if (ch == '*') {
+                               precision = va_arg(args, unsigned int);
+                               ch = *fmt++;
+                       } else {
+                               for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++)
+                                       precision = precision * 10 + (ch - '0');
+                       }
+                       if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '.'))
+                               break;
+                       width = precision;
+                       if (ch != '.') {
+                               /* Default precision for strings */
+                               precision = INT_MAX;
+                               break;
+                       }
+                       flags |= _NOLIBC_PF_FLAG('.');
                }
 
                /* Length modifier.
@@ -444,8 +454,12 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                        if (ch == 's') {
                                /* "%s" - character string. */
                                outstr = (const char  *)(uintptr_t)v;
-                               if (!outstr)
+                               if (!outstr) {
                                        outstr = "(null)";
+                                       /* Match glibc, nothing output if precision too small */
+                                       len = precision >= 6 ? 6 : 0;
+                                       goto do_output;
+                               }
                                goto do_strlen_output;
                        }
 
@@ -468,11 +482,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                        }
 
                        /* The value is converted offset into the buffer so that
-                        * the sign/prefix can be added in front.
+                        * 31 zero pad characters and the sign/prefix can be added in front.
                         * The longest digit string is 22 + 1 for octal conversions, the
                         * space is reserved even though octal isn't currently supported.
                         */
-                       out = outbuf + 2;
+                       out = outbuf + 2 + 31;
 
                        if (v == 0) {
                                /* There are special rules for zero. */
@@ -482,6 +496,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                                        len = 5;
                                        goto do_output;
                                }
+                               if (!precision) {
+                                       /* Explicit %nn.0d, no digits output */
+                                       len = 0;
+                                       goto prepend_sign;
+                               }
                                /* All other formats (including "%#x") just output "0". */
                                out[0] = '0';
                                len = 1;
@@ -500,6 +519,35 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                                }
                        }
 
+                       /* Add zero padding */
+                       if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '0', '.')) {
+                               if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) {
+                                       if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-'))
+                                               /* Left justify overrides zero pad */
+                                               goto prepend_sign;
+                                       /* eg "%05d", Zero pad to field width less sign.
+                                        * Note that precision can end up negative so all
+                                        * the variables have to be 'signed int'.
+                                        */
+                                       precision = width;
+                                       if (sign_prefix) {
+                                               precision--;
+                                               if (sign_prefix >= 256)
+                                                       precision--;
+                                       }
+                               }
+                               if (precision > 31)
+                                       /* Don't run off the start of outbuf[], arbitrary limit
+                                        * longer than the longest number field. */
+                                       precision = 31;
+                               for (; len < precision; len++) {
+                                       /* Stop gcc generating horrid code and memset(). */
+                                       _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+                                       *--out = '0';
+                               }
+                       }
+
+prepend_sign:
                        /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
                        for (; sign_prefix; sign_prefix >>= 8) {
                                /* Force gcc to increment len inside the loop. */
@@ -534,8 +582,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
                goto do_output;
 
 do_strlen_output:
-               /* Open coded strlen() (slightly smaller). */
-               for (len = 0;; len++)
+               /* Open coded strnlen() (slightly smaller). */
+               for (len = 0; len < precision; len++)
                        if (!outstr[len])
                                break;
 
index 2a83d5ea2d46475819ce6b1230164305e4d0a48c..9695d9b359cd3825471a9297cce617c9e3809827 100644 (file)
@@ -1838,12 +1838,15 @@ static int run_printf(int min, int max)
                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;
                CASE_TEST(unsigned_max); EXPECT_VFPRINTF(1, "4294967295", "%u", ~0u); break;
-               CASE_TEST(char);         EXPECT_VFPRINTF(1, "c", "%c", 'c'); break;
+               CASE_TEST(char);         EXPECT_VFPRINTF(1, "|c|d|   e|", "|%c|%.0c|%4c|", 'c', 'd', 'e'); 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(hex_alt_prec); EXPECT_VFPRINTF(1, "| 0x02|0x03| 0x123|", "|%#5.2x|%#04x|%#6.2x|", 2, 3, 0x123); break;
+               CASE_TEST(hex_0_alt);    EXPECT_VFPRINTF(1, "|0|0000|   00|", "|%#x|%#04x|%#5.2x|", 0, 0, 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(pointer_NULL); EXPECT_VFPRINTF(is_nolibc || is_glibc, "|(nil)|(nil)|", "|%p|%.4p|", (void *)0, (void *)0); break;
+               CASE_TEST(string_NULL);  EXPECT_VFPRINTF(is_nolibc || is_glibc, "|(null)||(null)|", "|%s|%.5s|%.6s|", (void *)0, (void *)0, (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;
@@ -1852,10 +1855,22 @@ static int run_printf(int min, int max)
                CASE_TEST(uintmax_max);  EXPECT_VFPRINTF(1, "18446744073709551615", "%ju", ~0ULL); break;
                CASE_TEST(truncation);   EXPECT_VFPRINTF(1, "0123456789012345678901234", "%s", "0123456789012345678901234"); break;
                CASE_TEST(string_width); EXPECT_VFPRINTF(1, "         1", "%10s", "1"); break;
+               CASE_TEST(string_trunc); EXPECT_VFPRINTF(1, "     12345", "%10.5s", "1234567890"); break;
                CASE_TEST(number_width); EXPECT_VFPRINTF(1, "         1", "%10d", 1); break;
                CASE_TEST(number_left);  EXPECT_VFPRINTF(1, "|-5      |", "|%-8d|", -5); break;
                CASE_TEST(string_align); EXPECT_VFPRINTF(1, "|foo     |", "|%-8s|", "foo"); break;
                CASE_TEST(width_trunc);  EXPECT_VFPRINTF(1, "                        1", "%25d", 1); break;
+               CASE_TEST(width_tr_lft); EXPECT_VFPRINTF(1, "1                             ", "%-30d", 1); break;
+               CASE_TEST(number_pad);   EXPECT_VFPRINTF(1, "0000000005", "%010d", 5); break;
+               CASE_TEST(number_pad);   EXPECT_VFPRINTF(1, "|0000000005|0x1234|", "|%010d|%#01x|", 5, 0x1234); break;
+               CASE_TEST(num_pad_neg);  EXPECT_VFPRINTF(1, "-000000005", "%010d", -5); break;
+               CASE_TEST(num_pad_hex);  EXPECT_VFPRINTF(1, "00fffffffb", "%010x", -5); break;
+               CASE_TEST(num_pad_trunc);EXPECT_VFPRINTF(is_nolibc, "    0000000000000000000000000000005", "%035d", 5); break;  /* max 31 '0' can be added */
+               CASE_TEST(num_p_tr_libc);EXPECT_VFPRINTF(!is_nolibc, "00000000000000000000000000000000005", "%035d", 5); break;
+               CASE_TEST(number_prec);  EXPECT_VFPRINTF(1, "     00005", "%10.5d", 5); break;
+               CASE_TEST(num_prec_neg); EXPECT_VFPRINTF(1, "    -00005", "%10.5d", -5); break;
+               CASE_TEST(num_prec_var); EXPECT_VFPRINTF(1, "    -00005", "%*.*d", 10, 5, -5); break;
+               CASE_TEST(num_0_prec_0); EXPECT_VFPRINTF(1, "|| |+||||", "|%.0d|% .0d|%+.0d|%.0u|%.0x|%#.0x|", 0, 0, 0, 0, 0, 0); break;
                CASE_TEST(errno);        errno = 22; EXPECT_VFPRINTF(is_nolibc, "errno=22", "%m"); break;
                CASE_TEST(errno-neg);    errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22   ", "%-12m"); break;
                CASE_TEST(scanf);        EXPECT_ZR(1, test_scanf()); break;