]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tools/nolibc/printf: Support negative variable width and precision
authorDavid Laight <david.laight.linux@gmail.com>
Mon, 23 Mar 2026 11:22:47 +0000 (11:22 +0000)
committerThomas Weißschuh <linux@weissschuh.net>
Wed, 1 Apr 2026 16:02:37 +0000 (18:02 +0200)
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 <david.laight.linux@gmail.com>
Link: https://patch.msgid.link/20260323112247.3196-1-david.laight.linux@gmail.com
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
tools/include/nolibc/stdio.h
tools/testing/selftests/nolibc/nolibc-test.c

index 8f7e1948a651e0c604f3bb226880d8d1746debb3..b6d14a58cfe720d6372783fa40497b8983a91294 100644 (file)
@@ -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;
 
index 1efd10152e831f5cc2151a349e6356688badf79d..e6fef6eb1db1ce14a4792d36dd53ee2f6ccc66b0 100644 (file)
@@ -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;