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 77DF13A1A26 for ; Mon, 2 Mar 2026 10:18:56 +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=1772446741; cv=none; b=n+gQm6pl1PVXQDp52L6T3b6IVrtlykPc4EHjd3iT1sBbdFVNHBU4qI9mkIg896NLL8f4KSDGdzCzvXtRlhwi3OxVu+aYlOucUG8VMPy4OszKR69YfTKDrocDf0FrWzUzLtRe6/y+WYeApSsMn/3ZCv8XQF3th5I01VrBrveJPmU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772446741; c=relaxed/simple; bh=cT3l67nJJSwZ5/IJsp1A2MYVrZXWclT+13bVD9jsh40=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=QzjDClFgqQa9AuHCFuGd9xZMd6oO0b9lcEZxvS4/tPGbDDiVk0phhjt4mLbH5+V3DZ+Rf+63zwnhznaq3X+spzwL1KbFHBJyIt+C3AKkj/X6cBAIlOvpwH3vw9h1nBrC2xHbEcFZmX+D5BnBh+JbaygyQHo7WG9Ha73inltkQ4g= 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=sslXVWCk; 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="sslXVWCk" Received: from mailtransmit02.runbox ([10.9.9.162] 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 1vx0MY-003BdB-AH; Mon, 02 Mar 2026 11:18:54 +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=n4Ywia81sK3cCjsfCYb9dvWvREMVAccpZhYd33h28W0=; b=sslXVWCkbi/IkqvSKM1V7dV4gA l60MbcGpHBupJaf/DygEbQbGBBqlE9SRL0jVDxCRC7JjbrjHGW+oZ6mqVCpHGgFKi3ukRa9Fu5+qV aO2nNfu6OZUu4q9dlc2diC9bbBggtiuPo5yx5TSjcsTtVa6QtFpsKvsKoDoEr7YbYYfr4a5BvRL8W dd7hkMY1w7gCxPS+HjEl+BDX4yJFGqav2jDlMfmdNbV816D+ppUnTFDokugxcFxkfOzNg+C84idop qS6tr2WDbNtOjlGsoyNIesGLUoYn1IIuoNs6tTbRjupAQ4T0fweAbxdwua6h6bhtJzsFtYtrO9wQo GB+3maKQ==; Received: from [10.9.9.72] (helo=submission01.runbox) by mailtransmit02.runbox with esmtp (Exim 4.86_2) (envelope-from ) id 1vx0MY-000560-0n; Mon, 02 Mar 2026 11:18:54 +0100 Received: by submission01.runbox with esmtpsa [Authenticated ID (1493616)] (TLS1.2:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.93) id 1vx0M8-006y7o-TT; Mon, 02 Mar 2026 11:18:28 +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 v4 next 02/23] tools/nolibc/printf: Move snprintf length check to callback Date: Mon, 2 Mar 2026 10:17:54 +0000 Message-Id: <20260302101815.3043-3-david.laight.linux@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20260302101815.3043-1-david.laight.linux@gmail.com> References: <20260302101815.3043-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 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()") Signed-off-by: David Laight --- 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(-) diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index 77d7669cdb80..a4df72d9a2d3 100644 --- a/tools/include/nolibc/stdio.h +++ b/tools/include/nolibc/stdio.h @@ -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))) -- 2.39.5