From: David Laight Date: Sun, 8 Mar 2026 11:37:40 +0000 (+0000) Subject: tools/nolibc/printf: Add support for zero padding and field precision X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d3d3f64f8e964f8af6ac72294e65caad5acc452e;p=thirdparty%2Fkernel%2Flinux.git tools/nolibc/printf: Add support for zero padding and field precision 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 Acked-by: Willy Tarreau 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 --- diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index c8a463ed73c7e..1294004afb300 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -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; diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 2a83d5ea2d464..9695d9b359cd3 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -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;