From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mta1.formilux.org (mta1.formilux.org [51.159.59.229]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4546F336896 for ; Sat, 7 Mar 2026 10:45:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.59.229 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772880336; cv=none; b=UBssaeev6S7u1f8Rufs8MQIiOosGOIDWaPDkFE+wYVacklS74BhcXYJwKKmDQqdPXeo9BbKaZntABhk568SqXGS84tQHF8GI94E5lNvfeApT/pDQ7S0HZ6gMhx+W5xP34WUz5jcC5VPmbZGNSUn358fldC3PyqmQn35VZWtskdY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772880336; c=relaxed/simple; bh=J6o1hdTvninvX3olIcg+fcAa0wZfu8YHao/UrIywH74=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=EVrTl10S/FTqUr9pt2rGmLt9wmHpwZK8YhIc0OkHxnJEnMNq38km+gctGAcEtuDfeTAoNB6fG7CZeSXEhbD1KKu1nyxqPcEl69nBgmR0EnBUrLUCIeJDPOfREtuQENaU2ohwqEuD/KqKdC/03E2FqadEQaDfVw4yfWt8ItPtPGc= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=1wt.eu; spf=pass smtp.mailfrom=1wt.eu; dkim=pass (1024-bit key) header.d=1wt.eu header.i=@1wt.eu header.b=PR7K7Q2J; arc=none smtp.client-ip=51.159.59.229 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=1wt.eu Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=1wt.eu Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=1wt.eu header.i=@1wt.eu header.b="PR7K7Q2J" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1wt.eu; s=mail; t=1772880332; bh=9L4rdahF2U049OdomuWbv+XCKDW5bBhgb/Jr+buf+no=; h=From:Message-ID:From; b=PR7K7Q2J9v6UwLWTDNLcl1hkYeyMrCfLP1VA05RQpMRZuj2RBmw71UZuLyS6Iw+li 21DBObA5rHKAZFoQ/xYRfwpNu0c9PNcD5gFLem7Oc17ttRHXym7HWVulCOomTryrLi tV31NW2mRwZtT9poLsnBEEu+tHKZAJ7N2K8SFjMU= Received: from 1wt.eu (ded1.1wt.eu [163.172.96.212]) by mta1.formilux.org (Postfix) with ESMTP id 74BD9C0A05; Sat, 07 Mar 2026 11:45:32 +0100 (CET) Date: Sat, 7 Mar 2026 11:45:32 +0100 From: Willy Tarreau To: david.laight.linux@gmail.com Cc: Thomas =?iso-8859-1?Q?Wei=DFschuh?= , linux-kernel@vger.kernel.org, Cheng Li Subject: Re: [PATCH v4 next 22/23] tools/nolibc/printf: Add support for octal output Message-ID: References: <20260302101815.3043-1-david.laight.linux@gmail.com> <20260302101815.3043-23-david.laight.linux@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20260302101815.3043-23-david.laight.linux@gmail.com> On Mon, Mar 02, 2026 at 10:18:14AM +0000, david.laight.linux@gmail.com wrote: > From: David Laight > > 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. Looks good and can indeed be useful to display file modes or umasks. > > Add tests for octal output. > > Signed-off-by: David Laight Acked-by: Willy Tarreau willy > --- > > 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(-) > > diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h > index 08011345275a..6036154be791 100644 > --- a/tools/include/nolibc/stdio.h > +++ b/tools/include/nolibc/stdio.h > @@ -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; > diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c > index 7398827fa08b..29ad1207464f 100644 > --- a/tools/testing/selftests/nolibc/nolibc-test.c > +++ b/tools/testing/selftests/nolibc/nolibc-test.c > @@ -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 25 > static 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; > -- > 2.39.5