diff options
| author | David Laight <david.laight.linux@gmail.com> | 2026-03-08 11:37:40 +0000 |
|---|---|---|
| committer | Thomas Weißschuh <linux@weissschuh.net> | 2026-03-20 17:58:10 +0100 |
| commit | d3d3f64f8e964f8af6ac72294e65caad5acc452e (patch) | |
| tree | 361bf2113796e008ce2f2b82df8b6e11bf761d83 /tools/include | |
| parent | b5f3f59cf4384a8c9e60fa4bb1a8f4ad71126a90 (diff) | |
| download | lwn-d3d3f64f8e964f8af6ac72294e65caad5acc452e.tar.gz lwn-d3d3f64f8e964f8af6ac72294e65caad5acc452e.zip | |
tools/nolibc/printf: Add support for zero padding and field precision
Includes support for variable field widths (eg "%*.*d").
Zero padding is limited to 31 zero characters.
This is wider than the largest numeric field so shouldn't be a problem.
All the standard printf formats are now supported except octal
and floating point.
Add tests for new features
Signed-off-by: David Laight <david.laight.linux@gmail.com>
Acked-by: Willy Tarreau <w@1wt.eu>
Link: https://patch.msgid.link/20260308113742.12649-16-david.laight.linux@gmail.com
[Thomas: fixup testcases for musl libc]
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Diffstat (limited to 'tools/include')
| -rw-r--r-- | tools/include/nolibc/stdio.h | 80 |
1 files changed, 64 insertions, 16 deletions
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index c8a463ed73c7..1294004afb30 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -292,12 +292,10 @@ int fseek(FILE *stream, long offset, int whence) /* printf(). Supports most of the normal integer and string formats. - * - %[#-+ 0][width][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%} + * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%} * - %% generates a single % * - %m outputs strerror(errno). * - %X outputs a..f the same as %x. - * - 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. * @@ -343,9 +341,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list char ch; unsigned long long v; long long signed_v; - int written, width, len; + int written, width, precision, len; unsigned int flags, ch_flag; - char outbuf[2 + 22 + 1]; + char outbuf[2 + 31 + 22 + 1]; char *out; const char *outstr; unsigned int sign_prefix; @@ -378,12 +376,24 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list flags |= ch_flag; } - /* width */ - while (ch >= '0' && ch <= '9') { - width *= 10; - width += ch - '0'; - - ch = *fmt++; + /* Width and precision */ + for (;; ch = *fmt++) { + if (ch == '*') { + precision = va_arg(args, unsigned int); + ch = *fmt++; + } else { + for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++) + precision = precision * 10 + (ch - '0'); + } + if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) + break; + width = precision; + if (ch != '.') { + /* Default precision for strings */ + precision = INT_MAX; + break; + } + flags |= _NOLIBC_PF_FLAG('.'); } /* Length modifier. @@ -444,8 +454,12 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list if (ch == 's') { /* "%s" - character string. */ outstr = (const char *)(uintptr_t)v; - if (!outstr) + if (!outstr) { outstr = "(null)"; + /* Match glibc, nothing output if precision too small */ + len = precision >= 6 ? 6 : 0; + goto do_output; + } goto do_strlen_output; } @@ -468,11 +482,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } /* The value is converted offset into the buffer so that - * the sign/prefix can be added in front. + * 31 zero pad characters and 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; + out = outbuf + 2 + 31; if (v == 0) { /* There are special rules for zero. */ @@ -482,6 +496,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list len = 5; goto do_output; } + if (!precision) { + /* Explicit %nn.0d, no digits output */ + len = 0; + goto prepend_sign; + } /* All other formats (including "%#x") just output "0". */ out[0] = '0'; len = 1; @@ -500,6 +519,35 @@ 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) { + 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'; + } + } + +prepend_sign: /* 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. */ @@ -534,8 +582,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; do_strlen_output: - /* Open coded strlen() (slightly smaller). */ - for (len = 0;; len++) + /* Open coded strnlen() (slightly smaller). */ + for (len = 0; len < precision; len++) if (!outstr[len]) break; |
