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, "Cheng Li" <lechain@gmail.com>
Cc: David Laight <david.laight.linux@gmail.com>
Subject: [PATCH v3 next 13/17] tools/nolibc/printf: Add support for conversion flags
Date: Mon, 23 Feb 2026 10:17:31 +0000	[thread overview]
Message-ID: <20260223101735.2922-14-david.laight.linux@gmail.com> (raw)
In-Reply-To: <20260223101735.2922-1-david.laight.linux@gmail.com>

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

Add support for:
  '+' and ' ' Sign characters for non-negative numbers.
  '#' adds 0x to hex numbers.
  outputting "(nil)" for NULL pointers.

Special code for 0 is needed because "%#x" needs to generate "0" not "0x0".

The code is changed to use the length returned by u64toh_r() and
prepend the sign (or "0x") after conversion to reduce the changes for
the next patch that inserts zero padding between the sign and digits.

Add tests for "%#x", "% d", "%+d" and passing NULL to "%p".

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

Changes for v3:
- Was patch 7 in v2.
- Changes for left alignment moved to the next patch.
- Rename 'sign' 'sign_prefix'.
- Include tests 

Changes for v2:         
- Add support for left justifying fields (removed from patch 6).
- Merge in the changes from the old patch 8.
- Add support for "%#x" (formally part of patch 9).

 tools/include/nolibc/stdio.h                 | 68 +++++++++++++++-----
 tools/testing/selftests/nolibc/nolibc-test.c |  3 +
 2 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 6dd27473ab7f..c21abe085fa6 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -296,7 +296,7 @@ int fseek(FILE *stream, long offset, int whence)
  *  - %% generates a single %
  *  - %m outputs strerror(errno).
  *  - %X outputs a..f the same as %x.
- *  - The modifiers [#-+ 0] are currently ignored.
+ *  - 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.
@@ -345,9 +345,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 	long long signed_v;
 	int written, width, len;
 	unsigned int flags, ch_flag;
-	char outbuf[21];
+	char outbuf[2 + 22 + 1];
 	char *out;
 	const char *outstr;
+	unsigned int sign_prefix;
 
 	written = 0;
 	while (1) {
@@ -409,8 +410,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 		 * so that 'X' can be allowed through.
 		 * 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same
 		 * value for both.
+		 *
+		 * We need to check for "%p" or "%#x" later, merging here gives better code.
+		 * But '#' collides with 'c' so shift right.
 		 */
-		ch_flag = _NOLIBC_PF_FLAG(ch);
+		ch_flag = _NOLIBC_PF_FLAG(ch) | (flags & _NOLIBC_PF_FLAG('#')) >> 1;
 		if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
 		    _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'x', 'p', 's')) {
 			/* 'long' is needed for pointer/string conversions and ltz lengths.
@@ -446,32 +450,64 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 				goto do_strlen_output;
 			}
 
-			out = outbuf;
+			/* The 'sign_prefix' can be zero, one or two ("0x") characters. */
+			sign_prefix = 0;
 
 			if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i')) {
 				/* "%d" and "%i" - signed decimal numbers. */
 				if (signed_v < 0) {
-					*out++ = '-';
+					sign_prefix = '-';
 					v = -(signed_v + 1);
 					v++;
+				} else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '+')) {
+					sign_prefix = '+';
+				} else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, ' ')) {
+					sign_prefix = ' ';
 				}
 			}
 
-			/* Convert the number to ascii in the required base. */
-			if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
-				/* Base 10 */
-				u64toa_r(v, out);
-			} else {
-				/* Base 16 */
+			/* The value is converted offset into the buffer so that
+			 * 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;
+
+			if (v == 0) {
+				/* There are special rules for zero. */
 				if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
-					*(out++) = '0';
-					*(out++) = 'x';
+					/* "%p" match glibc, precision is ignored */
+					outstr = "(nil)";
+					len = 5;
+					goto do_output;
+				}
+				/* All other formats (including "%#x") just output "0". */
+				out[0] = '0';
+				len = 1;
+			} else {
+				/* Convert the number to ascii in the required base. */
+				if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
+					/* Base 10 */
+					len = u64toa_r(v, out);
+				} else {
+					/* Base 16 */
+					if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
+						/* "%p" and "%#x" need "0x" prepending. */
+						sign_prefix = 'x' | '0' << 8;
+					}
+					len = u64toh_r(v, out);
 				}
-				u64toh_r(v, out);
 			}
 
-			outstr = outbuf;
-			goto do_strlen_output;
+			/* 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. */
+				_NOLIBC_OPTIMIZER_HIDE_VAR(len);
+				len++;
+				*--out = sign_prefix;
+			}
+			outstr = out;
+			goto do_output;
 		}
 
 		if (ch == 'm') {
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 498d3125eb24..f746711af777 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1832,6 +1832,7 @@ static int run_printf(int min, int max)
 		CASE_TEST(string);       EXPECT_VFPRINTF(1, "foo", "%s", "foo"); break;
 		CASE_TEST(number);       EXPECT_VFPRINTF(1, "1234", "%d", 1234); break;
 		CASE_TEST(negnumber);    EXPECT_VFPRINTF(1, "-1234", "%d", -1234); break;
+		CASE_TEST(num_sign);     EXPECT_VFPRINTF(1, "| 1|+2|+3|+4|5|", "|% d|%+d|% +d|%+ d|%#d|", 1, 2, 3, 4, 5); break;
 		CASE_TEST(unsigned);     EXPECT_VFPRINTF(1, "12345", "%u", 12345); break;
 		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;
@@ -1840,6 +1841,7 @@ static int run_printf(int min, int max)
 		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(pointer);      EXPECT_VFPRINTF(1, "0x1", "%p", (void *) 0x1); break;
+		CASE_TEST(pointer_NULL); EXPECT_VFPRINTF(1, "(nil)", "%p", (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(1, "a%d2", "a%-14l%d%d", 2); break;
 		CASE_TEST(invalid);      EXPECT_VFPRINTF(1, "a%12yx3%y42%P", "a%12yx%d%y%d%P", 3, 42); break;
@@ -1850,6 +1852,7 @@ static int run_printf(int min, int max)
 		CASE_TEST(string_width); EXPECT_VFPRINTF(1, "         1", "%10s", "1"); break;
 		CASE_TEST(number_width); EXPECT_VFPRINTF(1, "         1", "%10d", 1); break;
 		CASE_TEST(width_trunc);  EXPECT_VFPRINTF(1, "                             1", "%30d", 1); break;
+		CASE_TEST(hex_alt);      EXPECT_VFPRINTF(1, "|0x1|  0x2|    0|", "|%#x|%#5x|%#5x|", 1, 2, 0); break;
 		CASE_TEST(errno);        EXPECT_VFPRINTF(is_nolibc, "22:errno=22", "%d:%m", errno=22); break;
 		CASE_TEST(errno-neg);    EXPECT_VFPRINTF(is_nolibc, "-22:   errno=-22", "%d:%12m", errno=-22); break;
 		CASE_TEST(scanf);        EXPECT_ZR(1, test_scanf()); break;
-- 
2.39.5


  parent reply	other threads:[~2026-02-23 10:35 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-23 10:17 [PATCH v3 next 00/17] Enhance printf() david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 01/17] tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h david.laight.linux
2026-02-25 21:25   ` Thomas Weißschuh
2026-02-25 22:17     ` David Laight
2026-02-25 22:24       ` Thomas Weißschuh
2026-02-23 10:17 ` [PATCH v3 next 02/17] tools/nolibc: Optimise and common up the number to ascii functions david.laight.linux
2026-02-25 21:40   ` Thomas Weißschuh
2026-02-25 22:09     ` David Laight
2026-02-23 10:17 ` [PATCH v3 next 03/17] selftests/nolibc: Fix build with host headers and libc david.laight.linux
2026-02-25 21:24   ` Thomas Weißschuh
2026-02-23 10:17 ` [PATCH v3 next 04/17] selftests/nolibc: Improve reporting of vfprintf() errors david.laight.linux
2026-02-25 21:56   ` Thomas Weißschuh
2026-02-26 10:12     ` David Laight
2026-02-26 21:39       ` Thomas Weißschuh
2026-02-23 10:17 ` [PATCH v3 next 05/17] tools/nolibc: Implement strerror() in terms of strerror_r() david.laight.linux
2026-02-25 22:09   ` Thomas Weißschuh
2026-02-25 22:58     ` David Laight
2026-02-23 10:17 ` [PATCH v3 next 06/17] tools/nolibc/printf: Change variables 'c' to 'ch' and 'tmpbuf[]' to 'outbuf[]' david.laight.linux
2026-02-25 22:23   ` Thomas Weißschuh
2026-02-23 10:17 ` [PATCH v3 next 07/17] tools/nolibc/printf: Move snprintf length check to callback david.laight.linux
2026-02-25 22:37   ` Thomas Weißschuh
2026-02-25 23:12     ` David Laight
2026-02-26 21:29       ` Thomas Weißschuh
2026-02-26 22:11         ` David Laight
2026-02-23 10:17 ` [PATCH v3 next 08/17] tools/nolibc/printf: Output pad characters in 16 byte chunks david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 09/17] tools/nolibc/printf: Simplify __nolibc_printf() david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 10/17] tools/nolibc/printf: Use goto and reduce indentation david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 11/17] tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion chars david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 12/17] tools/nolibc/printf: Handle "%s" with the numeric formats david.laight.linux
2026-02-23 10:17 ` david.laight.linux [this message]
2026-02-23 10:17 ` [PATCH v3 next 14/17] tools/nolibc/printf: Add support for left aligning fields david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 16/17] tools/nolibc/printf: Add support for octal output david.laight.linux
2026-02-23 10:17 ` [PATCH v3 next 17/17] selftests/nolibc: Use printf variable field widths and precisions david.laight.linux

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=20260223101735.2922-14-david.laight.linux@gmail.com \
    --to=david.laight.linux@gmail.com \
    --cc=lechain@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