From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailtransmit05.runbox.com (mailtransmit05.runbox.com [185.226.149.38]) (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 B868D35D604 for ; Mon, 23 Feb 2026 10:57:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.226.149.38 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771844244; cv=none; b=fHxKLY5oymTPL/eS+WAtmkWIf027YWrmOTweAkGrHEeCDNsDtO7a6zdcVifqn5uE6rH+zgVoOuHFsyi25ufgT8s6nggL3jY4xN0Sim6Cn4/hT/XwlA+xFoJqIUo74brlRK2rp9gTFdeUwOLe2+sTNq8J2bEBN/8QbnHQ4tnJ+7c= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771844244; c=relaxed/simple; bh=O/Ug/S8pvV7oBR2H35/0kPAl55qIrfjUCUyJuM1W6pw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=uhJiPSvY6uVpaic5oDGs3EoHVuEC94T+mBw5H+pR1zYp+14AEu1nFGbvg2CecbPX8SlrcOB64W2Ue3LibTU05NVm4LDxsJhrwvVByCFWIGXaPOZekXy9+wtsPxIUarBk31qweVufMpbtnbcBwLmZ4YTkLCBHPI5Te7yUbT5/0Co= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=runbox.com; dkim=pass (2048-bit key) header.d=runbox.com header.i=@runbox.com header.b=RNuVoWkz; arc=none smtp.client-ip=185.226.149.38 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=runbox.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=runbox.com header.i=@runbox.com header.b="RNuVoWkz" Received: from mailtransmit03.runbox ([10.9.9.163] helo=aibo.runbox.com) by mailtransmit05.runbox.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.93) (envelope-from ) id 1vuT18-00GoCZ-N1; Mon, 23 Feb 2026 11:18:18 +0100 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=runbox.com; s=selector2; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To :Message-Id:Date:Subject:Cc:To:From; bh=05o4Bvei+MQw/FOg09GlU5+BLLldcUJ5gWL25utryfM=; b=RNuVoWkzEd++8rfB6guNY3Osn1 XTLU08t+qoIcLykOs4vLZBCf/1WDcTc3tkRG0c2ZWAk0w6c+Vql4HqJMwibfFP4e17PHmBdnwq349 8YXqgz1U4tZ3PUqnSC9OWDszavAZ2sWLN4l50WyuCmJoJlzCChrE/+VUbYeQ80wqLvbP65z9ts5hS aTy5AjmMZXRnS1TT/tfEmHkfkwNcqPf2z3TQ0cMBmCT45dsCPv8/53US2PU50mfDK4j+26fUmW1uL bf0s3HT8zlyjRxVi862G0EB03nxvw6yCBq6W1QI+dY+E4BGzfj25KreUe+LsS/Eeja8svkrW/rtNr +0KDgFEg==; Received: from [10.9.9.73] (helo=submission02.runbox) by mailtransmit03.runbox with esmtp (Exim 4.86_2) (envelope-from ) id 1vuT18-0001Qw-8k; Mon, 23 Feb 2026 11:18:18 +0100 Received: by submission02.runbox with esmtpsa [Authenticated ID (1493616)] (TLS1.2:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.93) id 1vuT16-006AjD-AT; Mon, 23 Feb 2026 11:18:16 +0100 From: david.laight.linux@gmail.com To: Willy Tarreau , =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= , linux-kernel@vger.kernel.org, Cheng Li Cc: David Laight Subject: [PATCH v3 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision Date: Mon, 23 Feb 2026 10:17:33 +0000 Message-Id: <20260223101735.2922-16-david.laight.linux@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20260223101735.2922-1-david.laight.linux@gmail.com> References: <20260223101735.2922-1-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-Transfer-Encoding: 8bit From: David Laight 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 Acked-by: Willy Tarreau Signed-off-by: David Laight --- 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 | 18 ++++- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 482e5b143c86..cc2870a6a7cb 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -292,12 +292,10 @@ int fseek(FILE *stream, long offset, int whence) /* printf(). Supports most of the normal integer and string formats. - * - %[#-+ 0][width][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%} + * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,x,X,p,s,m,%} * - %% generates a single % * - %m outputs strerror(errno). * - %X outputs a..f the same as %x. - * - The modifiers [-0] are currently ignored. - * - No support for precision or variable widths. * - No support for floating point or wide characters. * - Invalid formats are copied to the output buffer. * @@ -343,9 +341,9 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list char ch; unsigned long long v; long long signed_v; - int written, width, len; + int written, width, precision, len; unsigned int flags, ch_flag; - char outbuf[2 + 22 + 1]; + char outbuf[2 + 31 + 22 + 1]; char *out; const char *outstr; unsigned int sign_prefix; @@ -378,12 +376,24 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list flags |= ch_flag; } - /* width */ - while (ch >= '0' && ch <= '9') { - width *= 10; - width += ch - '0'; - - ch = *fmt++; + /* Width and precision */ + for (;; ch = *fmt++) { + if (ch == '*') { + precision = va_arg(args, unsigned int); + ch = *fmt++; + } else { + for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++) + precision = precision * 10 + (ch - '0'); + } + if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) + break; + width = precision; + if (ch != '.') { + /* Default precision for strings */ + precision = INT_MAX; + break; + } + flags |= _NOLIBC_PF_FLAG('.'); } /* Length modifier. @@ -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. */ @@ -529,8 +576,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; diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index cc59c0116855..e0cfe8d12ed1 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1838,10 +1838,12 @@ 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", "%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(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; @@ -1850,11 +1852,25 @@ 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, "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", "%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; + 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(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(errno); EXPECT_VFPRINTF(is_nolibc, "22:errno=22 ", "%d:%-12m", errno=22); break; CASE_TEST(errno-neg); EXPECT_VFPRINTF(is_nolibc, "-22: errno=-22", "%d:%12m", errno=-22); break; CASE_TEST(scanf); EXPECT_ZR(1, test_scanf()); break; -- 2.39.5