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 next 09/12] tools/nolibc/printf: support precision and zero padding
Date: Tue,  3 Feb 2026 10:29:57 +0000	[thread overview]
Message-ID: <20260203103000.20206-10-david.laight.linux@gmail.com> (raw)
In-Reply-To: <20260203103000.20206-1-david.laight.linux@gmail.com>

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

Support '*' for both length and precision.
Output "(nil)" for NULL pointers (matches glibc).
Output "0" not "0x0" for printf("%#x", 0).
Output no digits for printf("%.0d", 0).

Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
 tools/include/nolibc/stdio.h | 114 +++++++++++++++++++++++++++--------
 1 file changed, 89 insertions(+), 25 deletions(-)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index d8b6184ff0f3..bad528921590 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -240,10 +240,15 @@ char *fgets(char *s, int size, FILE *stream)
 }
 
 
-/* simple printf(). It supports the following formats:
- *  - %[-+ ][width][{l,t,z,ll,L,j,q}]{d,i,u,c,x,X,p,s,m}
- *  - %%
- *  - invalid formats are copied to the output buffer
+/* printf(). Supports most of the normal integer and string formats.
+ *  - %[#0-+ ][width|*[.precision|*]][{l,t,z,ll,L,j,q}]{d,i,u,c,x,X,p,s,m}
+ *  - %% generates a single %
+ *  - %m outputs strerror(errno).
+ *  - # only affects %x and prepends 0x to non-zero values.
+ *  - %o (octal) isn't supported.
+ *  - %X outputs a..f the same as %x.
+ *  - No support for wide characters.
+ *  - invalid formats are copied to the output buffer.
  */
 typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);
 
@@ -252,7 +257,7 @@ static __attribute__((unused, format(printf, 3, 0)))
 int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list args)
 {
 	char c;
-	int len, written, width;
+	int len, written, width, precision;
 	unsigned int flags;
 	char tmpbuf[32 + 24];
 	const char *outstr;
@@ -277,17 +282,30 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 
 			/* Flag characters */
 			for (; c >= 0x20 && c <= 0x3f; c = *fmt++) {
-				if ((__PF_FLAG(c) & (__PF_FLAG('-') | __PF_FLAG(' ') | __PF_FLAG('+'))) == 0)
+				if ((__PF_FLAG(c) & (__PF_FLAG('-') | __PF_FLAG(' ') | __PF_FLAG('+') |
+						     __PF_FLAG('#') | __PF_FLAG('0'))) == 0)
 					break;
 				flags |= __PF_FLAG(c);
 			}
 
-			/* width */
-			while (c >= '0' && c <= '9') {
-				width *= 10;
-				width += c - '0';
-
-				c = *fmt++;
+			/* width and precision */
+			for (;; c = *fmt++) {
+				if (c == '*') {
+					precision = va_arg(args, unsigned int);
+					c = *fmt++;
+				} else {
+					for (precision = 0; c >= '0' && c <= '9'; c = *fmt++)
+						precision = precision * 10 + (c - '0');
+				}
+				if (flags & __PF_FLAG('.'))
+					break;
+				width = precision;
+				if (c != '.') {
+					/* Default precision for strings */
+					precision = INT_MAX;
+					break;
+				}
+				flags |= __PF_FLAG('.');
 			}
 
 			/* Length modifiers are lower case except 'L' which is the same a 'q' */
@@ -308,7 +326,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 			if (!((c >= 'a' && c <= 'z') || (c == 'X' && (c = 'x'))))
 				goto bad_conversion_specifier;
 
-			/* Conversion specifiers */
+			/* Numeric and pointer conversion specifiers */
 			if (__PF_FLAG(c) & (__PF_FLAG('c') | __PF_FLAG('d') | __PF_FLAG('i') | __PF_FLAG('u') |
 					    __PF_FLAG('x') | __PF_FLAG('p') | __PF_FLAG('s'))) {
 				unsigned long long v;
@@ -338,12 +356,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 				case 's':
 					if (!v) {
 						outstr = "(null)";
-						len = 6;
+						/* Match glibc, nothing output if precision too small */
+						len = precision >= 6 ? 6 : 0;
 						goto do_output;
 					}
 					outstr = (void *)v;
 do_strnlen_output:
-					len = strnlen(outstr, INT_MAX);
+					len = strnlen(outstr, precision);
 					goto do_output;
 				case 'd':
 				case 'i':
@@ -356,17 +375,62 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list
 					} else if (flags & __PF_FLAG(' ')) {
 						sign = ' ';
 					}
-					__nolibc_fallthrough;
-				case 'u':
-					len = u64toa_r(v, out);
-					break;
-				case 'p':
-					sign = 'x' | '0' << 8;
-					__nolibc_fallthrough;
-				default: /* 'x' and 'p' above */
-					len = u64toh_r(v, out);
-					break;
+					c = 'u';
+				}
+
+				if (v == 0) {
+					/* There are special rules for zero. */
+					if (c == 'p') {
+						/* match glibc, precision is ignored */
+						outstr = "(nil)";
+						len = 5;
+						goto do_output;
+					}
+					if (!precision) {
+						/* Explicit %nn.0d, no digits output */
+						len = 0;
+						goto prepend_sign;
+					}
+					/* "#x" should output "0" not "0x0" */
+					*out = '0';
+					len = 1;
+				} else {
+					if (c == 'u') {
+						len = u64toa_r(v, out);
+					} else {
+						len = u64toh_r(v, out);
+						if (c == 'p' || (flags & __PF_FLAG('#')))
+							sign = 'x' | '0' << 8;
+					}
 				}
+
+				/* Add zero padding */
+				if (flags & (__PF_FLAG('0') | __PF_FLAG('.'))) {
+					if (!(flags & __PF_FLAG('.'))) {
+						if (flags & __PF_FLAG('-'))
+							/* Left justify overrides zero pad */
+							goto prepend_sign;
+						/* Zero pad to field width less sign */
+						precision = width;
+						if (sign) {
+							precision--;
+							if (sign >= 256)
+								precision--;
+						}
+					}
+					if (precision > 30)
+						/* Don't run off the start of tmpbuf[] */
+						precision = 30;
+					for (; len < precision; len++) {
+						/* Stop gcc generating horrid code and memset().
+						 * This is OPTIMIZER_HIDE_VAR() from compiler.h.
+						 */
+						__asm__ volatile("" : "=r"(len) : "0"(len));
+						*--out = '0';
+					}
+				}
+
+prepend_sign:
 				for (; sign; sign >>= 8) {
 					len++;
 					*--out = sign;
-- 
2.39.5


  parent reply	other threads:[~2026-02-03 10:30 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-03 10:29 [PATCH next 00/12] tools/nolibc: Enhance printf() david.laight.linux
2026-02-03 10:29 ` [PATCH next 01/12] tools/nolibc/printf: Move length check to snprintf callback david.laight.linux
2026-02-03 10:29 ` [PATCH next 02/12] tools/nolibc/printf: Add buffering to vfprintf() callback david.laight.linux
2026-02-03 10:29 ` [PATCH next 03/12] tools/nolibc/printf: output pad spaces in 16 byte chunks david.laight.linux
2026-02-03 10:29 ` [PATCH next 04/12] selftests/nolibc: Improve reporting of vfprintf() errors david.laight.linux
2026-02-03 10:29 ` [PATCH next 05/12] tools/nolibc/printf: Simplify __nolibc_printf() david.laight.linux
2026-02-03 10:29 ` [PATCH next 06/12] tools/nolibc/printf: Add support for left alignment and %[tzLq]d" david.laight.linux
2026-02-04  4:14   ` Willy Tarreau
2026-02-04 10:17     ` David Laight
2026-02-04 10:40       ` Willy Tarreau
2026-02-04 15:39         ` David Laight
2026-02-03 10:29 ` [PATCH next 07/12] tools/nolibc/printf: Prepend the sign after a numeric conversion david.laight.linux
2026-02-03 10:29 ` [PATCH next 08/12] tools/nolibc/printf: use bit-match to detect valid conversion characters david.laight.linux
2026-02-03 10:29 ` david.laight.linux [this message]
2026-02-03 10:29 ` [PATCH next 10/12] tools/nolibc/printf: Use bit-pattern for integral formats david.laight.linux
2026-02-03 10:29 ` [PATCH 11/12] selftests/nolibc: Increase coverage of printf format tests david.laight.linux
2026-02-03 18:22   ` kernel test robot
2026-02-03 22:05     ` David Laight
2026-02-03 23:23   ` kernel test robot
2026-02-03 10:30 ` [PATCH 12/12] selftests/nolibc: Use printf("%.*s", n, "") to align output david.laight.linux
2026-02-03 17:39 ` [PATCH next 00/12] tools/nolibc: Enhance printf() 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=20260203103000.20206-10-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