From: David Laight Date: Mon, 23 Mar 2026 11:22:47 +0000 (+0000) Subject: tools/nolibc/printf: Support negative variable width and precision X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1dff9ac2c85860af67a74777fa6d31c2ad07a15a;p=thirdparty%2Fkernel%2Flinux.git tools/nolibc/printf: Support negative variable width and precision For (eg) "%*.*s" treat a negative field width as a request to left align the output (the same as the '-' flag), and a negative precision to request the default precision. Set the default precision to -1 (not INT_MAX) and add explicit checks to the string handling for negative values (makes the tet unsigned). For numeric output check for 'precision >= 0' instead of testing _NOLIBC_PF_FLAGS_CONTAIN(flags, '.'). This needs an inverted test, some extra goto and removes an indentation. The changed conditionals fix printf("%0-#o", 0) - but '0' and '-' shouldn't both be specified. Signed-off-by: David Laight Link: https://patch.msgid.link/20260323112247.3196-1-david.laight.linux@gmail.com Signed-off-by: Thomas Weißschuh --- diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 8f7e1948a651e..b6d14a58cfe72 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -347,6 +347,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list char *out; const char *outstr; unsigned int sign_prefix; + int got_width; written = 0; while (1) { @@ -377,23 +378,28 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } /* Width and precision */ - for (;; ch = *fmt++) { + for (got_width = 0;; ch = *fmt++) { if (ch == '*') { - precision = va_arg(args, unsigned int); + precision = va_arg(args, int); ch = *fmt++; } else { for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++) precision = precision * 10 + (ch - '0'); } - if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) + if (got_width) break; width = precision; if (ch != '.') { /* Default precision for strings */ - precision = INT_MAX; + precision = -1; break; } - flags |= _NOLIBC_PF_FLAG('.'); + got_width = 1; + } + /* A negative width (e.g. from "%*s") requests left justify. */ + if (width < 0) { + width = -width; + flags |= _NOLIBC_PF_FLAG('-'); } /* Length modifier. @@ -457,7 +463,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list if (!outstr) { outstr = "(null)"; /* Match glibc, nothing output if precision too small */ - len = precision >= 6 ? 6 : 0; + len = precision < 0 || precision >= 6 ? 6 : 0; goto do_output; } goto do_strlen_output; @@ -533,32 +539,34 @@ 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) { + if (precision < 0) { + /* No explicit precision (or negative from "%.*s"). */ + if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '0')) + goto no_zero_padding; + if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-')) + /* Left justify overrides zero pad */ + goto no_zero_padding; + /* 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 (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'; } } + 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'; + } +no_zero_padding: /* %#o has set sign_prefix to '0', but we don't want so add an extra * leading zero here. @@ -603,7 +611,7 @@ prepend_sign: do_strlen_output: /* Open coded strnlen() (slightly smaller). */ - for (len = 0; len < precision; len++) + for (len = 0; precision < 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 1efd10152e831..e6fef6eb1db1c 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1859,7 +1859,7 @@ static int run_printf(int min, int max) CASE_TEST(char); EXPECT_VFPRINTF(1, "|c|d| e|", "|%c|%.0c|%4c|", 'c', 'd', 'e'); break; CASE_TEST(octal); EXPECT_VFPRINTF(1, "|17| 0033||", "|%o|%6.4o|%.0o|", 017, 033, 0); break; CASE_TEST(octal_max); EXPECT_VFPRINTF(1, "1777777777777777777777", "%llo", ~0ULL); break; - CASE_TEST(octal_alt); EXPECT_VFPRINTF(1, "|0|01|02|034|0|", "|%#o|%#o|%#02o|%#02o|%#.0o|", 0, 1, 2, 034, 0); break; + CASE_TEST(octal_alt); EXPECT_VFPRINTF(1, "|0|01|02|034|0|0|", "|%#o|%#o|%#02o|%#02o|%#.0o|%0-#o|", 0, 1, 2, 034, 0, 0); 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; @@ -1877,6 +1877,7 @@ static int run_printf(int min, int max) CASE_TEST(truncation); EXPECT_VFPRINTF(1, "012345678901234567890123456789", "%s", "012345678901234567890123456789"); 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(string_var); EXPECT_VFPRINTF(1, "| ab|ef | ij|kl |", "|%*.*s|%*.*s|%*.*s|%*.*s|", 3, 2, "abcd", -3, 2, "efgh", 3, -1, "ij", -3, -1, "kl"); 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; @@ -1890,7 +1891,7 @@ static int run_printf(int min, int max) 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(number_var); EXPECT_VFPRINTF(1, "| -00005|5 |", "|%*.*d|%*.*d|", 10, 5, -5, -2, -10, 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;