public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: david.laight.linux@gmail.com
To: "Willy Tarreau" <w@1wt.eu>,
	"Thomas Weißschuh" <linux@weissschuh.net>,
	linux-kernel@vger.kernel.org
Cc: David Laight <david.laight.linux@gmail.com>
Subject: [PATCH 1/1] tools/nolibc/printf: Support negative variable width and precision
Date: Mon, 23 Mar 2026 11:22:47 +0000	[thread overview]
Message-ID: <20260323112247.3196-1-david.laight.linux@gmail.com> (raw)

From: David Laight <david.laight.linux@gmail.com>

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.

Best viewed with 'git diff -b' after being commited.

Additional test cases added.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
---

I missed this bit in the earlier patches.
Size wise it is pretty neutral. It really seems to depend on how many registers
get saved across the call to _nolibc_u64toa_base() - gcc doesn't seem to use
the correct registers to avoid spills.

I did look at whether making 'width' negative at the top was better than
keeping a '-' flag - but it bloats things because you need the absolute value
at the bottom.

 tools/include/nolibc/stdio.h                 | 68 +++++++++++---------
 tools/testing/selftests/nolibc/nolibc-test.c |  5 +-
 2 files changed, 41 insertions(+), 32 deletions(-)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 8f7e1948a651..b6d14a58cfe7 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 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 
 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 0ca695acbc44..83082486b8c3 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1825,7 +1825,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;
@@ -1843,6 +1843,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;
@@ -1856,7 +1857,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;
-- 
2.39.5


             reply	other threads:[~2026-03-23 11:22 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-23 11:22 david.laight.linux [this message]
2026-03-25 20:37 ` [PATCH 1/1] tools/nolibc/printf: Support negative variable width and precision Thomas Weißschuh
2026-03-25 23:03   ` David Laight

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260323112247.3196-1-david.laight.linux@gmail.com \
    --to=david.laight.linux@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@weissschuh.net \
    --cc=w@1wt.eu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox