Enhance printf()
Subsystems (2)
v4 cover Enhance printf()
From: David Laight <[email protected]>
Update printf() so that it handles almost all the non-fp formats. In particular:
- Left alignment.
- Zero padding.
- Alternate form "%#x" and "%#o".
- Field precision.
- Variable field width and precision.
- Width modifiers q, L, t and z.
- Conversion specifiers i, o and X (X generates lower case). About the only things that are missing are wide chanacters and floating point.
The tests are updated to match.
Bloat/savings (in nolibc-test, but excluding the program) to patch 14:
(Measured for v3)
Function old new delta
_nolibc_u64toa_base.isra - 143 +143
strerror - 78 +78
__nolibc_sprintf_cb 58 91 +33
itoa_r.isra 60 75 +15
utoa_r.isra 144 - -144
__nolibc_printf 1081 729 -352
(All these functions include ~40 bytes for the stack protector code.) utoa_r.isra and _nolibc_u64toa_base.isra pretty much cancel each other out. itoa_r.isra grows slightly since it calls _nolibc_u64toa_base(). strerror() used to be inlined, but over half of it is the stack check. While some of the code added to __nolibc_sprintf_cb() has come out of __nolibc_printf() 16-20 bytes is removed from the caller.
So there is a net saving of about 280 bytes (including losing a copy of the number to ascii code).
The later patches add code back in:
patch 13 - conversion flags " +#" +80 bytes
patch 14 - left aligning fields +38 bytes
patch 15 - zero padding and field precision +260 bytes
patch 16 - octal output +34 bytes
So probably about +130 bytes, but it will depend on what the application actually calls and inlining decisions made by the compiler. (All x86-64, other architectures will vary.)
The biggest size change is probably removing the .data from strerror(). This reduced the program binary file by 4k if it is the only initialised data in a small program.
Changes for v4:
- Old patches 2, 3 and 6 have been applied to nolibc-next and removed.
- Patch 7 (fix padding) has been moved before the selftest changes so that the tests don't fail.
- NOLIBC_IGNORE_ERRNO is left only applying to %m and not strerror().
- Avoid calling memcpy(tgt, NULL, 0) in the vsnprintf callback function.
- Some of the patches have been split.
The final version of stdio.h only differs from v3 in the strerror() and vsnprintf callback code.
Significant changes for v3:
The patches have been re-ordered, split and joined but the final code is pretty much the same as v2.
- Include the patch to stdlib.h that optimises the 'number to ascii' functions. This is needed for the final patch that adds octal support. This includes a fix to the previous version that could generate negative digits.
- Add octal support in the final patch.
- Update the selftests as new features are added.
- The patch to buffer fprintf() output has been removed.
Changes for v2:
Mostly changes to improve the readability of the code.
- New patch #1 inserted to rename the variable 'c' to 'ch'.
- Use #define 'magic' for the bit-masks that check multiple characters. The check for the conversion flag characters is then based on:
ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, ' ', '#', '+', '-', '0');
- Re-order the changes so that the old patch 10 (Use bit-pattern for integral formats) is done at the same time as bit-masks are used for the flags characters and length modifiers.
This means the restructuring changes are done before new features are added.
- Put all the changes to the selftest together at the end. There is one extra test for ("%#01x", 0x1234) (should be "0x1234") which is problematic because once you've removed the length of the "0x" from the field width there are -1 character postions for the digits.
David Laight (23):
tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h tools/nolibc/printf: Move snprintf length check to callback selftests/nolibc: Return correct value when printf test fails selftests/nolibc: check vsnprintf() output buffer before the length selftests/nolibc: Use length of 'expected' string to check snprintf()
output
selftests/nolibc: Check that snprintf() doesn't write beyond the
buffer end
selftests/nolibc: Let EXPECT_VFPRINTF() tests be skipped selftests/nolibc: Rename w to written in expect_vfprintf() tools/nolibc: Implement strerror() in terms of strerror_r() tools/nolibc: Rename the 'errnum' parameter to strerror() tools/nolibc/printf: Output pad characters in 16 byte chunks tools/nolibc/printf: Simplify __nolibc_printf() tools/nolibc/printf: Use goto and reduce indentation tools/nolibc/printf: Use bit-masks to hold requested flag, length and
conversion chars
tools/nolibc/printf: Add support for length modifiers tzqL and formats
iX
tools/nolibc/printf: Handle "%s" with the numeric formats tools/nolibc/printf: Prepend sign to converted number tools/nolibc/printf: Add support for conversion flags space and plus tools/nolibc/printf: Special case 0 and add support for %#x tools/nolibc/printf: Add support for left aligning fields tools/nolibc/printf: Add support for zero padding and field precision tools/nolibc/printf: Add support for octal output selftests/nolibc: Use printf variable field widths and precisions
tools/include/nolibc/compiler.h | 3 + tools/include/nolibc/stdio.h | 501 ++++++++++++++----- tools/testing/selftests/nolibc/nolibc-test.c | 157 +++--- 3 files changed, 469 insertions(+), 192 deletions(-)
Signature · 1 line
2.39.5
PATCH v4 1/23 tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
From: David Laight <[email protected]>
Needed to stop compiler 'optimisations' bloating code. Equivalent to the definition in include/linux/compiler.h
---
Changes for v4:
- Remove #if guard
tools/include/nolibc/compiler.h | 3 +++ 1 file changed, 3 insertions(+)
tools/include/nolibc/compiler.h +3 -0
@@ -71,4 +71,7 @@# define __nolibc_static_assert (_t)#endif +/* Make the optimizer believe the variable can be manipulated arbitrarily. */ +#define _NOLIBC_OPTIMIZER_HIDE_VAR (var) __asm__ ("" : "+r" (var)) +#endif /* _NOLIBC_COMPILER_H */
Signature · 1 line
2.39.5
Re: [PATCH v4 next 01/23] tools/nolibc: Add _NOLIBC_OPTIMIZER_HIDE_VAR() to compiler.h
Quoted 4 lines · On Mon, Mar 02, 2026 at 10:17:53AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Needed to stop compiler 'optimisations' bloating code. Equivalent to the definition in
include/linux/compiler.h
Can definitely be useful from time to time, I'm doing similar things in my programs all the time and after I regret not having centralized it. Here it will help contributors who could get discouraged by code size variations caused by their tiny changes.
Quoted 1 line
Signed-off-by David Laight <[email protected]>
Willy
Quoted 21 lines
---
Changes for v4:
- Remove #if guardtools/include/nolibc/compiler.h | 3 +++ 1 file changed, 3 insertions(+)tools/include/nolibc/compiler.h +3
-0@@ -71,4 +71,7 @@# define __nolibc_static_assert (_t)#endif +/* Make the optimizer believe the variable can be manipulated arbitrarily. */ +#define _NOLIBC_OPTIMIZER_HIDE_VAR (var) __asm__ ("" : "+r" (var)) +#endif /* _NOLIBC_COMPILER_H */ Signature · 1 line
2.39.5
PATCH v4 2/23 tools/nolibc/printf: Move snprintf length check to callback
In reply to [email protected]From: David Laight <[email protected]>
Move output truncation to the snprintf() callback.
This simplifies the main code and fixes truncation of padded fields.
Add a zero length callback to 'finalise' the buffer rather than doing it in snprintf() itself.
Fixes e90ce42e81381 ("tools/nolibc: implement width padding in printf()")
---
For v4:
- Avoid memcpy(ptr, NULL, 0)
For v3:
- Patch 2 in v2, patch 1 in v1.
- Note that it fixes e90ce42e81381.
- Update comments (again).
- Rename size => space in snprintf 'state.
- Copy state->space to a local rather than relying on the compiler doing CSE, changes the code slightly for x86 (but not the size).
- Unconditionally write a '\0' to terminate the old data before overwriting it with new data.
Saves a few bytes of object code.
tools/include/nolibc/stdio.h | 94 +++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 27 deletions(-)
tools/include/nolibc/stdio.h +67 -27
@@ -295,16 +295,25 @@ int fseek(FILE *stream, long offset, int whence)* - %[l*]{d,u,c,x,p} * - %s * - unknown modifiers are ignored. +* +* Called by vfprintf() and snprintf() to do the actual formatting. +* The callers provide a callback function to save the formatted data. +* The callback function is called multiple times: +* - for each group of literal characters in the format string. +* - for field padding. +* - for each conversion specifier. +* - with (NULL, 0) at the end of the __nolibc_printf. +* If the callback returns non-zero __nolibc_printf() immediately returns -1. */ -typedef int (*__nolibc_printf_cb )(intptr_t state,const char *buf,size_t size); +typedef int (*__nolibc_printf_cb )(void *state,const char *buf,size_t size); -static __attribute__((unused,format (printf,4 ,0 ))) -int __nolibc_printf (__nolibc_printf_cb cb,intptr_t state,size_t n,const char *fmt,va_list args) +static __attribute__((unused,format (printf,3 ,0 ))) +int __nolibc_printf (__nolibc_printf_cb cb,void *state,const char *fmt,va_list args) {char escape, lpref, ch;unsigned long long v;unsigned int written, width; -size_t len, ofs, w; +size_t len, ofs;char outbuf[21 ];const char *outstr;
@@ -406,17 +415,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char outstr = fmt; len = ofs -1 ; flush_str: -if (n) { - w = len < n ? len : n; - n -= w; -while (width-- > w) { -if (cb (state," " ,1 ) !=0 ) -return -1 ; - written +=1 ; - } -if (cb (state, outstr, w) !=0 ) +while (width-- > len) { +if (cb (state," " ,1 ) !=0 )return -1 ; + written +=1 ; } +if (cb (state, outstr, len) !=0 ) +return -1 ; written += len; do_escape:
@@ -429,18 +434,25 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char/* literal char, just queue it */ } + +/* Request a final '\0' be added to the snprintf() output. +* This may be the only call of the cb() function. +*/ +if (cb (state,NULL ,0 ) !=0 ) +return -1 ; +return written; } -static int __nolibc_fprintf_cb (intptr_t state,const char *buf,size_t size) +static int __nolibc_fprintf_cb (void *stream,const char *buf,size_t size) { -return _fwrite (buf, size, (FILE *)state); +return _fwrite (buf, size, stream); }static __attribute__((unused,format (printf,2 ,0 )))int vfprintf (FILE *stream,const char *fmt,va_list args) { -return __nolibc_printf (__nolibc_fprintf_cb, (intptr_t )stream,SIZE_MAX , fmt, args); +return __nolibc_printf (__nolibc_fprintf_cb, stream, fmt, args); } static __attribute__((unused, format(printf, 1, 0)))
@@ -498,26 +510,54 @@ int dprintf(int fd, const char *fmt, ...)return ret; } -static int __nolibc_sprintf_cb (intptr_t _state,const char *buf,size_t size) +struct __nolibc_sprintf_cb_state { +char *buf; +size_t space; +}; + +static int __nolibc_sprintf_cb (void *v_state,const char *buf,size_t size) { -char **state = (char **)_state; +struct __nolibc_sprintf_cb_state *state = v_state; +size_t space = state->space; +char *tgt; + +/* Truncate the request to fit in the output buffer space. +* The last byte is reserved for the terminating '\0'. +* state->space can only be zero for snprintf(NULL, 0, fmt, args) +* so this normally lets through calls with 'size == 0'. +*/ +if (size >= space) { +if (space <=1 ) +return 0 ; + size = space -1 ; + } + tgt = state->buf; + +/* __nolibc_printf() ends with cb(state, NULL, 0) to request the output +* buffer be '\0' terminated. +* That will be the only cb() call for, eg, snprintf(buf, sz, ""). +* Zero lengths can occur at other times (eg "%s" for an empty string). +* Unconditionally write the '\0' byte to reduce code size, it is +* normally overwritten by the data being output. +* There is no point adding a '\0' after copied data - there is always +* another call. +*/ + *tgt ='\0' ; +if (size) { + state->space = space - size; + state->buf = tgt + size; +memcpy (tgt, buf, size); + } -memcpy (*state, buf, size); - *state += size;return 0 ; }static __attribute__((unused,format (printf,3 ,0 )))int vsnprintf (char *buf,size_t size,const char *fmt,va_list args) { -char *state = buf; -int ret; +struct __nolibc_sprintf_cb_state state = { .buf = buf, .space = size }; - ret =__nolibc_printf (__nolibc_sprintf_cb, (intptr_t )&state, size, fmt, args); -if (ret <0 ) -return ret; - buf[(size_t )ret < size ? (size_t )ret : size -1 ] ='\0' ; -return ret; +return __nolibc_printf (__nolibc_sprintf_cb, &state, fmt, args); } static __attribute__((unused, format(printf, 3, 4)))
Signature · 1 line
2.39.5
Re: [PATCH v4 next 02/23] tools/nolibc/printf: Move snprintf length check to callback
Quoted 10 lines · On Mon, Mar 02, 2026 at 10:17:54AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Move output truncation to the
snprintf()callback.
This simplifies the main code and fixes truncation of padded fields.Add a zero length callback to 'finalise' the buffer rather than doing it in
snprintf()itself.Fixes
e90ce42e81381("tools/nolibc: implement width padding inprintf()")Signed-off-by David Laight <[email protected]>
Willy
Quoted 173 lines
---
For v4:
- Avoidmemcpy(ptr, NULL, 0)For v3:
- Patch 2 in v2, patch 1 in v1.
- Note that it fixese90ce42e81381.
- Update comments (again).
- Rename size => space in snprintf 'state.
- Copystate->spaceto a local rather than relying on the compiler doing CSE, changes the code slightly for x86 (but not the size).
- Unconditionally write a '\0' to terminate the old data before overwriting it with new data.
Saves a few bytes of object code.tools/include/nolibc/stdio.h | 94 +++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 27 deletions(-)tools/include/nolibc/stdio.h +67
-27@@ -295,16 +295,25 @@ int fseek(FILE *stream, long offset, int whence)* - %[l*]{d,u,c,x,p} * - %s * - unknown modifiers are ignored. +* +* Called by vfprintf() and snprintf() to do the actual formatting. +* The callers provide a callback function to save the formatted data. +* The callback function is called multiple times: +* - for each group of literal characters in the format string. +* - for field padding. +* - for each conversion specifier. +* - with (NULL, 0) at the end of the __nolibc_printf. +* If the callback returns non-zero __nolibc_printf() immediately returns -1. */ -typedef int (*__nolibc_printf_cb )(intptr_t state,const char *buf,size_t size); +typedef int (*__nolibc_printf_cb )(void *state,const char *buf,size_t size); -static __attribute__((unused,format (printf,4 ,0 ))) -int __nolibc_printf (__nolibc_printf_cb cb,intptr_t state,size_t n,const char *fmt,va_list args) +static __attribute__((unused,format (printf,3 ,0 ))) +int __nolibc_printf (__nolibc_printf_cb cb,void *state,const char *fmt,va_list args) {char escape, lpref, ch;unsigned long long v;unsigned int written, width; -size_t len, ofs, w; +size_t len, ofs;char outbuf[21 ];const char *outstr;@@ -406,17 +415,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char outstr = fmt; len = ofs -1 ; flush_str: -if (n) { - w = len < n ? len : n; - n -= w; -while (width-- > w) { -if (cb (state," " ,1 ) !=0 ) -return -1 ; - written +=1 ; - } -if (cb (state, outstr, w) !=0 ) +while (width-- > len) { +if (cb (state," " ,1 ) !=0 )return -1 ; + written +=1 ; } +if (cb (state, outstr, len) !=0 ) +return -1 ; written += len; do_escape:@@ -429,18 +434,25 @@ int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char/* literal char, just queue it */ } + +/* Request a final '\0' be added to the snprintf() output. +* This may be the only call of the cb() function. +*/ +if (cb (state,NULL ,0 ) !=0 ) +return -1 ; +return written; } -static int __nolibc_fprintf_cb (intptr_t state,const char *buf,size_t size) +static int __nolibc_fprintf_cb (void *stream,const char *buf,size_t size) { -return _fwrite (buf, size, (FILE *)state); +return _fwrite (buf, size, stream); }static __attribute__((unused,format (printf,2 ,0 )))int vfprintf (FILE *stream,const char *fmt,va_list args) { -return __nolibc_printf (__nolibc_fprintf_cb, (intptr_t )stream,SIZE_MAX , fmt, args); +return __nolibc_printf (__nolibc_fprintf_cb, stream, fmt, args); } static __attribute__((unused, format(printf, 1, 0)))@@ -498,26 +510,54 @@ int dprintf(int fd, const char *fmt, ...)return ret; } -static int __nolibc_sprintf_cb (intptr_t _state,const char *buf,size_t size) +struct __nolibc_sprintf_cb_state { +char *buf; +size_t space; +}; + +static int __nolibc_sprintf_cb (void *v_state,const char *buf,size_t size) { -char **state = (char **)_state; +struct __nolibc_sprintf_cb_state *state = v_state; +size_t space = state->space; +char *tgt; + +/* Truncate the request to fit in the output buffer space. +* The last byte is reserved for the terminating '\0'. +* state->space can only be zero for snprintf(NULL, 0, fmt, args) +* so this normally lets through calls with 'size == 0'. +*/ +if (size >= space) { +if (space <=1 ) +return 0 ; + size = space -1 ; + } + tgt = state->buf; + +/* __nolibc_printf() ends with cb(state, NULL, 0) to request the output +* buffer be '\0' terminated. +* That will be the only cb() call for, eg, snprintf(buf, sz, ""). +* Zero lengths can occur at other times (eg "%s" for an empty string). +* Unconditionally write the '\0' byte to reduce code size, it is +* normally overwritten by the data being output. +* There is no point adding a '\0' after copied data - there is always +* another call. +*/ + *tgt ='\0' ; +if (size) { + state->space = space - size; + state->buf = tgt + size; +memcpy (tgt, buf, size); + } -memcpy (*state, buf, size); - *state += size;return 0 ; }static __attribute__((unused,format (printf,3 ,0 )))int vsnprintf (char *buf,size_t size,const char *fmt,va_list args) { -char *state = buf; -int ret; +struct __nolibc_sprintf_cb_state state = { .buf = buf, .space = size }; - ret =__nolibc_printf (__nolibc_sprintf_cb, (intptr_t )&state, size, fmt, args); -if (ret <0 ) -return ret; - buf[(size_t )ret < size ? (size_t )ret : size -1 ] ='\0' ; -return ret; +return __nolibc_printf (__nolibc_sprintf_cb, &state, fmt, args); } static __attribute__((unused, format(printf, 3, 4)))Signature · 1 line
2.39.5
PATCH v4 3/23 selftests/nolibc: Return correct value when printf test fails
In reply to [email protected]From: David Laight <[email protected]>
Correctly return 1 (the number of errors) when strcmp() fails rather than the return value from strncmp() which is the signed difference between the mismatching characters.
---
New patch for v4.
Corrects the error reporting for test failures.
But is really replaced by the next patch.
tools/testing/selftests/nolibc/nolibc-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/testing/selftests/nolibc/nolibc-test.c +1 -1
@@ -1683,7 +1683,7 @@ static int expect_vfprintf(int llen, int c, const char *expected, const char *fm } llen +=printf (" \"%s\" = \"%s\"" , expected, buf); - ret =strncmp (expected, buf, c); + ret =strncmp (expected, buf, c) !=0 ;result (llen, ret ?FAIL :OK );return ret;
Signature · 1 line
2.39.5
PATCH v4 4/23 selftests/nolibc: check vsnprintf() output buffer before the length
In reply to [email protected]From: David Laight <[email protected]>
Check the string matches before checking the returned length. Only print the string once when it matches.
Makes it a lot easier to diagnose any incorrect output.
---
v4: split out from patch 3
tools/testing/selftests/nolibc/nolibc-test.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-)
tools/testing/selftests/nolibc/nolibc-test.c +9 -7
@@ -1668,25 +1668,27 @@ static int expect_vfprintf(int llen, int c, const char *expected, const char *fmchar buf[100 ];va_list args;ssize_t w; -int ret; -va_start (args, fmt);/* Only allow writing 21 bytes, to test truncation */ w =vsnprintf (buf,21 , fmt, args);va_end (args); + llen +=printf (" \"%s\"" , buf); +if (strncmp (expected, buf, c)) { + llen +=printf (" should be \"%s\"" , expected); +result (llen,FAIL ); +return 1 ; + } +if (w != c) { llen +=printf (" written(%d) != %d" , (int )w, c);result (llen,FAIL );return 1 ; } - llen +=printf (" \"%s\" = \"%s\"" , expected, buf); - ret =strncmp (expected, buf, c) !=0 ; - -result (llen, ret ?FAIL :OK ); -return ret; +result (llen,OK ); +return 0 ; }static int test_scanf (void )
Signature · 1 line
2.39.5
PATCH v4 5/23 selftests/nolibc: Use length of 'expected' string to check snprintf() output
In reply to [email protected]From: David Laight <[email protected]>
Instead of requiring the test cases specifying both the length and expected output, take the length from the expected output. Tests that expect the output be truncated are changed to specify the un-truncated output.
Change the strncmp() to a memcmp() with an extra check that the output is actually terminated.
Append a '+' to the printed output (after the final ") when the output is truncated.
---
v4: split out from patch 3.
tools/testing/selftests/nolibc/nolibc-test.c | 67 ++++++++++++-------- 1 file changed, 41 insertions(+), 26 deletions(-)
tools/testing/selftests/nolibc/nolibc-test.c +41 -26
@@ -1660,29 +1660,44 @@ int run_stdlib(int min, int max)return ret; } -#define EXPECT_VFPRINTF (c, expected, fmt, ...) \ - ret += expect_vfprintf(llen, c, expected, fmt, ##__VA_ARGS__) +#define EXPECT_VFPRINTF (expected, fmt, ...) \ + ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__) -static int expect_vfprintf (int llen,int c,const char *expected,const char *fmt, ...) +#define VFPRINTF_LEN 20 +static int expect_vfprintf (int llen,const char *expected,const char *fmt, ...) { -char buf[100 ]; +char buf[VFPRINTF_LEN +80 ]; +unsigned int cmp_len;va_list args; -ssize_t w; +ssize_t w, expected_len;va_start (args, fmt); -/* Only allow writing 21 bytes, to test truncation */ - w =vsnprintf (buf,21 , fmt, args); +/* Limit buffer length to test truncation */ + w =vsnprintf (buf,VFPRINTF_LEN +1 , fmt, args);va_end (args); llen +=printf (" \"%s\"" , buf); -if (strncmp (expected, buf, c)) { - llen +=printf (" should be \"%s\"" , expected); + + expected_len =strlen (expected); +if (expected_len >VFPRINTF_LEN ) { +/* Indicate truncated in test output */ + llen +=printf ("+" ); + cmp_len =VFPRINTF_LEN ; + }else { + cmp_len = expected_len; + } + +if (memcmp (expected, buf, cmp_len) || buf[cmp_len]) { +/* Copy and truncate until "%.*s" supported */ +memcpy (buf, expected, cmp_len); + buf[cmp_len] =0 ; + llen +=printf (" should be \"%s\"" , buf);result (llen,FAIL );return 1 ; } -if (w != c) { - llen +=printf (" written(%d) != %d" , (int )w, c); +if (w != expected_len) { + llen +=printf (" written(%d) != %d" , (int )w, (int )expected_len);result (llen,FAIL );return 1 ; }
@@ -1809,21 +1824,21 @@ static int run_printf(int min, int max)* test numbers. */ switch (test + __LINE__ +1 ) { -CASE_TEST (empty);EXPECT_VFPRINTF (0 ,"" ,"" );break ; -CASE_TEST (simple);EXPECT_VFPRINTF (3 ,"foo" ,"foo" );break ; -CASE_TEST (string);EXPECT_VFPRINTF (3 ,"foo" ,"%s" ,"foo" );break ; -CASE_TEST (number);EXPECT_VFPRINTF (4 ,"1234" ,"%d" ,1234 );break ; -CASE_TEST (negnumber);EXPECT_VFPRINTF (5 ,"-1234" ,"%d" ,-1234 );break ; -CASE_TEST (unsigned);EXPECT_VFPRINTF (5 ,"12345" ,"%u" ,12345 );break ; -CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );break ; -CASE_TEST (hex);EXPECT_VFPRINTF (1 ,"f" ,"%x" ,0xf );break ; -CASE_TEST (pointer);EXPECT_VFPRINTF (3 ,"0x1" ,"%p" , (void *)0x1 );break ; -CASE_TEST (uintmax_t);EXPECT_VFPRINTF (20 ,"18446744073709551615" ,"%ju" ,0xffffffffffffffffULL );break ; -CASE_TEST (intmax_t);EXPECT_VFPRINTF (20 ,"-9223372036854775807" ,"%jd" ,0x8000000000000001LL );break ; -CASE_TEST (truncation);EXPECT_VFPRINTF (25 ,"01234567890123456789" ,"%s" ,"0123456789012345678901234" );break ; -CASE_TEST (string_width);EXPECT_VFPRINTF (10 ," 1" ,"%10s" ,"1" );break ; -CASE_TEST (number_width);EXPECT_VFPRINTF (10 ," 1" ,"%10d" ,1 );break ; -CASE_TEST (width_trunc);EXPECT_VFPRINTF (25 ," " ,"%25d" ,1 );break ; +CASE_TEST (empty);EXPECT_VFPRINTF ("" ,"" );break ; +CASE_TEST (simple);EXPECT_VFPRINTF ("foo" ,"foo" );break ; +CASE_TEST (string);EXPECT_VFPRINTF ("foo" ,"%s" ,"foo" );break ; +CASE_TEST (number);EXPECT_VFPRINTF ("1234" ,"%d" ,1234 );break ; +CASE_TEST (negnumber);EXPECT_VFPRINTF ("-1234" ,"%d" ,-1234 );break ; +CASE_TEST (unsigned);EXPECT_VFPRINTF ("12345" ,"%u" ,12345 );break ; +CASE_TEST (char);EXPECT_VFPRINTF ("c" ,"%c" ,'c' );break ; +CASE_TEST (hex);EXPECT_VFPRINTF ("f" ,"%x" ,0xf );break ; +CASE_TEST (pointer);EXPECT_VFPRINTF ("0x1" ,"%p" , (void *)0x1 );break ; +CASE_TEST (uintmax_t);EXPECT_VFPRINTF ("18446744073709551615" ,"%ju" ,0xffffffffffffffffULL );break ; +CASE_TEST (intmax_t);EXPECT_VFPRINTF ("-9223372036854775807" ,"%jd" ,0x8000000000000001LL );break ; +CASE_TEST (truncation);EXPECT_VFPRINTF ("0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ; +CASE_TEST (string_width);EXPECT_VFPRINTF (" 1" ,"%10s" ,"1" );break ; +CASE_TEST (number_width);EXPECT_VFPRINTF (" 1" ,"%10d" ,1 );break ; +CASE_TEST (width_trunc);EXPECT_VFPRINTF (" 1" ,"%25d" ,1 );break ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ;CASE_TEST (strerror);EXPECT_ZR (1 ,test_strerror ());break ;CASE_TEST (printf_error);EXPECT_ZR (1 ,test_printf_error ());break ;
Signature · 1 line
2.39.5
PATCH v4 6/23 selftests/nolibc: Check that snprintf() doesn't write beyond the buffer end
In reply to [email protected]From: David Laight <[email protected]>
Fill buf[] with known data and check the vsnprintf() doesn't write beyond the specified buffer length.
Would have picked up the bug in field padding.
---
v4: split out from patch 3
tools/testing/selftests/nolibc/nolibc-test.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
tools/testing/selftests/nolibc/nolibc-test.c +13 -0
@@ -1671,6 +1671,10 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)va_list args;ssize_t w, expected_len; +/* Fill and terminate buf[] to check for overlong/absent writes */ +memset (buf,0xa5 ,sizeof (buf) -1 ); + buf[sizeof (buf) -1 ] =0 ; +va_start (args, fmt);/* Limit buffer length to test truncation */ w =vsnprintf (buf,VFPRINTF_LEN +1 , fmt, args);
@@ -1702,6 +1706,15 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)return 1 ; } +/* Check for any overwrites after the actual data. */ +while (++cmp_len <sizeof (buf) -1 ) { +if ((unsigned char )buf[cmp_len] !=0xa5 ) { + llen +=printf (" overwrote buf[%d] with 0x%x" , cmp_len, buf[cmp_len]); +result (llen,FAIL ); +return 1 ; + } + } +result (llen,OK );return 0 ; }
Signature · 1 line
2.39.5
PATCH v4 7/23 selftests/nolibc: Let EXPECT_VFPRINTF() tests be skipped
In reply to [email protected]From: David Laight <[email protected]>
Tests that check explicit nolibc behavior (eg "%m") or test places where the nolibc behaviour deviates from the libc need skipping when compiled to use the host libc.
---
Changes for v4:
- Split out from patch 3.
- Implement the 'skip' in EXPECT_VFPRINTF() not expect_vfprintf() to match the other tests.
tools/testing/selftests/nolibc/nolibc-test.c | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-)
tools/testing/selftests/nolibc/nolibc-test.c +17 -17
@@ -1660,8 +1660,8 @@ int run_stdlib(int min, int max)return ret; } -#define EXPECT_VFPRINTF (expected, fmt, ...) \ - ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__) +#define EXPECT_VFPRINTF (cond, expected, fmt, ...) \ + do { if (!(cond)) result(llen, SKIPPED); else ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__); } while (0)#define VFPRINTF_LEN 20static int expect_vfprintf (int llen,const char *expected,const char *fmt, ...)
@@ -1837,21 +1837,21 @@ static int run_printf(int min, int max)* test numbers. */ switch (test + __LINE__ +1 ) { -CASE_TEST (empty);EXPECT_VFPRINTF ("" ,"" );break ; -CASE_TEST (simple);EXPECT_VFPRINTF ("foo" ,"foo" );break ; -CASE_TEST (string);EXPECT_VFPRINTF ("foo" ,"%s" ,"foo" );break ; -CASE_TEST (number);EXPECT_VFPRINTF ("1234" ,"%d" ,1234 );break ; -CASE_TEST (negnumber);EXPECT_VFPRINTF ("-1234" ,"%d" ,-1234 );break ; -CASE_TEST (unsigned);EXPECT_VFPRINTF ("12345" ,"%u" ,12345 );break ; -CASE_TEST (char);EXPECT_VFPRINTF ("c" ,"%c" ,'c' );break ; -CASE_TEST (hex);EXPECT_VFPRINTF ("f" ,"%x" ,0xf );break ; -CASE_TEST (pointer);EXPECT_VFPRINTF ("0x1" ,"%p" , (void *)0x1 );break ; -CASE_TEST (uintmax_t);EXPECT_VFPRINTF ("18446744073709551615" ,"%ju" ,0xffffffffffffffffULL );break ; -CASE_TEST (intmax_t);EXPECT_VFPRINTF ("-9223372036854775807" ,"%jd" ,0x8000000000000001LL );break ; -CASE_TEST (truncation);EXPECT_VFPRINTF ("0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ; -CASE_TEST (string_width);EXPECT_VFPRINTF (" 1" ,"%10s" ,"1" );break ; -CASE_TEST (number_width);EXPECT_VFPRINTF (" 1" ,"%10d" ,1 );break ; -CASE_TEST (width_trunc);EXPECT_VFPRINTF (" 1" ,"%25d" ,1 );break ; +CASE_TEST (empty);EXPECT_VFPRINTF (1 ,"" ,"" );break ; +CASE_TEST (simple);EXPECT_VFPRINTF (1 ,"foo" ,"foo" );break ; +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 (unsigned);EXPECT_VFPRINTF (1 ,"12345" ,"%u" ,12345 );break ; +CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );break ; +CASE_TEST (hex);EXPECT_VFPRINTF (1 ,"f" ,"%x" ,0xf );break ; +CASE_TEST (pointer);EXPECT_VFPRINTF (1 ,"0x1" ,"%p" , (void *)0x1 );break ; +CASE_TEST (uintmax_t);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" ,0xffffffffffffffffULL );break ; +CASE_TEST (intmax_t);EXPECT_VFPRINTF (1 ,"-9223372036854775807" ,"%jd" ,0x8000000000000001LL );break ; +CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ; +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" ,"%25d" ,1 );break ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ;CASE_TEST (strerror);EXPECT_ZR (1 ,test_strerror ());break ;CASE_TEST (printf_error);EXPECT_ZR (1 ,test_printf_error ());break ;
Signature · 1 line
2.39.5
PATCH v4 8/23 selftests/nolibc: Rename w to written in expect_vfprintf()
In reply to [email protected]From: David Laight <[email protected]>
Single character variable names don't make code easy to read. Rename 'w' (used for the return value from snprintf()) 'written'.
---
V4: Split out from patch 4.
tools/testing/selftests/nolibc/nolibc-test.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
tools/testing/selftests/nolibc/nolibc-test.c +4 -4
@@ -1669,7 +1669,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)char buf[VFPRINTF_LEN +80 ];unsigned int cmp_len;va_list args; -ssize_t w, expected_len; +ssize_t written, expected_len;/* Fill and terminate buf[] to check for overlong/absent writes */ memset (buf,0xa5 ,sizeof (buf) -1 );
@@ -1677,7 +1677,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)va_start (args, fmt);/* Limit buffer length to test truncation */ - w =vsnprintf (buf,VFPRINTF_LEN +1 , fmt, args); + written =vsnprintf (buf,VFPRINTF_LEN +1 , fmt, args);va_end (args); llen +=printf (" \"%s\"" , buf);
@@ -1700,8 +1700,8 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...)return 1 ; } -if (w != expected_len) { - llen +=printf (" written(%d) != %d" , (int )w, (int )expected_len); +if (written != expected_len) { + llen +=printf (" written(%d) != %d" , (int )written, (int )expected_len);result (llen,FAIL );return 1 ; }
Signature · 1 line
2.39.5
PATCH v4 9/23 tools/nolibc: Implement strerror() in terms of strerror_r()
In reply to [email protected]From: David Laight <[email protected]>
strerror() can be the only part of a program that has a .data section. This requres 4k in the program file.
Add a simple implementation of strerror_r() (ignores buflen) and use that in strerror() so that the "errno=" string is copied at run-time. Use __builtin_memcpy() because that optimises away the input string and just writes the required constants to the target buffer.
Ignoring buflen is unlikely to be a problem given that the output is always short.
Code size change largely depends on whether the inlining decision for strerror() changes.
Change the tests to use the normal EXPECT_VFPRINTF() when testing %m. Skip the tests when !is_nolibc.
---
v4:
- Leave the NOLIBC_IGNORE_ERRNO check in __nolibc_printf().
- Don't rename the errno parameter to strerror() in this patch.
tools/include/nolibc/stdio.h | 18 +++++++++++++++--- tools/testing/selftests/nolibc/nolibc-test.c | 20 ++------------------ 2 files changed, 17 insertions(+), 21 deletions(-)
tools/include/nolibc/stdio.h +15 -3
@@ -722,14 +722,26 @@ int setvbuf(FILE *stream __attribute__((unused)),return 0 ; } +static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{ +__builtin_memcpy (buf,"errno=" ,6 ); +return 6 +i64toa_r (errnum, buf +6 ); +} +static __attribute__((unused))const char *strerror (int errno) { -static char buf[18 ] ="errno=" ; +static char buf[18 ]; +char *b = buf; + +/* Force gcc to use 'register offset' to access buf[]. */ +_NOLIBC_OPTIMIZER_HIDE_VAR (b); -i64toa_r (errno, &buf[6 ]); +/* Use strerror_r() to avoid having the only .data in small programs. */ +strerror_r (errno, b,sizeof (buf)); -return buf; +return b; }#endif /* _NOLIBC_STDIO_H */
tools/testing/selftests/nolibc/nolibc-test.c +2 -18
@@ -1786,23 +1786,6 @@ static int test_scanf(void)return 0 ; } -int test_strerror (void ) -{ -char buf[100 ]; -ssize_t ret; - -memset (buf,'A' ,sizeof (buf)); - - errno =EINVAL ; - ret =snprintf (buf,sizeof (buf),"%m" ); -if (is_nolibc) { -if (ret <6 ||memcmp (buf,"errno=" ,6 )) -return 1 ; - } - -return 0 ; -} -static int test_printf_error (void ) {int fd, ret, saved_errno;
@@ -1852,8 +1835,9 @@ 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" ,"%25d" ,1 );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 ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ; -CASE_TEST (strerror);EXPECT_ZR (1 ,test_strerror ());break ;CASE_TEST (printf_error);EXPECT_ZR (1 ,test_printf_error ());break ;case __LINE__:return ret;/* must be last */
Signature · 1 line
2.39.5
Willy Tarreau
maintainerRe: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Hi David,
Quoted 4 lines · On Mon, Mar 02, 2026 at 10:18:01AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
strerror()can be the only part of a program that has a .data section. This requres 4k in the program file.
Thanks for handling this one! Indeed, I saw a trivial hello world program take 4kB once %m got supported, which is a shame.
Quoted 7 lines
Add a simple implementation of
strerror_r()(ignores buflen) and use that instrerror()so that the "errno=" string is copied at run-time. Use__builtin_memcpy()because that optimises away the input string and just writes the required constants to the target buffer.Ignoring buflen is unlikely to be a problem given that the output is always short.
On this point it's not necessarily true, as we can overflow too short an output, e.g. when calling strerror_r() on a single-byte buffer:
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 727
Quoted 3 lines
tools/include/nolibc/stdio.h +3
-0+static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{
Here I think we can simply do this to comply with the man page:
if (buflen < 18) {
errno = ERANGE;
return -1;
}(and we can safely ignore it for strerror()).
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 731
Quoted 4 lines
tools/include/nolibc/stdio.h +4
-0+__builtin_memcpy (buf,"errno=" ,6 ); +return 6 +i64toa_r (errnum, buf +6 ); +} +
(...)
Thanks,
Willy
Re: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Quoted 16 lines · On Sat, 7 Mar 2026 11:18:41 +0100 Willy Tarreau <[email protected]> wrote:
Hi David,
Quoted 4 lines · On Mon, Mar 02, 2026 at 10:18:01AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
strerror()can be the only part of a program that has a .data section. This requres 4k in the program file.Thanks for handling this one! Indeed, I saw a trivial hello world program take 4kB once %m got supported, which is a shame.
Quoted 5 lines
Add a simple implementation of
strerror_r()(ignores buflen) and use that instrerror()so that the "errno=" string is copied at run-time. Use__builtin_memcpy()because that optimises away the input string and just writes the required constants to the target buffer.
Quoted 5 lines
Quoted 2 lines
Ignoring buflen is unlikely to be a problem given that the output is always short.
On this point it's not necessarily true, as we can overflow too short an output, e.g. when calling
strerror_r()on a single-byte buffer:
But that would be silly, and you get what you deserve :-) It's not like passing char[64] will be too short because of some overlong error text.
Quoted 10 lines
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 727
Quoted 4 lines
tools/include/nolibc/stdio.h +3
-0+static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{Here I think we can simply do this to comply with the man page:
if (buflen < 18) { errno = ERANGE; return -1;
Looks like it should 'return ERANGE' (matching glibc 2.13+).
Quoted 3 lines
}(and we can safely ignore it for
strerror()).
ISTR strerror_r() tends to get inlined into strerror() so it would be optimised away.
That simple (slightly over-enthusiastic) check is probably fine. Especially since normal code will be expecting a much longer string.
I did think of implementing strerror_r() as:
return snprintf(buf, buflen, "errno=%d", errnum) < buflen ? 0 : ERANGE;
but that seems excessive.
Quoted 9 lines
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 731
Quoted 5 lines
tools/include/nolibc/stdio.h +4
-0+__builtin_memcpy (buf,"errno=" ,6 ); +return 6 +i64toa_r (errnum, buf +6 ); +} +(...)
Thanks,
Willy
Willy Tarreau
maintainerRe: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Quoted 22 lines · David Laight
Quoted 19 lines · On Sat, 7 Mar 2026 11:18:41 +0100 Willy Tarreau <[email protected]> wrote:
Hi David,
Quoted 4 lines · On Mon, Mar 02, 2026 at 10:18:01AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
strerror()can be the only part of a program that has a .data section. This requres 4k in the program file.Thanks for handling this one! Indeed, I saw a trivial hello world program take 4kB once %m got supported, which is a shame.
Quoted 7 lines
Add a simple implementation of
strerror_r()(ignores buflen) and use that instrerror()so that the "errno=" string is copied at run-time. Use__builtin_memcpy()because that optimises away the input string and just writes the required constants to the target buffer.Ignoring buflen is unlikely to be a problem given that the output is always short.
Quoted 6 lines
Quoted 2 lines
On this point it's not necessarily true, as we can overflow too short an output, e.g. when calling
strerror_r()on a single-byte buffer:But that would be silly, and you get what you deserve :-) It's not like passing char[64] will be too short because of some overlong error text.
Yes I know and I also hate having to write code to defend against silliness, but sometimes what looks silly to some in fact looks smart to others. You could for example find it stupid to call snprintf() with a zero-length but it's actually used sometimes to figure the size to allocate.
Quoted 11 lines
Quoted 9 lines
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 727
Quoted 3 lines
tools/include/nolibc/stdio.h +3
-0+static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{Here I think we can simply do this to comply with the man page:
if (buflen < 18) { errno = ERANGE; return -1;Looks like it should 'return ERANGE' (matching glibc 2.13+).
Ah you're right, I initially misunderstood the man page as "a positive number with errno set". But yes, returning ERANGE is fine!
Quoted 4 lines
Quoted 1 line
(and we can safely ignore it for
strerror()).ISTR
strerror_r()tends to get inlined intostrerror()so it would be optimised away.
That's what I suspected as well.
Quoted 6 lines
That simple (slightly over-enthusiastic) check is probably fine. Especially since normal code will be expecting a much longer string.
I did think of implementing
strerror_r()as:return snprintf(buf, buflen, "errno=%d", errnum) < buflen ? 0 : ERANGE;but that seems excessive.
Yes ;-)
Willy
Re: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
On Sat, 7 Mar 2026 12:37:35 +0100
Willy Tarreau <[email protected]> wrote:
...
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 727
Quoted 3 lines
tools/include/nolibc/stdio.h +3
-0+static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{Here I think we can simply do this to comply with the man page:
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
if (buflen < 18) { errno = ERANGE; return -1;Looks like it should 'return ERANGE' (matching glibc 2.13+).
Ah you're right, I initially misunderstood the man page as "a positive number with errno set". But yes, returning ERANGE is fine!
Which also means I got the return value wrong when I made it return the length.
Might be easiest to apply these patches and then fix it up?
David
Willy Tarreau
maintainerRe: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Quoted 16 lines · David Laight
On Sat, 7 Mar 2026 12:37:35 +0100
Willy Tarreau <[email protected]> wrote:...
Quoted 12 lines
Quoted 11 lines
Quoted 9 lines
tools/include/nolibc/stdio.h int setvbuf(FILE *stream __attribute__((unused)), · line 727
Quoted 3 lines
tools/include/nolibc/stdio.h +3
-0+static __attribute__((unused,)) +int strerror_r (int errnum,char *buf,size_t buflen __attribute__((unused))) +{Here I think we can simply do this to comply with the man page:
if (buflen < 18) { errno = ERANGE; return -1;Looks like it should 'return ERANGE' (matching glibc 2.13+).
Quoted 7 lines
Quoted 2 lines
Ah you're right, I initially misunderstood the man page as "a positive number with errno set". But yes, returning ERANGE is fine!
Which also means I got the return value wrong when I made it return the length.
Might be easiest to apply these patches and then fix it up?
Better only redo this one. It's always bad to purposely merge a patch that we know has an issue, it can complicate some bisect later.
Willy
PATCH v4 10/23 tools/nolibc: Rename the 'errnum' parameter to strerror()
In reply to [email protected]From: David Laight <[email protected]>
Change the parameter variable name from 'errno' to 'errnum'. Matches any documentation and avoids any issues that might happen if errno is actually a #define (which is not uncommon).
---
v4:
- split from the previous patch.
tools/include/nolibc/stdio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
tools/include/nolibc/stdio.h +2 -2
@@ -730,7 +730,7 @@ int strerror_r(int errnum, char *buf, size_t buflen __attribute__((unused))) }static __attribute__((unused)) -const char *strerror (int errno) +const char *strerror (int errnum) {static char buf[18 ];char *b = buf;
@@ -739,7 +739,7 @@ const char *strerror(int errno)_NOLIBC_OPTIMIZER_HIDE_VAR (b);/* Use strerror_r() to avoid having the only .data in small programs. */ -strerror_r (errno, b,sizeof (buf)); +strerror_r (errnum, b,sizeof (buf));return b; }
Signature · 1 line
2.39.5
Re: [PATCH v4 next 10/23] tools/nolibc: Rename the 'errnum' parameter to strerror()
Quoted 5 lines · On Mon, Mar 02, 2026 at 10:18:02AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Change the parameter variable name from 'errno' to 'errnum'. Matches any documentation and avoids any issues that might happen if errno is actually a #define (which is not uncommon).
Good point! And more importantly, shadowing the real errno is super risky in case we'd call a macro that accesses it from that function.
Quoted 2 lines
Signed-off-by David Laight <[email protected]>
Willy
PATCH v4 11/23 tools/nolibc/printf: Output pad characters in 16 byte chunks
In reply to [email protected]From: David Laight <[email protected]>
Simple to do and saves calls to the callback function.
Change variables written, width and len to 'signed int' to get better code.
---
For v4: no change.
For v3:
- Change to signed variables here rather than a later patch.
tools/include/nolibc/stdio.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
tools/include/nolibc/stdio.h +9 -5
@@ -312,8 +312,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list {char escape, lpref, ch;unsigned long long v; -unsigned int written, width; -size_t len, ofs; +int written, width, len; +size_t ofs;char outbuf[21 ];const char *outstr;
@@ -415,10 +415,14 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list outstr = fmt; len = ofs -1 ; flush_str: -while (width-- > len) { -if (cb (state," " ,1 ) !=0 ) + width -= len; +while (width >0 ) { +/* Output pad in 16 byte blocks with the small block first. */ +int pad_len = ((width -1 ) &15 ) +1 ; + width -= pad_len; + written += pad_len; +if (cb (state," " , pad_len) !=0 )return -1 ; - written +=1 ; }if (cb (state, outstr, len) !=0 )return -1 ;
Signature · 1 line
2.39.5
PATCH v4 12/23 tools/nolibc/printf: Simplify __nolibc_printf()
In reply to [email protected]From: David Laight <[email protected]>
Move the check for the length modifiers into the format processing between the field width and conversion specifier.
This lets the loop be simplified and a 'fast scan' for a format start used.
If an error is detected (eg an invalid conversion specifier) then copy the invalid format to the output buffer.
Reduces code size by about 10% on x86-64.
Some versions of gcc bloat this version by generating a jump table. All goes away in the later patches.
Acked-By; Willy Tarreau <[email protected]>
---
No change for v3 or v4.
tools/include/nolibc/stdio.h | 104 ++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 51 deletions(-)
tools/include/nolibc/stdio.h +53 -51
@@ -310,28 +310,52 @@ typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);static __attribute__((unused,format (printf,3 ,0 )))int __nolibc_printf (__nolibc_printf_cb cb,void *state,const char *fmt,va_list args) { -char escape, lpref, ch; +char lpref, ch;unsigned long long v;int written, width, len; -size_t ofs;char outbuf[21 ];const char *outstr; - written = ofs = escape = lpref =0 ; + written =0 ;while (1 ) { - ch = fmt[ofs++]; + outstr = fmt; + ch = *fmt++; +if (!ch) +break ; + width =0 ; +if (ch !='%' ) { +while (*fmt && *fmt !='%' ) + fmt++; +/* Output characters from the format string. */ + len = fmt - outstr; + }else { +/* we're in a format sequence */ -if (escape) { -/* we're in an escape sequence, ofs == 1 */ - escape =0 ; + ch = *fmt++;/* width */ while (ch >='0' && ch <='9' ) { width *=10 ; width += ch -'0' ; - ch = fmt[ofs++]; + ch = *fmt++; + } + +/* Length modifiers */ +if (ch =='l' ) { + lpref =1 ; + ch = *fmt++; +if (ch =='l' ) { + lpref =2 ; + ch = *fmt++; + } + }else if (ch =='j' ) { +/* intmax_t is long long */ + lpref =2 ; + ch = *fmt++; + }else { + lpref =0 ; }if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) {
@@ -387,56 +411,34 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list#else outstr =strerror (errno);#endif /* NOLIBC_IGNORE_ERRNO */ - } -else if (ch =='%' ) { -/* queue it verbatim */ -continue ; - } -else { -/* modifiers or final 0 */ -if (ch =='l' ) { -/* long format prefix, maintain the escape */ - lpref++; - }else if (ch =='j' ) { - lpref =2 ; + }else { +if (ch !='%' ) { +/* Invalid format: back up to output the format characters */ + fmt = outstr +1 ; +/* and output a '%' now. */ } - escape =1 ; - goto do_escape; +/* %% is documented as a 'conversion specifier'. +* Any flags, precision or length modifier are ignored. +*/ + width =0 ; + outstr ="%" ; } len =strlen (outstr); - goto flush_str; } -/* not an escape sequence */ -if (ch ==0 || ch =='%' ) { -/* flush pending data on escape or end */ - escape =1 ; - lpref =0 ; - outstr = fmt; - len = ofs -1 ; - flush_str: - width -= len; -while (width >0 ) { -/* Output pad in 16 byte blocks with the small block first. */ -int pad_len = ((width -1 ) &15 ) +1 ; - width -= pad_len; - written += pad_len; -if (cb (state," " , pad_len) !=0 ) -return -1 ; - } -if (cb (state, outstr, len) !=0 ) -return -1 ; + written += len; - written += len; - do_escape: -if (ch ==0 ) -break ; - fmt += ofs; - ofs =0 ; -continue ; + width -= len; +while (width >0 ) { +/* Output pad in 16 byte blocks with the small block first. */ +int pad_len = ((width -1 ) &15 ) +1 ; + width -= pad_len; + written += pad_len; +if (cb (state," " , pad_len) !=0 ) +return -1 ; } - -/* literal char, just queue it */ +if (cb (state, outstr, len) !=0 ) +return -1 ; }/* Request a final '\0' be added to the snprintf() output.
Signature · 1 line
2.39.5
PATCH v4 13/23 tools/nolibc/printf: Use goto and reduce indentation
In reply to [email protected]From: David Laight <[email protected]>
Upcoming changes will need to use goto to jump to the code that outputs characters.
Use 'goto do_output' to output a known number of characters. Use 'goto do_strlen_output' to output a '\0' terminated string.
Removes a level of indentation from the format processing code.
---
For v4:
- Output a single '%' from the format string.
New patch for v3.
Makes the final code look better and there is less to change if done early.
tools/include/nolibc/stdio.h | 170 +++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 78 deletions(-)
tools/include/nolibc/stdio.h +92 -78
@@ -329,103 +329,117 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list fmt++;/* Output characters from the format string. */ len = fmt - outstr; - }else { -/* we're in a format sequence */ + goto do_output; + } - ch = *fmt++; +/* we're in a format sequence */ -/* width */ -while (ch >='0' && ch <='9' ) { - width *=10 ; - width += ch -'0' ; + ch = *fmt++; - ch = *fmt++; - } +/* width */ +while (ch >='0' && ch <='9' ) { + width *=10 ; + width += ch -'0' ; + + ch = *fmt++; + } -/* Length modifiers */ +/* Length modifiers */ +if (ch =='l' ) { + lpref =1 ; + ch = *fmt++;if (ch =='l' ) { - lpref =1 ; - ch = *fmt++; -if (ch =='l' ) { - lpref =2 ; - ch = *fmt++; - } - }else if (ch =='j' ) { -/* intmax_t is long long */ lpref =2 ; ch = *fmt++; - }else { - lpref =0 ; } + }else if (ch =='j' ) { +/* intmax_t is long long */ + lpref =2 ; + ch = *fmt++; + }else { + lpref =0 ; + } -if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) { -char *out = outbuf; +if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) { +char *out = outbuf; -if (ch =='p' ) +if (ch =='p' ) + v = va_arg(args, unsigned long); +else if (lpref) { +if (lpref >1 ) + v = va_arg(args, unsigned long long); +else v = va_arg(args, unsigned long); -else if (lpref) { -if (lpref >1 ) - v = va_arg(args, unsigned long long); -else - v = va_arg(args, unsigned long); - }else - v = va_arg(args, unsigned int); - -if (ch =='d' ) { -/* sign-extend the value */ -if (lpref ==0 ) - v = (long long )(int )v; -else if (lpref ==1 ) - v = (long long )(long )v; - } + }else + v = va_arg(args, unsigned int); -switch (ch) { -case 'c' : - out[0 ] = v; - out[1 ] =0 ; -break ; -case 'd' : -i64toa_r (v, out); -break ; -case 'u' : -u64toa_r (v, out); -break ; -case 'p' : - *(out++) ='0' ; - *(out++) ='x' ; - __nolibc_fallthrough; -default :/* 'x' and 'p' above */ -u64toh_r (v, out); -break ; - } - outstr = outbuf; +if (ch =='d' ) { +/* sign-extend the value */ +if (lpref ==0 ) + v = (long long )(int )v; +else if (lpref ==1 ) + v = (long long )(long )v; } -else if (ch =='s' ) { - outstr = va_arg(args, char *); -if (!outstr) - outstr="(null)" ; + +switch (ch) { +case 'c' : + out[0 ] = v; + out[1 ] =0 ; +break ; +case 'd' : +i64toa_r (v, out); +break ; +case 'u' : +u64toa_r (v, out); +break ; +case 'p' : + *(out++) ='0' ; + *(out++) ='x' ; + __nolibc_fallthrough; +default :/* 'x' and 'p' above */ +u64toh_r (v, out); +break ; } -else if (ch =='m' ) { + outstr = outbuf; + goto do_strlen_output; + } + +if (ch =='s' ) { + outstr = va_arg(args, char *); +if (!outstr) + outstr="(null)" ; + goto do_strlen_output; + } + +if (ch =='m' ) {#ifdef NOLIBC_IGNORE_ERRNO - outstr ="unknown error" ; + outstr ="unknown error" ;#else - outstr =strerror (errno); + outstr =strerror (errno);#endif /* NOLIBC_IGNORE_ERRNO */ - }else { -if (ch !='%' ) { -/* Invalid format: back up to output the format characters */ - fmt = outstr +1 ; -/* and output a '%' now. */ - } -/* %% is documented as a 'conversion specifier'. -* Any flags, precision or length modifier are ignored. -*/ - width =0 ; - outstr ="%" ; - } - len =strlen (outstr); + goto do_strlen_output; } +if (ch !='%' ) { +/* Invalid format: back up to output the format characters */ + fmt = outstr +1 ; +/* and output a '%' now. */ + } +/* %% is documented as a 'conversion specifier'. +* Any flags, precision or length modifier are ignored. +*/ + len =1 ; + width =0 ; + outstr = fmt -1 ; + goto do_output; + +do_strlen_output: +/* Open coded strlen() (slightly smaller). */ +for (len =0 ;; len++) +if (!outstr[len]) +break ; + +do_output: written += len; width -= len;
Signature · 1 line
2.39.5
Re: [PATCH v4 next 13/23] tools/nolibc/printf: Use goto and reduce indentation
Quoted 8 lines · On Mon, Mar 02, 2026 at 10:18:05AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Upcoming changes will need to use goto to jump to the code that outputs characters.
Use 'gotodo_output' to output a known number of characters. Use 'gotodo_strlen_output' to output a '\0' terminated string.Removes a level of indentation from the format processing code.
Nice! I, too, prefer this more explicit way of transitionning between states. For others, the patch is best reviewed with "git show -b" or "git diff -b" after applying it (67 lines instead of 200).
David, when changing code indent like this, I often like to suggest doing this in the commit messages. I'm saying this in case there's a further respin.
Quoted 1 line
Signed-off-by David Laight <[email protected]>
Willy
Quoted 213 lines
---
For v4:
- Output a single '%' from the format string.New patch for v3.
Makes the final code look better and there is less to change if done early.tools/include/nolibc/stdio.h | 170 +++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 78 deletions(-)tools/include/nolibc/stdio.h +92
-78@@ -329,103 +329,117 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list fmt++;/* Output characters from the format string. */ len = fmt - outstr; - }else { -/* we're in a format sequence */ + goto do_output; + } - ch = *fmt++; +/* we're in a format sequence */ -/* width */ -while (ch >='0' && ch <='9' ) { - width *=10 ; - width += ch -'0' ; + ch = *fmt++; - ch = *fmt++; - } +/* width */ +while (ch >='0' && ch <='9' ) { + width *=10 ; + width += ch -'0' ; + + ch = *fmt++; + } -/* Length modifiers */ +/* Length modifiers */ +if (ch =='l' ) { + lpref =1 ; + ch = *fmt++;if (ch =='l' ) { - lpref =1 ; - ch = *fmt++; -if (ch =='l' ) { - lpref =2 ; - ch = *fmt++; - } - }else if (ch =='j' ) { -/* intmax_t is long long */ lpref =2 ; ch = *fmt++; - }else { - lpref =0 ; } + }else if (ch =='j' ) { +/* intmax_t is long long */ + lpref =2 ; + ch = *fmt++; + }else { + lpref =0 ; + } -if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) { -char *out = outbuf; +if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) { +char *out = outbuf; -if (ch =='p' ) +if (ch =='p' ) + v = va_arg(args, unsigned long); +else if (lpref) { +if (lpref >1 ) + v = va_arg(args, unsigned long long); +else v = va_arg(args, unsigned long); -else if (lpref) { -if (lpref >1 ) - v = va_arg(args, unsigned long long); -else - v = va_arg(args, unsigned long); - }else - v = va_arg(args, unsigned int); - -if (ch =='d' ) { -/* sign-extend the value */ -if (lpref ==0 ) - v = (long long )(int )v; -else if (lpref ==1 ) - v = (long long )(long )v; - } + }else + v = va_arg(args, unsigned int); -switch (ch) { -case 'c' : - out[0 ] = v; - out[1 ] =0 ; -break ; -case 'd' : -i64toa_r (v, out); -break ; -case 'u' : -u64toa_r (v, out); -break ; -case 'p' : - *(out++) ='0' ; - *(out++) ='x' ; - __nolibc_fallthrough; -default :/* 'x' and 'p' above */ -u64toh_r (v, out); -break ; - } - outstr = outbuf; +if (ch =='d' ) { +/* sign-extend the value */ +if (lpref ==0 ) + v = (long long )(int )v; +else if (lpref ==1 ) + v = (long long )(long )v; } -else if (ch =='s' ) { - outstr = va_arg(args, char *); -if (!outstr) - outstr="(null)" ; + +switch (ch) { +case 'c' : + out[0 ] = v; + out[1 ] =0 ; +break ; +case 'd' : +i64toa_r (v, out); +break ; +case 'u' : +u64toa_r (v, out); +break ; +case 'p' : + *(out++) ='0' ; + *(out++) ='x' ; + __nolibc_fallthrough; +default :/* 'x' and 'p' above */ +u64toh_r (v, out); +break ; } -else if (ch =='m' ) { + outstr = outbuf; + goto do_strlen_output; + } + +if (ch =='s' ) { + outstr = va_arg(args, char *); +if (!outstr) + outstr="(null)" ; + goto do_strlen_output; + } + +if (ch =='m' ) {#ifdef NOLIBC_IGNORE_ERRNO - outstr ="unknown error" ; + outstr ="unknown error" ;#else - outstr =strerror (errno); + outstr =strerror (errno);#endif /* NOLIBC_IGNORE_ERRNO */ - }else { -if (ch !='%' ) { -/* Invalid format: back up to output the format characters */ - fmt = outstr +1 ; -/* and output a '%' now. */ - } -/* %% is documented as a 'conversion specifier'. -* Any flags, precision or length modifier are ignored. -*/ - width =0 ; - outstr ="%" ; - } - len =strlen (outstr); + goto do_strlen_output; } +if (ch !='%' ) { +/* Invalid format: back up to output the format characters */ + fmt = outstr +1 ; +/* and output a '%' now. */ + } +/* %% is documented as a 'conversion specifier'. +* Any flags, precision or length modifier are ignored. +*/ + len =1 ; + width =0 ; + outstr = fmt -1 ; + goto do_output; + +do_strlen_output: +/* Open coded strlen() (slightly smaller). */ +for (len =0 ;; len++) +if (!outstr[len]) +break ; + +do_output: written += len; width -= len;Signature · 1 line
2.39.5
PATCH v4 14/23 tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion chars
In reply to [email protected]From: David Laight <[email protected]>
Use flags bits (1u << (ch & 31)) for the flags, length modifiers, and conversion specifiers.
This makes it easy to test for multiple values at once.
Detect the conversion flags " #+-0" although they are currently all ignored.
Unconditionally generate the signed values (for %d) to remove a second set of checks for the size.
Separate out the formatting of single characters from numbers. Output the sign for negative values then negate and treat as unsigned.
---
Changes for v4:
- Move the support for length modifiers t, j, q, L and formats i and X to the next patch.
- Convert ll to j (not q) since q isn't added until the next patch.
Changes for v3:
- Patch 6 in v2.
- Move all the variable definitions to the top of the function. The loop body is a bit long to hide definitions at its top.
- Avoid -Wtype-limits validating format characters.
- Include changes to the selftests.
Changes for v2:
- Use #defines to make the code a lot more readable.
- Include the changes from the old patch 10 that used masks for the conversion specifiers.
- Detect all the valid flag characters even though they are not implemented.
- Support for left justifying field is moved to patch 7.
tools/include/nolibc/stdio.h | 157 ++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 49 deletions(-)
tools/include/nolibc/stdio.h +108 -49
@@ -291,10 +291,14 @@ int fseek(FILE *stream, long offset, int whence) } -/* minimal printf(). It supports the following formats: -* - %[l*]{d,u,c,x,p} -* - %s -* - unknown modifiers are ignored. +/* printf(). Supports the following integer and string formats. +* - %[#-+ 0][width][{l,ll,j}]{c,d,u,x,p,s,m,%} +* - %% generates a single % +* - %m outputs strerror(errno). +* - 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. * * Called by vfprintf() and snprintf() to do the actual formatting. * The callers provide a callback function to save the formatted data.
@@ -305,15 +309,43 @@ int fseek(FILE *stream, long offset, int whence)* - with (NULL, 0) at the end of the __nolibc_printf. * If the callback returns non-zero __nolibc_printf() immediately returns -1. */ +typedef int (*__nolibc_printf_cb )(void *state,const char *buf,size_t size); +/* This code uses 'flag' variables that are indexed by the low 6 bits +* of characters to optimise checks for multiple characters. +* +* _NOLIBC_PF_FLAGS_CONTAIN(flags, 'a', 'b'. ...) +* returns non-zero if the bit for any of the specified characters is set. +* +* _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'a', 'b'. ...) +* returns the flag bit for ch if it is one of the specified characters. +* All the characters must be in the same 32 character block (non-alphabetic, +* upper case, or lower case) of the ASCII character set. +*/ +#define _NOLIBC_PF_FLAG (ch) (1u << ((ch) & 0x1f)) +#define _NOLIBC_PF_FLAG_NZ (ch) ((ch) ? _NOLIBC_PF_FLAG(ch) : 0) +#define _NOLIBC_PF_FLAG8 (cmp_1, cmp_2, cmp_3, cmp_4, cmp_5, cmp_6, cmp_7, cmp_8, ...) \ + (_NOLIBC_PF_FLAG_NZ(cmp_1) | _NOLIBC_PF_FLAG_NZ(cmp_2) | \ + _NOLIBC_PF_FLAG_NZ(cmp_3) | _NOLIBC_PF_FLAG_NZ(cmp_4) | \ + _NOLIBC_PF_FLAG_NZ(cmp_5) | _NOLIBC_PF_FLAG_NZ(cmp_6) | \ + _NOLIBC_PF_FLAG_NZ(cmp_7) | _NOLIBC_PF_FLAG_NZ(cmp_8)) +#define _NOLIBC_PF_FLAGS_CONTAIN (flags, ...) \ + ((flags) & _NOLIBC_PF_FLAG8(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0)) +#define _NOLIBC_PF_CHAR_IS_ONE_OF (ch, cmp_1, ...) \ + ((unsigned int)(ch) - (cmp_1 & 0xe0) > 0x1f ? 0 : \ + _NOLIBC_PF_FLAGS_CONTAIN(_NOLIBC_PF_FLAG(ch), cmp_1, __VA_ARGS__)) +static __attribute__((unused,format (printf,3 ,0 )))int __nolibc_printf (__nolibc_printf_cb cb,void *state,const char *fmt,va_list args) { -char lpref, ch; +char ch;unsigned long long v; +long long signed_v;int written, width, len; +unsigned int flags, ch_flag;char outbuf[21 ]; +char *out;const char *outstr; written =0 ;
@@ -324,6 +356,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_listbreak ; width =0 ; + flags =0 ;if (ch !='%' ) {while (*fmt && *fmt !='%' ) fmt++;
@@ -334,7 +367,14 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list/* we're in a format sequence */ - ch = *fmt++; +/* Conversion flag characters */ +while (1 ) { + ch = *fmt++; + ch_flag =_NOLIBC_PF_CHAR_IS_ONE_OF (ch,' ' ,'#' ,'+' ,'-' ,'0' ); +if (!ch_flag) +break ; + flags |= ch_flag; + }/* width */ while (ch >='0' && ch <='9' ) {
@@ -344,62 +384,78 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list ch = *fmt++; } -/* Length modifiers */ -if (ch =='l' ) { - lpref =1 ; - ch = *fmt++; -if (ch =='l' ) { - lpref =2 ; - ch = *fmt++; +/* Length modifier. +* They miss the conversion flags characters " #+-0" so can go into flags. +* Change ll to j (both always 64bits). +*/ + ch_flag =_NOLIBC_PF_CHAR_IS_ONE_OF (ch,'l' ,'j' ); +if (ch_flag !=0 ) { +if (ch =='l' && fmt[0 ] =='l' ) { + fmt++; + ch_flag =_NOLIBC_PF_FLAG ('j' ); } - }else if (ch =='j' ) { -/* intmax_t is long long */ - lpref =2 ; + flags |= ch_flag; ch = *fmt++; - }else { - lpref =0 ; } -if (ch =='c' || ch =='d' || ch =='u' || ch =='x' || ch =='p' ) { -char *out = outbuf; +/* Conversion specifiers. */ -if (ch =='p' ) +/* Numeric and pointer conversion specifiers. +* +* Use an explicit bound check (rather than _NOLIBC_PF_CHAR_IS_ONE_OF()) +* so ch_flag can be used later. +*/ + ch_flag =_NOLIBC_PF_FLAG (ch); +if ((ch >='a' && ch <='z' ) && +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'u' ,'x' ,'p' )) { +/* 'long' is needed for pointer conversions and ltz lengths. +* A single test can be used provided 'p' (the same bit as '0') +* is masked from flags. +*/ +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag | (flags & ~_NOLIBC_PF_FLAG ('p' )), +'p' ,'l' )) { v = va_arg(args, unsigned long); -else if (lpref) { -if (lpref >1 ) - v = va_arg(args, unsigned long long); -else - v = va_arg(args, unsigned long); - }else + signed_v = (long )v; + }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,'j' )) { + v = va_arg(args, unsigned long long); + signed_v = v; + }else { v = va_arg(args, unsigned int); + signed_v = (int )v; + } -if (ch =='d' ) { -/* sign-extend the value */ -if (lpref ==0 ) - v = (long long )(int )v; -else if (lpref ==1 ) - v = (long long )(long )v; +if (ch =='c' ) { +/* "%c" - single character. */ + outbuf[0 ] = v; + len =1 ; + outstr = outbuf; + goto do_output; } -switch (ch) { -case 'c' : - out[0 ] = v; - out[1 ] =0 ; -break ; -case 'd' : -i64toa_r (v, out); -break ; -case 'u' : + out = outbuf; + +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' )) { +/* "%d" and "%i" - signed decimal numbers. */ +if (signed_v <0 ) { + *out++ ='-' ; + v = -(signed_v +1 ); + v++; + } + } + +/* Convert the number to ascii in the required base. */ +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'u' )) { +/* Base 10 */ u64toa_r (v, out); -break ; -case 'p' : - *(out++) ='0' ; - *(out++) ='x' ; - __nolibc_fallthrough; -default :/* 'x' and 'p' above */ + }else { +/* Base 16 */ +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'p' )) { + *(out++) ='0' ; + *(out++) ='x' ; + }u64toh_r (v, out); -break ; } + outstr = outbuf; goto do_strlen_output; }
@@ -442,6 +498,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list do_output: written += len; +/* Stop gcc back-merging this code into one of the conditionals above. */ +_NOLIBC_OPTIMIZER_HIDE_VAR (len); + width -= len;while (width >0 ) {/* Output pad in 16 byte blocks with the small block first. */
Signature · 1 line
2.39.5
PATCH v4 15/23 tools/nolibc/printf: Add support for length modifiers tzqL and formats iX
In reply to [email protected]From: David Laight <[email protected]>
Length modifiers t (ptrdiff_t) and z (size_t) are aliases for l (long), q and L are 64bit the same as j (intmax).
Format i is an alias for d and X similar to x but uppper case. Supporting them is mostly just adding the relavant bit to the bit pattern used for maching characters.
Although %X is detected the output will be lower case.
Change/add tests to use conversions i and X, and length modifiers L and ll. Use the correct minimum value for "%Li".
---
v4: Split from the previous patch.
tools/include/nolibc/stdio.h | 27 ++++++++++++-------- tools/testing/selftests/nolibc/nolibc-test.c | 14 +++++++--- 2 files changed, 27 insertions(+), 14 deletions(-)
tools/include/nolibc/stdio.h +16 -11
@@ -291,10 +291,11 @@ int fseek(FILE *stream, long offset, int whence) } -/* printf(). Supports the following integer and string formats. -* - %[#-+ 0][width][{l,ll,j}]{c,d,u,x,p,s,m,%} +/* 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,%} * - %% 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.
@@ -386,9 +387,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list/* Length modifier. * They miss the conversion flags characters " #+-0" so can go into flags. -* Change ll to j (both always 64bits). +* Change both L and ll to j (all alwys 64bit). */ - ch_flag =_NOLIBC_PF_CHAR_IS_ONE_OF (ch,'l' ,'j' ); +if (ch =='L' ) + ch ='j' ; + ch_flag =_NOLIBC_PF_CHAR_IS_ONE_OF (ch,'l' ,'t' ,'z' ,'j' ,'q' );if (ch_flag !=0 ) {if (ch =='l' && fmt[0 ] =='l' ) { fmt++;
@@ -403,20 +406,22 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list/* Numeric and pointer conversion specifiers. * * Use an explicit bound check (rather than _NOLIBC_PF_CHAR_IS_ONE_OF()) -* so ch_flag can be used later. +* so that 'X' can be allowed through. +* 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same +* value for both. */ ch_flag =_NOLIBC_PF_FLAG (ch); -if ((ch >='a' && ch <='z' ) && -_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'u' ,'x' ,'p' )) { +if (((ch >='a' && ch <='z' ) || ch =='X' ) && +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'x' ,'p' )) {/* 'long' is needed for pointer conversions and ltz lengths. * A single test can be used provided 'p' (the same bit as '0') * is masked from flags. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag | (flags & ~_NOLIBC_PF_FLAG ('p' )), -'p' ,'l' )) { +'p' ,'l' ,'t' ,'z' )) { v = va_arg(args, unsigned long); signed_v = (long )v; - }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,'j' )) { + }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,'j' ,'q' )) { v = va_arg(args, unsigned long long); signed_v = v; }else {
@@ -434,7 +439,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list out = outbuf; -if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' )) { +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' )) {/* "%d" and "%i" - signed decimal numbers. */ if (signed_v <0 ) { *out++ ='-' ;
@@ -444,7 +449,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list }/* Convert the number to ascii in the required base. */ -if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'u' )) { +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' ,'u' )) {/* Base 10 */ u64toa_r (v, out); }else {
tools/testing/selftests/nolibc/nolibc-test.c +11 -3
@@ -1826,11 +1826,19 @@ static int run_printf(int min, int max)CASE_TEST (number);EXPECT_VFPRINTF (1 ,"1234" ,"%d" ,1234 );break ;CASE_TEST (negnumber);EXPECT_VFPRINTF (1 ,"-1234" ,"%d" ,-1234 );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 ; +CASE_TEST (unsigned_max);EXPECT_VFPRINTF (1 ,"4294967295" ,"%u" , ~0u );break ;CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );break ; -CASE_TEST (hex);EXPECT_VFPRINTF (1 ,"f" ,"%x" ,0xf );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 (pointer);EXPECT_VFPRINTF (1 ,"0x1" ,"%p" , (void *)0x1 );break ; -CASE_TEST (uintmax_t);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" ,0xffffffffffffffffULL );break ; -CASE_TEST (intmax_t);EXPECT_VFPRINTF (1 ,"-9223372036854775807" ,"%jd" ,0x8000000000000001LL );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 ; +CASE_TEST (intmax_max);EXPECT_VFPRINTF (1 ,"9223372036854775807" ,"%lld" , ~0ULL >>1 );break ; +CASE_TEST (intmax_min);EXPECT_VFPRINTF (1 ,"-9223372036854775808" ,"%Li" , (~0ULL >>1 ) +1 );break ; +CASE_TEST (uintmax_max);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" , ~0ULL );break ;CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ;CASE_TEST (string_width);EXPECT_VFPRINTF (1 ," 1" ,"%10s" ,"1" );break ;CASE_TEST (number_width);EXPECT_VFPRINTF (1 ," 1" ,"%10d" ,1 );break ;
Signature · 1 line
2.39.5
PATCH v4 16/23 tools/nolibc/printf: Handle "%s" with the numeric formats
In reply to [email protected]From: David Laight <[email protected]>
Avoids the extra va_arg() call with is non-trivial on a lot of modern ABI.
---
Unchanched for v4.
Changes for v3:
- Moved to its own patch (part of patch 7 in v2)..
- Fix 32bit compile.
tools/include/nolibc/stdio.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-)
tools/include/nolibc/stdio.h +12 -10
@@ -412,13 +412,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ ch_flag =_NOLIBC_PF_FLAG (ch);if (((ch >='a' && ch <='z' ) || ch =='X' ) && -_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'x' ,'p' )) { -/* 'long' is needed for pointer conversions and ltz lengths. +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'x' ,'p' ,'s' )) { +/* 'long' is needed for pointer/string conversions and ltz lengths. * A single test can be used provided 'p' (the same bit as '0') * is masked from flags. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag | (flags & ~_NOLIBC_PF_FLAG ('p' )), -'p' ,'l' ,'t' ,'z' )) { +'p' ,'s' ,'l' ,'t' ,'z' )) { v = va_arg(args, unsigned long); signed_v = (long )v; }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,'j' ,'q' )) {
@@ -437,6 +437,15 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; } +if (ch =='s' ) { +/* "%s" - character string. */ + outstr = (const char *)(uintptr_t )v; +if (!outstr) { + outstr ="(null)" ; + } + goto do_strlen_output; + } + out = outbuf;if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' )) {
@@ -465,13 +474,6 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_strlen_output; } -if (ch =='s' ) { - outstr = va_arg(args, char *); -if (!outstr) - outstr="(null)" ; - goto do_strlen_output; - } -if (ch =='m' ) {#ifdef NOLIBC_IGNORE_ERRNO outstr ="unknown error" ;
Signature · 1 line
2.39.5
Re: [PATCH v4 next 16/23] tools/nolibc/printf: Handle "%s" with the numeric formats
Quoted 3 lines · On Mon, Mar 02, 2026 at 10:18:08AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Avoids the extra
va_arg()call with is non-trivial on a lot of
^^^^
s/with/which/ I guess :-)
Quoted 3 lines
modern ABI.
Signed-off-by David Laight <[email protected]>
thanks!
Willy
Quoted 64 lines
---
Unchanched for v4.
Changes for v3:
- Moved to its own patch (part of patch 7 in v2)..
- Fix 32bit compile.tools/include/nolibc/stdio.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-)tools/include/nolibc/stdio.h +12
-10@@ -412,13 +412,13 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ ch_flag =_NOLIBC_PF_FLAG (ch);if (((ch >='a' && ch <='z' ) || ch =='X' ) && -_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'x' ,'p' )) { -/* 'long' is needed for pointer conversions and ltz lengths. +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'x' ,'p' ,'s' )) { +/* 'long' is needed for pointer/string conversions and ltz lengths. * A single test can be used provided 'p' (the same bit as '0') * is masked from flags. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag | (flags & ~_NOLIBC_PF_FLAG ('p' )), -'p' ,'l' ,'t' ,'z' )) { +'p' ,'s' ,'l' ,'t' ,'z' )) { v = va_arg(args, unsigned long); signed_v = (long )v; }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,'j' ,'q' )) {@@ -437,6 +437,15 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; } +if (ch =='s' ) { +/* "%s" - character string. */ + outstr = (const char *)(uintptr_t )v; +if (!outstr) { + outstr ="(null)" ; + } + goto do_strlen_output; + } + out = outbuf;if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' )) {@@ -465,13 +474,6 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_strlen_output; } -if (ch =='s' ) { - outstr = va_arg(args, char *); -if (!outstr) - outstr="(null)" ; - goto do_strlen_output; - } -if (ch =='m' ) {#ifdef NOLIBC_IGNORE_ERRNO outstr ="unknown error" ;Signature · 1 line
2.39.5
PATCH v4 17/23 tools/nolibc/printf: Prepend sign to converted number
In reply to [email protected]From: David Laight <[email protected]>
Instead of appending the converted number to the sign, convert first and then prepend the sign (or "0x").
Use the length returned by u64toh_r() instead of calling strlen().
Needed so that zero padding can be inserted between the sign and digits in an upcoming patch.
---
Changes for v4:
- Split from the patch that supported modifiers " +#".
tools/include/nolibc/stdio.h | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-)
tools/include/nolibc/stdio.h +25 -9
@@ -345,9 +345,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_listlong 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 ) {
@@ -446,32 +447,47 @@ 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++; } } +/* 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 ; +/* 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); + len =u64toa_r (v, out); }else {/* Base 16 */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'p' )) { - *(out++) ='0' ; - *(out++) ='x' ; +/* "%p" needs "0x" prepending. */ + sign_prefix ='x' |'0' <<8 ; } -u64toh_r (v, out); + len =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' ) {
Signature · 1 line
2.39.5
Re: [PATCH 17/23] tools/nolibc/printf: Prepend sign to converted number
Quoted 35 lines · On Mon, Mar 02, 2026 at 10:18:09AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Instead of appending the converted number to the sign, convert first and then prepend the sign (or "0x").
Use the length returned byu64toh_r()instead of callingstrlen().Needed so that zero padding can be inserted between the sign and digits in an upcoming patch.
Signed-off-by David Laight <[email protected]>---
Changes for v4:
- Split from the patch that supported modifiers " +#".tools/include/nolibc/stdio.h | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-)tools/include/nolibc/stdio.h +2
-1@@ -345,9 +345,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_listlong 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 ) {
tools/include/nolibc/stdio.h int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list · line 451
Quoted 6 lines
tools/include/nolibc/stdio.h +2
-1@@ -446,32 +447,47 @@ 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 ;
It would help to mention that last chars are on the lowest bits, because it's not totally intuitive that these will be consumed in reverse order at the end. Also even if obvious once you see it, mentioning that we stop on the first zero byte would help reviewers ;-)
Quoted 19 lines
tools/include/nolibc/stdio.h +8
-1if (_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++; } } +/* 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 ; +/* Convert the number to ascii in the required base. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' ,'u' )) {
tools/include/nolibc/stdio.h int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list · line 477
Quoted 10 lines
tools/include/nolibc/stdio.h +3
-3/* Base 10 */ -u64toa_r (v, out); + len =u64toa_r (v, out); }else {/* Base 16 */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'p' )) { - *(out++) ='0' ; - *(out++) ='x' ; +/* "%p" needs "0x" prepending. */ + sign_prefix ='x' |'0' <<8 ;
This one could have been more readable in the other order:
sign_prefix = '0' << 8 | 'x';
Quoted 9 lines
tools/include/nolibc/stdio.h +3
-3} -u64toh_r (v, out); + len =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 ) {
tools/include/nolibc/stdio.h int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list · line 493
Quoted 10 lines
tools/include/nolibc/stdio.h +7
-0+/* 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' ) {
Overall I'm fine with this and my comments only apply to any potential respin.
Willy
PATCH v4 18/23 tools/nolibc/printf: Add support for conversion flags space and plus
In reply to [email protected]From: David Laight <[email protected]>
Flags ' ' and '+' are sign characters for positive numbers.
---
v4: Split from the previous patch and support for the # flag.
tools/include/nolibc/stdio.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
tools/include/nolibc/stdio.h +5 -1
@@ -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.
@@ -456,6 +456,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list 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 =' ' ; } }
Signature · 1 line
2.39.5
Re: [PATCH v4 next 18/23] tools/nolibc/printf: Add support for conversion flags space and plus
Quoted 5 lines · On Mon, Mar 02, 2026 at 10:18:10AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Flags ' ' and '+' are sign characters for positive numbers.
Signed-off-by David Laight <[email protected]>
willy
Quoted 33 lines
---
v4: Split from the previous patch and support for the # flag.
tools/include/nolibc/stdio.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)tools/include/nolibc/stdio.h +5
-1@@ -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. @@ -456,6 +456,10 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list 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 =' ' ; } }Signature · 1 line
2.39.5
PATCH v4 19/23 tools/nolibc/printf: Special case 0 and add support for %#x
In reply to [email protected]From: David Laight <[email protected]>
The output for %#x is almost the same as that for %p, both output in hexadecimal with a leading "0x".
However for zero %#x should just output "0" (the same as decimal and ocal). For %p match glibc and output "(nil)" rather than "0x0" or "0".
Add tests for "%#x", "% d", "%+d" and passing NULL to "%p".
---
v4: Split into its' own patch.
tools/include/nolibc/stdio.h | 38 ++++++++++++++------ tools/testing/selftests/nolibc/nolibc-test.c | 3 ++ 2 files changed, 30 insertions(+), 11 deletions(-)
tools/include/nolibc/stdio.h +27 -11
@@ -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.
@@ -410,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.
@@ -470,17 +473,30 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ out = outbuf +2 ; -/* 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 (v ==0 ) { +/* There are special rules for zero. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'p' )) { -/* "%p" needs "0x" prepending. */ - sign_prefix ='x' |'0' <<8 ; +/* "%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); } - len =u64toh_r (v, out); }/* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
tools/testing/selftests/nolibc/nolibc-test.c +3 -0
@@ -1825,6 +1825,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 ;
@@ -1832,7 +1833,9 @@ static int run_printf(int min, int max)CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );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 ;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 ;
Signature · 1 line
2.39.5
Re: [PATCH v4 next 19/23] tools/nolibc/printf: Special case 0 and add support for %#x
Quoted 10 lines · On Mon, Mar 02, 2026 at 10:18:11AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
The output for %#x is almost the same as that for %p, both output in hexadecimal with a leading "0x".
However for zero %#x should just output "0" (the same as decimal and ocal). For %p match glibc and output "(nil)" rather than "0x0" or "0".Add tests for "%#x", "% d", "%+d" and passing NULL to "%p".
Signed-off-by David Laight <[email protected]>
Willy
Quoted 98 lines
---
v4: Split into its' own patch.
tools/include/nolibc/stdio.h | 38 ++++++++++++++------ tools/testing/selftests/nolibc/nolibc-test.c | 3 ++ 2 files changed, 30 insertions(+), 11 deletions(-)tools/include/nolibc/stdio.h +27
-11@@ -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. @@ -410,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. @@ -470,17 +473,30 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ out = outbuf +2 ; -/* 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 (v ==0 ) { +/* There are special rules for zero. */ if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'p' )) { -/* "%p" needs "0x" prepending. */ - sign_prefix ='x' |'0' <<8 ; +/* "%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); } - len =u64toh_r (v, out); }/* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */ tools/testing/selftests/nolibc/nolibc-test.c +3
-0@@ -1825,6 +1825,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 ;@@ -1832,7 +1833,9 @@ static int run_printf(int min, int max)CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );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 ;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 ;Signature · 1 line
2.39.5
PATCH v4 20/23 tools/nolibc/printf: Add support for left aligning fields
In reply to [email protected]From: David Laight <[email protected]>
Output the characters before or after the pad - writing the pad takes more code.
Include additional/changed tests
---
Unchanged for v4.
Changes for v3:
- Formally part of patch 7.
tools/include/nolibc/stdio.h | 6 +++++- tools/testing/selftests/nolibc/nolibc-test.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-)
tools/include/nolibc/stdio.h +5 -1
@@ -544,7 +544,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list/* Stop gcc back-merging this code into one of the conditionals above. */ _NOLIBC_OPTIMIZER_HIDE_VAR (len); +/* Output the characters on the required side of any padding. */ width -= len; + flags =_NOLIBC_PF_FLAGS_CONTAIN (flags,'-' ); +if (flags &&cb (state, outstr, len) !=0 ) +return -1 ;while (width >0 ) {/* Output pad in 16 byte blocks with the small block first. */ int pad_len = ((width -1 ) &15 ) +1 ;
@@ -553,7 +557,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_listif (cb (state," " , pad_len) !=0 )return -1 ; } -if (cb (state, outstr, len) !=0 ) +if (!flags &&cb (state, outstr, len) !=0 )return -1 ; }
tools/testing/selftests/nolibc/nolibc-test.c +3 -1
@@ -1845,9 +1845,11 @@ static int run_printf(int min, int max)CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ;CASE_TEST (string_width);EXPECT_VFPRINTF (1 ," 1" ,"%10s" ,"1" );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 ;CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%25d" ,1 );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 ; +CASE_TEST (errno-neg); errno =-22 ;EXPECT_VFPRINTF (is_nolibc,"errno=-22 " ,"%-12m" );break ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ;CASE_TEST (printf_error);EXPECT_ZR (1 ,test_printf_error ());break ;case __LINE__:
Signature · 1 line
2.39.5
Re: [PATCH v4 next 20/23] tools/nolibc/printf: Add support for left aligning fields
Quoted 7 lines · On Mon, Mar 02, 2026 at 10:18:12AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Output the characters before or after the pad - writing the pad takes more code.
Include additional/changed tests
Signed-off-by David Laight <[email protected]>
Willy
Quoted 55 lines
---
Unchanged for v4.
Changes for v3:
- Formally part of patch 7.tools/include/nolibc/stdio.h | 6 +++++- tools/testing/selftests/nolibc/nolibc-test.c | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-)tools/include/nolibc/stdio.h +5
-1@@ -544,7 +544,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list/* Stop gcc back-merging this code into one of the conditionals above. */ _NOLIBC_OPTIMIZER_HIDE_VAR (len); +/* Output the characters on the required side of any padding. */ width -= len; + flags =_NOLIBC_PF_FLAGS_CONTAIN (flags,'-' ); +if (flags &&cb (state, outstr, len) !=0 ) +return -1 ;while (width >0 ) {/* Output pad in 16 byte blocks with the small block first. */ int pad_len = ((width -1 ) &15 ) +1 ;@@ -553,7 +557,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_listif (cb (state," " , pad_len) !=0 )return -1 ; } -if (cb (state, outstr, len) !=0 ) +if (!flags &&cb (state, outstr, len) !=0 )return -1 ; }tools/testing/selftests/nolibc/nolibc-test.c +3
-1@@ -1845,9 +1845,11 @@ static int run_printf(int min, int max)CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ;CASE_TEST (string_width);EXPECT_VFPRINTF (1 ," 1" ,"%10s" ,"1" );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 ;CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%25d" ,1 );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 ; +CASE_TEST (errno-neg); errno =-22 ;EXPECT_VFPRINTF (is_nolibc,"errno=-22 " ,"%-12m" );break ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ;CASE_TEST (printf_error);EXPECT_ZR (1 ,test_printf_error ());break ;case __LINE__:Signature · 1 line
2.39.5
PATCH v4 21/23 tools/nolibc/printf: Add support for zero padding and field precision
In reply to [email protected]From: David Laight <[email protected]>
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
--
Unchanged for v4.
Changes for v3:
- Formerly patch 8
- Extra comments.
- Adjust offsets in outbuf[], support adding 31 '0' digits while still allowing enough room for octal support to be added and keeping outbuf[] at a nice round 56 bytes.
Changes for v2:
- These changes were previously in patch 9.
However you need to apply the old patch 10 to get anything like the same source. The files then more of less match apart from 'c' being renamed 'ch' and the 'magic' #defines.
tools/include/nolibc/stdio.h | 77 ++++++++++++++++---- tools/testing/selftests/nolibc/nolibc-test.c | 19 ++++- 2 files changed, 79 insertions(+), 17 deletions(-)
tools/include/nolibc/stdio.h +62 -15
@@ -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_listchar 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.
@@ -446,6 +456,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list outstr = (const char *)(uintptr_t )v;if (!outstr) { outstr ="(null)" ; +/* Match glibc, nothing output if precision too small */ + len = precision >=6 ?6 :0 ; + goto do_output; } goto do_strlen_output; }
@@ -467,11 +480,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. */
@@ -481,6 +494,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 ;
@@ -499,6 +517,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. */
@@ -533,8 +580,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 ;
tools/testing/selftests/nolibc/nolibc-test.c +17 -2
@@ -1830,12 +1830,15 @@ static int run_printf(int min, int max)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 ;CASE_TEST (unsigned_max);EXPECT_VFPRINTF (1 ,"4294967295" ,"%u" , ~0u );break ; -CASE_TEST (char);EXPECT_VFPRINTF (1 ,"c" ,"%c" ,'c' );break ; +CASE_TEST (char);EXPECT_VFPRINTF (1 ,"|c|d| e|" ,"|%c|%.0c|%4c|" ,'c' ,'d' ,'e' );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 ; +CASE_TEST (hex_alt_prec);EXPECT_VFPRINTF (1 ,"| 0x02|0x03| 0x123|" ,"|%#5.2x|%#04x|%#6.2x|" ,2 ,3 ,0x123 );break ; +CASE_TEST (hex_0_alt);EXPECT_VFPRINTF (1 ,"|0|0000| 00|" ,"|%#x|%#04x|%#5.2x|" ,0 ,0 ,0 );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 (pointer_NULL);EXPECT_VFPRINTF (1 ,"|(nil)|(nil)|" ,"|%p|%.4p|" , (void *)0 , (void *)0 );break ; +CASE_TEST (string_NULL);EXPECT_VFPRINTF (1 ,"|(null)||(null)|" ,"|%s|%.5s|%.6s|" , (void *)0 , (void *)0 , (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 ;
@@ -1844,10 +1847,22 @@ static int run_printf(int min, int max)CASE_TEST (uintmax_max);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" , ~0ULL );break ;CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );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 (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 ;CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%25d" ,1 );break ; +CASE_TEST (width_tr_lft);EXPECT_VFPRINTF (1 ,"1 " ,"%-30d" ,1 );break ; +CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"0000000005" ,"%010d" ,5 );break ; +CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"|0000000005|0x1234|" ,"|%010d|%#01x|" ,5 ,0x1234 );break ; +CASE_TEST (num_pad_neg);EXPECT_VFPRINTF (1 ,"-000000005" ,"%010d" ,-5 );break ; +CASE_TEST (num_pad_hex);EXPECT_VFPRINTF (1 ,"00fffffffb" ,"%010x" ,-5 );break ; +CASE_TEST (num_pad_trunc);EXPECT_VFPRINTF (is_nolibc," 0000000000000000000000000000005" ,"%035d" ,5 );break ;/* max 31 '0' can be added */ +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 (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 ;CASE_TEST (scanf);EXPECT_ZR (1 ,test_scanf ());break ;
Signature · 1 line
2.39.5
PATCH v4 22/23 tools/nolibc/printf: Add support for octal output
In reply to [email protected]From: David Laight <[email protected]>
Octal output isn't often used, but adding it costs very little.
Supporting "%#o" is mildly annoying, it has to add a leading '0' if there isn't one present. In simple cases this is the same as adding a sign of '0' - but that adds an extra '0' in a few places.
So you need 3 tests, %o, # and no leading '0' (which can only be checked after the zero pad for precision).
If all the test are deferred until after zero padding then too many values are 'live' across the call to _nolibc_u64toa_base() and get spilled to stack. Hence the check that ignores the 'sign' if it is the same as the first character of the output string.
Add tests for octal output.
---
v4:
- Increase VFPRINTF_LEN to 25 to that 64bit octal output isn't truncated. Change truncation tests to match.
(Was done earlier in v3.)
New patch for v3.
Relies on the changes to u64toa_r() etc in stdlib.h (patch 2).
tools/include/nolibc/stdio.h | 50 ++++++++++++++------ tools/testing/selftests/nolibc/nolibc-test.c | 9 ++-- 2 files changed, 41 insertions(+), 18 deletions(-)
tools/include/nolibc/stdio.h +35 -15
@@ -292,7 +292,7 @@ int fseek(FILE *stream, long offset, int whence)/* printf(). Supports most of the normal integer and string formats. -* - %[#0-+ ][width|*[.precision|*}][{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,o,x,X,p,s,m,%} * - %% generates a single % * - %m outputs strerror(errno). * - %X outputs a..f the same as %x.
@@ -426,7 +426,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ 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' )) { +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'o' ,'x' ,'p' ,'s' )) {/* 'long' is needed for pointer/string conversions and ltz lengths. * A single test can be used provided 'p' (the same bit as '0') * is masked from flags.
@@ -477,12 +477,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,' ' )) { sign_prefix =' ' ; } + }else { +/* "#o" requires that the output always starts with a '0'. +* This needs another check after any zero padding to avoid +* adding an extra leading '0'. +*/ +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'o' ) && +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'#' -1 )) + sign_prefix ='0' ; }/* The value is converted offset into the buffer so that * 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. +* The longest digit string is 22 + 1 for octal conversions. */ out = outbuf +2 +31 ;
@@ -495,7 +502,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; }if (!precision) { -/* Explicit %nn.0d, no digits output */ +/* Explicit %nn.0d, no digits output (except for %#.0o) */ len =0 ; goto prepend_sign; }
@@ -504,17 +511,23 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list len =1 ; }else {/* Convert the number to ascii in the required base. */ +unsigned long long recip; +unsigned int base;if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' ,'u' )) { -/* Base 10 */ - len =u64toa_r (v, out); + base =10 ; + recip =_NOLIBC_U64TOA_RECIP (10 ); + }else if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'o' )) { + base =8 ; + recip =_NOLIBC_U64TOA_RECIP (8 ); }else { -/* Base 16 */ + base =16 ; + recip =_NOLIBC_U64TOA_RECIP (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); } + len =_nolibc_u64toa_base (v, out, base, recip); }/* Add zero padding */
@@ -545,13 +558,20 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } } +/* %#o has set sign_prefix to '0', but we don't want so add an extra +* leading zero here. +* Since the only other byte values of sign_prefix are ' ', '+' and '-' +* it is enough to check that out[] doesn't already start with sign_prefix. +*/ +if (sign_prefix != *out) { 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. */ -_NOLIBC_OPTIMIZER_HIDE_VAR (len); - len++; - *--out = sign_prefix; +/* 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;
tools/testing/selftests/nolibc/nolibc-test.c +6 -3
@@ -1663,7 +1663,7 @@ int run_stdlib(int min, int max)#define EXPECT_VFPRINTF (cond, expected, fmt, ...) \ do { if (!(cond)) result(llen, SKIPPED); else ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__); } while (0) -#define VFPRINTF_LEN 20 +#define VFPRINTF_LEN 25static int expect_vfprintf (int llen,const char *expected,const char *fmt, ...) {char buf[VFPRINTF_LEN +80 ];
@@ -1831,6 +1831,9 @@ static int run_printf(int min, int max)CASE_TEST (signed_min);EXPECT_VFPRINTF (1 ,"-2147483648" ,"%i" , (~0u >>1 ) +1 );break ;CASE_TEST (unsigned_max);EXPECT_VFPRINTF (1 ,"4294967295" ,"%u" , ~0u );break ;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 (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 ;
@@ -1845,13 +1848,13 @@ static int run_printf(int min, int max)CASE_TEST (intmax_max);EXPECT_VFPRINTF (1 ,"9223372036854775807" ,"%lld" , ~0ULL >>1 );break ;CASE_TEST (intmax_min);EXPECT_VFPRINTF (1 ,"-9223372036854775808" ,"%Li" , (~0ULL >>1 ) +1 );break ;CASE_TEST (uintmax_max);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" , ~0ULL );break ; -CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ; +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 (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 ; -CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%25d" ,1 );break ; +CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%30d" ,1 );break ;CASE_TEST (width_tr_lft);EXPECT_VFPRINTF (1 ,"1 " ,"%-30d" ,1 );break ;CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"0000000005" ,"%010d" ,5 );break ;CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"|0000000005|0x1234|" ,"|%010d|%#01x|" ,5 ,0x1234 );break ;
Signature · 1 line
2.39.5
Re: [PATCH v4 next 22/23] tools/nolibc/printf: Add support for octal output
Quoted 4 lines · On Mon, Mar 02, 2026 at 10:18:14AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Octal output isn't often used, but adding it costs very little.
Quoted 9 lines
Supporting "%#o" is mildly annoying, it has to add a leading '0' if there isn't one present. In simple cases this is the same as adding a sign of '0' - but that adds an extra '0' in a few places.
So you need 3 tests, %o, # and no leading '0' (which can only be checked after the zero pad for precision).
If all the test are deferred until after zero padding then too many values are 'live' across the call to_nolibc_u64toa_base()and get spilled to stack. Hence the check that ignores the 'sign' if it is the same as the first character of the output string.
Looks good and can indeed be useful to display file modes or umasks.
Quoted 4 lines
Add tests for octal output.
Signed-off-by David Laight <[email protected]>
willy
Quoted 163 lines
---
v4:
- IncreaseVFPRINTF_LENto 25 to that 64bit octal output isn't truncated. Change truncation tests to match.
(Was done earlier in v3.)New patch for v3.
Relies on the changes tou64toa_r()etc instdlib.h(patch 2).tools/include/nolibc/stdio.h | 50 ++++++++++++++------ tools/testing/selftests/nolibc/nolibc-test.c | 9 ++-- 2 files changed, 41 insertions(+), 18 deletions(-)tools/include/nolibc/stdio.h +35
-15@@ -292,7 +292,7 @@ int fseek(FILE *stream, long offset, int whence)/* printf(). Supports most of the normal integer and string formats. -* - %[#0-+ ][width|*[.precision|*}][{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,o,x,X,p,s,m,%} * - %% generates a single % * - %m outputs strerror(errno). * - %X outputs a..f the same as %x. @@ -426,7 +426,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list*/ 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' )) { +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'c' ,'d' ,'i' ,'u' ,'o' ,'x' ,'p' ,'s' )) {/* 'long' is needed for pointer/string conversions and ltz lengths. * A single test can be used provided 'p' (the same bit as '0') * is masked from flags. @@ -477,12 +477,19 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list }else if (_NOLIBC_PF_FLAGS_CONTAIN (flags,' ' )) { sign_prefix =' ' ; } + }else { +/* "#o" requires that the output always starts with a '0'. +* This needs another check after any zero padding to avoid +* adding an extra leading '0'. +*/ +if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'o' ) && +_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'#' -1 )) + sign_prefix ='0' ; }/* The value is converted offset into the buffer so that * 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. +* The longest digit string is 22 + 1 for octal conversions. */ out = outbuf +2 +31 ;@@ -495,7 +502,7 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; }if (!precision) { -/* Explicit %nn.0d, no digits output */ +/* Explicit %nn.0d, no digits output (except for %#.0o) */ len =0 ; goto prepend_sign; }@@ -504,17 +511,23 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list len =1 ; }else {/* Convert the number to ascii in the required base. */ +unsigned long long recip; +unsigned int base;if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'d' ,'i' ,'u' )) { -/* Base 10 */ - len =u64toa_r (v, out); + base =10 ; + recip =_NOLIBC_U64TOA_RECIP (10 ); + }else if (_NOLIBC_PF_FLAGS_CONTAIN (ch_flag,'o' )) { + base =8 ; + recip =_NOLIBC_U64TOA_RECIP (8 ); }else { -/* Base 16 */ + base =16 ; + recip =_NOLIBC_U64TOA_RECIP (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); } + len =_nolibc_u64toa_base (v, out, base, recip); }/* Add zero padding */ @@ -545,13 +558,20 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } } +/* %#o has set sign_prefix to '0', but we don't want so add an extra +* leading zero here. +* Since the only other byte values of sign_prefix are ' ', '+' and '-' +* it is enough to check that out[] doesn't already start with sign_prefix. +*/ +if (sign_prefix != *out) { 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. */ -_NOLIBC_OPTIMIZER_HIDE_VAR (len); - len++; - *--out = sign_prefix; +/* 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;tools/testing/selftests/nolibc/nolibc-test.c +6
-3@@ -1663,7 +1663,7 @@ int run_stdlib(int min, int max)#define EXPECT_VFPRINTF (cond, expected, fmt, ...) \ do { if (!(cond)) result(llen, SKIPPED); else ret += expect_vfprintf(llen, expected, fmt, ##__VA_ARGS__); } while (0) -#define VFPRINTF_LEN 20 +#define VFPRINTF_LEN 25static int expect_vfprintf (int llen,const char *expected,const char *fmt, ...) {char buf[VFPRINTF_LEN +80 ];@@ -1831,6 +1831,9 @@ static int run_printf(int min, int max)CASE_TEST (signed_min);EXPECT_VFPRINTF (1 ,"-2147483648" ,"%i" , (~0u >>1 ) +1 );break ;CASE_TEST (unsigned_max);EXPECT_VFPRINTF (1 ,"4294967295" ,"%u" , ~0u );break ;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 (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 ;@@ -1845,13 +1848,13 @@ static int run_printf(int min, int max)CASE_TEST (intmax_max);EXPECT_VFPRINTF (1 ,"9223372036854775807" ,"%lld" , ~0ULL >>1 );break ;CASE_TEST (intmax_min);EXPECT_VFPRINTF (1 ,"-9223372036854775808" ,"%Li" , (~0ULL >>1 ) +1 );break ;CASE_TEST (uintmax_max);EXPECT_VFPRINTF (1 ,"18446744073709551615" ,"%ju" , ~0ULL );break ; -CASE_TEST (truncation);EXPECT_VFPRINTF (1 ,"0123456789012345678901234" ,"%s" ,"0123456789012345678901234" );break ; +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 (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 ; -CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%25d" ,1 );break ; +CASE_TEST (width_trunc);EXPECT_VFPRINTF (1 ," 1" ,"%30d" ,1 );break ;CASE_TEST (width_tr_lft);EXPECT_VFPRINTF (1 ,"1 " ,"%-30d" ,1 );break ;CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"0000000005" ,"%010d" ,5 );break ;CASE_TEST (number_pad);EXPECT_VFPRINTF (1 ,"|0000000005|0x1234|" ,"|%010d|%#01x|" ,5 ,0x1234 );break ;Signature · 1 line
2.39.5
PATCH v4 23/23 selftests/nolibc: Use printf variable field widths and precisions
In reply to [email protected]From: David Laight <[email protected]>
Now that printf supports '*' for field widths and precisions then can be used to simplify the test output.
- aligning the "[OK]" strings.
- reporting the expected sprintf() output when there is a mismatch.
---
Unchanged for v4.
tools/testing/selftests/nolibc/nolibc-test.c | 26 ++++---------------- 1 file changed, 5 insertions(+), 21 deletions(-)
tools/testing/selftests/nolibc/nolibc-test.c +5 -21
@@ -156,21 +156,6 @@ static const char *errorname(int err) } } -static void align_result (size_t llen) -{ -const size_t align =64 ; -char buf[align]; -size_t n; - -if (llen >= align) -return ; - - n = align - llen; -memset (buf,' ' , n); - buf[n] ='\0' ; -fputs (buf, stdout); -} -enum RESULT {OK ,FAIL ,
@@ -188,8 +173,10 @@ static void result(int llen, enum RESULT r)else msg =" [FAIL]" ; -align_result (llen); -puts (msg); + llen =64 - llen; +if (llen <0 ) + llen =0 ; +printf ("%*s%s\n" , llen,"" , msg); }/* The tests below are intended to be used by the macroes, which evaluate
@@ -1692,10 +1679,7 @@ static int expect_vfprintf(int llen, const char *expected, const char *fmt, ...) }if (memcmp (expected, buf, cmp_len) || buf[cmp_len]) { -/* Copy and truncate until "%.*s" supported */ -memcpy (buf, expected, cmp_len); - buf[cmp_len] =0 ; - llen +=printf (" should be \"%s\"" , buf); + llen +=printf (" should be \"%.*s\"" ,VFPRINTF_LEN , expected);result (llen,FAIL );return 1 ; }
Signature · 1 line
2.39.5
Willy Tarreau
maintainerHi David,
Quoted 42 lines · On Mon, Mar 02, 2026 at 10:17:52AM +0000, [email protected] wrote:
From: David Laight <[email protected]>
Update
printf()so that it handles almost all the non-fp formats. In particular:
- Left alignment.
- Zero padding.
- Alternate form "%#x" and "%#o".
- Field precision.
- Variable field width and precision.
- Width modifiers q, L, t and z.
- Conversion specifiers i, o and X (X generates lower case). About the only things that are missing are wide chanacters and floating point.The tests are updated to match.
Bloat/savings (in nolibc-test, but excluding the program) to patch 14:
(Measured for v3)
Function old new delta
_nolibc_u64toa_base.isra - 143 +143
strerror - 78 +78
__nolibc_sprintf_cb58 91 +33
itoa_r.isra 60 75 +15
utoa_r.isra 144 - -144
__nolibc_printf1081 729 -352
(All these functions include ~40 bytes for the stack protector code.) utoa_r.isra and _nolibc_u64toa_base.isra pretty much cancel each other out. itoa_r.isra grows slightly since it calls_nolibc_u64toa_base().strerror()used to be inlined, but over half of it is the stack check. While some of the code added to__nolibc_sprintf_cb()has come out of__nolibc_printf()16-20 bytes is removed from the caller.
So there is a net saving of about 280 bytes (including losing a copy of the number to ascii code).The later patches add code back in:
patch 13 - conversion flags " +#" +80 bytes
patch 14 - left aligning fields +38 bytes
patch 15 - zero padding and field precision +260 bytes
patch 16 - octal output +34 bytes
So probably about +130 bytes, but it will depend on what the application actually calls and inlining decisions made by the compiler. (All x86-64, other architectures will vary.)
Quoted 3 lines
The biggest size change is probably removing the .data from
strerror(). This reduced the program binary file by 4k if it is the only initialised data in a small program.
Thanks for this work. I'm personally fine with the series overall, even though there are tiny comments here and there but nothing blocking. Let's wait for Thomas' finaly review however.
I agree that having a more featureful printf() will definitely help in various areas since it's used a lot in low-level userland code and testing.
Thanks,
Willy
Thomas Weißschuh
maintainerHi David,
Quoted 1 line · On 2026-03-02 10:17:52+0000, [email protected] wrote:
From: David Laight <[email protected]>
(...)
I am happy with the patches of this series.
Quoted 11 lines
David Laight (23):
tools/nolibc: Add_NOLIBC_OPTIMIZER_HIDE_VAR()tocompiler.h
tools/nolibc/printf: Move snprintf length check to callback selftests/nolibc: Return correct value when printf test fails selftests/nolibc: checkvsnprintf()output buffer before the length selftests/nolibc: Use length of 'expected' string to checksnprintf()output selftests/nolibc: Check thatsnprintf()doesn't write beyond the buffer end selftests/nolibc: LetEXPECT_VFPRINTF()tests be skippedselftests/nolibc: Rename w to written in
expect_vfprintf()
Unfortunately b4 chokes on these patches because this patch is missing the 'v4' tag in the subject prefix. Given that the one below needs some changes anyways, I was lazy and applied the series only up until here. (Patch 1 is also not applied, as there was no user yet for _NOLIBC_OPTIMIZER_HIDE_VAR() ).
Could you rebase the series on nolibc-next, add the error handling to strerror_r(), fix the wording nitpicks from Willy and resend the patches? I can also try to fix this up locally, but that would be more work on my side than it would be for you I reckon.
Let me know if this is an issue and I'll try to make it work.
Willy:
I interpreted your mail [0] as Acked-by for the whole series.
[0] https://lore.kernel.org/lkml/[email protected]/
Quoted 20 lines
tools/nolibc: Implementstrerror()in terms ofstrerror_r()
tools/nolibc: Rename the 'errnum' parameter tostrerror()tools/nolibc/printf: Output pad characters in 16 byte chunkstools/nolibc/printf: Simplify__nolibc_printf()
tools/nolibc/printf: Use goto and reduce indentation
tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion charstools/nolibc/printf: Add support for length modifiers tzqL and formats iXtools/nolibc/printf: Handle "%s" with the numeric formatstools/nolibc/printf: Prepend sign to converted numbertools/nolibc/printf: Add support for conversion flags space and plustools/nolibc/printf: Special case 0 and add support for %#xtools/nolibc/printf: Add support for left aligning fieldstools/nolibc/printf: Add support for zero padding and field precisiontools/nolibc/printf: Add support for octal output
selftests/nolibc: Use printf variable field widths and precisionstools/include/nolibc/compiler.h | 3 + tools/include/nolibc/stdio.h | 501 ++++++++++++++----- tools/testing/selftests/nolibc/nolibc-test.c | 157 +++--- 3 files changed, 469 insertions(+), 192 deletions(-)
Quoted 19 lines · On Sat, 7 Mar 2026 19:02:30 +0100 Thomas Weißschuh <[email protected]> wrote:
Hi David,
Quoted 1 line · On 2026-03-02 10:17:52+0000, [email protected] wrote:
From: David Laight <[email protected]>
(...)
I am happy with the patches of this series.
Quoted 9 lines
David Laight (23):
tools/nolibc: Add_NOLIBC_OPTIMIZER_HIDE_VAR()tocompiler.h
tools/nolibc/printf: Move snprintf length check to callback selftests/nolibc: Return correct value when printf test fails selftests/nolibc: checkvsnprintf()output buffer before the length selftests/nolibc: Use length of 'expected' string to checksnprintf()output selftests/nolibc: Check thatsnprintf()doesn't write beyond the buffer end selftests/nolibc: LetEXPECT_VFPRINTF()tests be skipped
Quoted 4 lines
Quoted 1 line
selftests/nolibc: Rename w to written in
expect_vfprintf()Unfortunately b4 chokes on these patches because this patch is missing the 'v4' tag in the subject prefix.
I noticed that after sending them.
I add it by hand (there might be an easier way) but it is easy to miss it when doing a final check/update of the patches. I then sent them without a final-final check.
Quoted 10 lines
Given that the one below needs some
changes anyways, I was lazy and applied the series only up until here. (Patch 1 is also not applied, as there was no user yet for_NOLIBC_OPTIMIZER_HIDE_VAR()).Could you rebase the series on nolibc-next, add the error handling to
strerror_r(), fix the wording nitpicks from Willy and resend the patches? I can also try to fix this up locally, but that would be more work on my side than it would be for you I reckon.
Let me know if this is an issue and I'll try to make it work.
That shouldn't be too hard.
Was a right PITA moving them from linus's tree because --3way doesn't work when the patches come from different git trees.
I'll just create a new branch, use 'git am' to apply each patch then edit and amend.
Then recover all the info after the --- line.
I'm getting used to that sequence :-)
David
Quoted 27 lines
Willy:
I interpreted your mail [0] as Acked-by for the whole series.
[0] https://lore.kernel.org/lkml/[email protected]/
Quoted 20 lines
tools/nolibc: Implementstrerror()in terms ofstrerror_r()
tools/nolibc: Rename the 'errnum' parameter tostrerror()tools/nolibc/printf: Output pad characters in 16 byte chunkstools/nolibc/printf: Simplify__nolibc_printf()
tools/nolibc/printf: Use goto and reduce indentation
tools/nolibc/printf: Use bit-masks to hold requested flag, length and conversion charstools/nolibc/printf: Add support for length modifiers tzqL and formats iXtools/nolibc/printf: Handle "%s" with the numeric formatstools/nolibc/printf: Prepend sign to converted numbertools/nolibc/printf: Add support for conversion flags space and plustools/nolibc/printf: Special case 0 and add support for %#xtools/nolibc/printf: Add support for left aligning fieldstools/nolibc/printf: Add support for zero padding and field precisiontools/nolibc/printf: Add support for octal output
selftests/nolibc: Use printf variable field widths and precisionstools/include/nolibc/compiler.h | 3 + tools/include/nolibc/stdio.h | 501 ++++++++++++++----- tools/testing/selftests/nolibc/nolibc-test.c | 157 +++--- 3 files changed, 469 insertions(+), 192 deletions(-)
Thomas Weißschuh
maintainerQuoted 24 lines · David Laight
Quoted 21 lines · On Sat, 7 Mar 2026 19:02:30 +0100 Thomas Weißschuh <[email protected]> wrote:
Hi David,
Quoted 1 line · On 2026-03-02 10:17:52+0000, [email protected] wrote:
From: David Laight <[email protected]>
(...)
I am happy with the patches of this series.
Quoted 11 lines
David Laight (23):
tools/nolibc: Add_NOLIBC_OPTIMIZER_HIDE_VAR()tocompiler.h
tools/nolibc/printf: Move snprintf length check to callback selftests/nolibc: Return correct value when printf test fails selftests/nolibc: checkvsnprintf()output buffer before the length selftests/nolibc: Use length of 'expected' string to checksnprintf()output selftests/nolibc: Check thatsnprintf()doesn't write beyond the buffer end selftests/nolibc: LetEXPECT_VFPRINTF()tests be skippedselftests/nolibc: Rename w to written in
expect_vfprintf()
Quoted 7 lines
Quoted 2 lines
Unfortunately b4 chokes on these patches because this patch is missing the 'v4' tag in the subject prefix.
I noticed that after sending them.
I add it by hand (there might be an easier way) but it is easy to miss it when doing a final check/update of the patches. I then sent them without a final-final check.
git send-email/format-patch have --reroll-count for that. b4 does manages the reroll count automatically.
Quoted 11 lines
Quoted 10 lines
Given that the one below needs some
changes anyways, I was lazy and applied the series only up until here. (Patch 1 is also not applied, as there was no user yet for_NOLIBC_OPTIMIZER_HIDE_VAR()).Could you rebase the series on nolibc-next, add the error handling to
strerror_r(), fix the wording nitpicks from Willy and resend the patches? I can also try to fix this up locally, but that would be more work on my side than it would be for you I reckon.
Let me know if this is an issue and I'll try to make it work.
Quoted 7 lines
That shouldn't be too hard.
Was a right PITA moving them from linus's tree because --3way doesn't work when the patches come from different git trees.
I'll just create a new branch, use 'git am' to apply each patch then edit and amend.
Then recover all the info after the --- line.
I'm getting used to that sequence :-)
This looks like a usecase for 'git rebase (--onto)'.
In any case: Thanks!
Thomas
Willy Tarreau
maintainerQuoted 33 lines · Thomas Weißschuh
Hi David,
Quoted 1 line · On 2026-03-02 10:17:52+0000, [email protected] wrote:
From: David Laight <[email protected]>
(...)
I am happy with the patches of this series.
Quoted 11 lines
David Laight (23):
tools/nolibc: Add_NOLIBC_OPTIMIZER_HIDE_VAR()tocompiler.h
tools/nolibc/printf: Move snprintf length check to callback selftests/nolibc: Return correct value when printf test fails selftests/nolibc: checkvsnprintf()output buffer before the length selftests/nolibc: Use length of 'expected' string to checksnprintf()output selftests/nolibc: Check thatsnprintf()doesn't write beyond the buffer end selftests/nolibc: LetEXPECT_VFPRINTF()tests be skippedselftests/nolibc: Rename w to written in
expect_vfprintf()Unfortunately b4 chokes on these patches because this patch is missing the 'v4' tag in the subject prefix. Given that the one below needs some changes anyways, I was lazy and applied the series only up until here. (Patch 1 is also not applied, as there was no user yet for
_NOLIBC_OPTIMIZER_HIDE_VAR()).Could you rebase the series on nolibc-next, add the error handling to
strerror_r(), fix the wording nitpicks from Willy and resend the patches? I can also try to fix this up locally, but that would be more work on my side than it would be for you I reckon.
Let me know if this is an issue and I'll try to make it work.
Quoted 4 lines
Willy:
I interpreted your mail [0] as Acked-by for the whole series.
[0] https://lore.kernel.org/lkml/[email protected]/
Yep definitely!
Willy