[PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()

david.laight.linux@gmail.com posted 23 patches 1 month, 1 week ago
There is a newer version of this series
[PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Posted by david.laight.linux@gmail.com 1 month, 1 week ago
From: David Laight <david.laight.linux@gmail.com>

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.

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

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(-)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index a4df72d9a2d3..03fcd0229f90 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -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 */
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 9ebebe4ff253..638f18fc5123 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -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 */
-- 
2.39.5
Re: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Posted by Willy Tarreau 1 month ago
Hi David,

On Mon, Mar 02, 2026 at 10:18:01AM +0000, david.laight.linux@gmail.com wrote:
> From: David Laight <david.laight.linux@gmail.com>
> 
> 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.

> 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.

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:

> +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()).

> +	__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()
Posted by David Laight 1 month ago
On Sat, 7 Mar 2026 11:18:41 +0100
Willy Tarreau <w@1wt.eu> wrote:

> Hi David,
> 
> On Mon, Mar 02, 2026 at 10:18:01AM +0000, david.laight.linux@gmail.com wrote:
> > From: David Laight <david.laight.linux@gmail.com>
> > 
> > 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.
> 
> > 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.  
> 
> 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.

> 
> > +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+).

> 	}
> 
> (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.

> 
> > +	__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()
Posted by Willy Tarreau 1 month ago
On Sat, Mar 07, 2026 at 11:31:05AM +0000, David Laight wrote:
> On Sat, 7 Mar 2026 11:18:41 +0100
> Willy Tarreau <w@1wt.eu> wrote:
> 
> > Hi David,
> > 
> > On Mon, Mar 02, 2026 at 10:18:01AM +0000, david.laight.linux@gmail.com wrote:
> > > From: David Laight <david.laight.linux@gmail.com>
> > > 
> > > 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.
> > 
> > > 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.  
> > 
> > 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.

> > > +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!

> > (and we can safely ignore it for strerror()).
> 
> ISTR strerror_r() tends to get inlined into strerror() so it
> would be optimised away.

That's what I suspected as well.

> 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()
Posted by David Laight 1 month ago
On Sat, 7 Mar 2026 12:37:35 +0100
Willy Tarreau <w@1wt.eu> wrote:

...
> > > > +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!

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
Re: [PATCH v4 next 09/23] tools/nolibc: Implement strerror() in terms of strerror_r()
Posted by Willy Tarreau 1 month ago
On Sat, Mar 07, 2026 at 04:55:06PM +0000, David Laight wrote:
> On Sat, 7 Mar 2026 12:37:35 +0100
> Willy Tarreau <w@1wt.eu> wrote:
> 
> ...
> > > > > +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!
> 
> 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