From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AA4AE374180 for ; Sun, 8 Mar 2026 11:38:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772969882; cv=none; b=MFW8AOljQHLP6OKfecrDoYAg0O6zAcGESliXYVwZtJvPFzMVd5z5Bh5HPu1ZfWRWDzS2Py/ExyzM6hYQH0sFIEmMqZ5s6vtm/lVUU+yP7bJ6MlkydNBJCSnb6kp2jsKhOKiJRDV2N8BsPdvdvDH0P5SvJK9LNYuIkbmU38rviMM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772969882; c=relaxed/simple; bh=t2ve8PKaytGeQ2t/TCPMTgcz/SwW84qmRjX+uJuXG+0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Cv/LEMpA5icpwGuYloGLf77Eso/q7xhOte5cpbYpBRRqkldRxnNc99zac08jJZuJJ9K1XRlUukk/FnbuUrhNGETGfnfkKtkMjiFrfgK9Lw3Md6GYdULtxDDOr6vZBHAIU7j1Um4NouDbsXr0Pi9f77nhLpok3SNRBt/cdDz0jY4= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=h5+nlT5i; arc=none smtp.client-ip=209.85.128.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="h5+nlT5i" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-4806f3fc50bso116638775e9.0 for ; Sun, 08 Mar 2026 04:38:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772969879; x=1773574679; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=RMKr+si20BbKDBd5m1MsUVaLFCe2Mb71pelEywASkwE=; b=h5+nlT5idIcI0SYFyelLWBBwl0BpJdKUbjA/IcDJpSL1HmetXn1GJwwTP9IVQDzPX8 7ZwFUqwIkIUgnm5lrlxGCXd5qN1rX+FiZ5061xCnkp1YqvbPQ5RulB5USgMEvycO5bQF FjzSEzTH+l0TRZWjinnQgvHx5Uc8VYfNHmiknfJgE4drN3I/yMmQgdBmhsRNNghVQL/Z GKJkbn8sp0K7H7eCAzvB2eMafDPVc5cmWdPuXWe+OXc0tD8AVL93t2GHl3vi36MEh3vH c4fT79LaW9WgJpl8i2vo/AqqREATLhDagUF5vSdqoBr1N1CD1x+gLnweZi2YeDBJ68Tu UKQw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772969879; x=1773574679; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=RMKr+si20BbKDBd5m1MsUVaLFCe2Mb71pelEywASkwE=; b=xS58msB5dexmFi5drQfbASIh6y2sqEBmrs3F9Jk81glds73CseAnuR2uUV3RZ2+ytQ EIgLP1+hx+78MMlgFil43Td4r9+WIFfVvhJSLeQ2TbOrhA94ShejePebhhGx6jE42Mkp rIx0qe+YMKvZYsB3sseBRdF4iZaVlHIX22AE3PjaFX4oG5pFUhDAq//LOrpEKzuqWUAb kjREO/YEBfltMf0c0mikpOSk8dmNCK22MkCv5oVUt5moR6gxYHNTc9ZnHb5TVB7nBKn8 Rglue34hVJCof1GHsYovgs5enCs7iGw6oNqzINPLyB++HFU0GuVbe+8UINNf1foCzCaJ Jo+Q== X-Forwarded-Encrypted: i=1; AJvYcCWw8gChRkWToRzqrsCySV7zZh+z/EaeWK+hmuKlz9Pjmcdq3LE8zIqRs9gT50h1dqeJnOqPg9FNo1TpJa8=@vger.kernel.org X-Gm-Message-State: AOJu0Yx+XQHiddXN29NMWnFzKEI+fF7bXuxQJtDVll9VvPEKEKEYASDH ksbBSjL71qlQfv863EoOY2Tordceeg/rCfnhhiQQyCS/nVayi8gSwT9q X-Gm-Gg: ATEYQzyu2tMNStEcqmTzDhX2GAKyMYYAKP9Y8bazygVpQwlygKWw1//BL3k1B+5/sre X2focMvUW+gO55Q2KW5MY9/ysFw6as1t+rLnFDHpPcIyJGcv/akciqD83TAwk2syzVuKNZaKd+x IZOg8yKvbSAWkvGURHdrdQ/v39sh2OjLAokifNG6a+TigpGjVrY8+KNzz99awtIOzXdqcQ2dqSJ 7+iwNmQyAZDMgi6DG8ZyoWywX9Bx2kFOfE3zpNTGDfiTdNWmYCkw7s0NlntVhoY845g9GuMayAP E1k50qcPRXWi2z/6jgWg/hoLMmd41KHNgxvNGBjjIBRUC4aRkFUZzQRR9jv4J64mk6UKt/377Du qj1dG0KM1H3QLGDbtodXjEYv7HKRQQdhfIbOpQGc/IhlntfzGY6YHjW+T/bQCLkRUBkgQFZi3Y3 riR92U0OTerOa1ELMw0hAxSyB/vrg3ghvb9nvespekH7I/EA5uE25IIphSSCVWkBg4SpZxuHAPU F4Juu4ZhmppXUWnG0qe+kA= X-Received: by 2002:a05:600c:500d:b0:47e:e48b:506d with SMTP id 5b1f17b1804b1-4852695b9f7mr133237045e9.16.1772969878901; Sun, 08 Mar 2026 04:37:58 -0700 (PDT) Received: from snowdrop.snailnet.com (82-69-66-36.dsl.in-addr.zen.co.uk. [82.69.66.36]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-485237dd017sm63621855e9.2.2026.03.08.04.37.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 08 Mar 2026 04:37:58 -0700 (PDT) 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 v5 next 15/17] tools/nolibc/printf: Add support for zero padding and field precision Date: Sun, 8 Mar 2026 11:37:40 +0000 Message-Id: <20260308113742.12649-16-david.laight.linux@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20260308113742.12649-1-david.laight.linux@gmail.com> References: <20260308113742.12649-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 --- Unchanged for v4 and v5. 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(-) diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index fccc8a2d9b8a..3b2a5613f603 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; } @@ -469,11 +482,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } /* The value is converted offset into the buffer so that - * the sign/prefix can be added in front. + * 31 zero pad characters and the sign/prefix can be added in front. * The longest digit string is 22 + 1 for octal conversions, the * space is reserved even though octal isn't currently supported. */ - out = outbuf + 2; + out = outbuf + 2 + 31; if (v == 0) { /* There are special rules for zero. */ @@ -483,6 +496,11 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list len = 5; goto do_output; } + if (!precision) { + /* Explicit %nn.0d, no digits output */ + len = 0; + goto prepend_sign; + } /* All other formats (including "%#x") just output "0". */ out[0] = '0'; len = 1; @@ -501,6 +519,35 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list } } + /* Add zero padding */ + if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '0', '.')) { + if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '.')) { + if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-')) + /* Left justify overrides zero pad */ + goto prepend_sign; + /* eg "%05d", Zero pad to field width less sign. + * Note that precision can end up negative so all + * the variables have to be 'signed int'. + */ + precision = width; + if (sign_prefix) { + precision--; + if (sign_prefix >= 256) + precision--; + } + } + if (precision > 31) + /* Don't run off the start of outbuf[], arbitrary limit + * longer than the longest number field. */ + precision = 31; + for (; len < precision; len++) { + /* Stop gcc generating horrid code and memset(). */ + _NOLIBC_OPTIMIZER_HIDE_VAR(len); + *--out = '0'; + } + } + +prepend_sign: /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */ for (; sign_prefix; sign_prefix >>= 8) { /* Force gcc to increment len inside the loop. */ @@ -535,8 +582,8 @@ int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list goto do_output; do_strlen_output: - /* Open coded strlen() (slightly smaller). */ - for (len = 0;; len++) + /* Open coded strnlen() (slightly smaller). */ + for (len = 0; len < precision; len++) if (!outstr[len]) break; diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 89ded1dd7318..7398827fa08b 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -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; -- 2.39.5