All of lore.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.