From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailtransmit04.runbox.com (mailtransmit04.runbox.com [185.226.149.37]) (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 8411834E74D for ; Mon, 23 Feb 2026 10:35:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.226.149.37 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771842949; cv=none; b=M5z47yU2rzHtMI5h5AW/YDzsJCAxi8aDuiYK90gUk5XWX6MqRghcMp035HgZzR57LQjTZuZcVHs/hVattJz7cH9aMs0C08uNXmmjPzBveIHFHHrmu4Hypl0xmHDvkz64OZGLhEo8BpiOBsg5fHx/vnesKYgoEFg1rdsokEYz4zc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771842949; c=relaxed/simple; bh=qrqPGlM8tyPpYDfD0bJpBrAl4QDU3AeNK95Bkvri0aI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ZJ3CR2Qz0CGVBPFh8XpMOpzLIrPCtqVJfiRmw5vVcZpFt5U/G3R7L0xq6QqGfK/RnEa0MzHugihDUpAg6gGUJllzAMkmacVIScgcj/IWvS/LW16i7ne8Wej9TbxmgvY4LDJeaZd/UqKLdT/gKXEam+F+7lAGlyLripI2+yOYxWA= 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=iIoslzm7; arc=none smtp.client-ip=185.226.149.37 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="iIoslzm7" Received: from mailtransmit03.runbox ([10.9.9.163] helo=aibo.runbox.com) by mailtransmit04.runbox.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.93) (envelope-from ) id 1vuT1A-00GgoX-Jv; Mon, 23 Feb 2026 11:18:20 +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=9qWJcMC6R8wI95vbEiZTwzfoOGH5nPzcpuWTfNiHHs4=; b=iIoslzm7ms6yyRv2/eTVCKHY5S x0ke9FH5dhbl+Kl3IaEjpJtNF8u5/5lrvqo8jKzaXfhLiwyeEpip75KP1O7l6Jt+RegT1QDKUkeQJ 1hJsWVeohkaxbpLnib+8l3oT2qrOrmcM5wK7Vqk4YeM2ormU5j6/kbGVXcD0AZsR4GmOC5lttym7C M2nYkPSY9SR3QtiHKCRyOXrwD4Lv++nZAn0hQe+J/g/5k30xNBa8ZMIB8myRcCxjLl3VCH7mbWKh1 3rDPWsWdHRHqlRtcxfyOI6LI9e3fofbAEIhT3F2t0ZKOICC5wUGca92v6e4etigSiOYUyzlVpmUk0 1Z7apBvA==; Received: from [10.9.9.73] (helo=submission02.runbox) by mailtransmit03.runbox with esmtp (Exim 4.86_2) (envelope-from ) id 1vuT1A-0001Rs-5j; Mon, 23 Feb 2026 11:18:20 +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 1vuT13-006AjD-1a; Mon, 23 Feb 2026 11:18:13 +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 07/17] tools/nolibc/printf: Move snprintf length check to callback Date: Mon, 23 Feb 2026 10:17:25 +0000 Message-Id: <20260223101735.2922-8-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 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 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 | 92 +++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h index a271059cf67a..5c8f233336b9 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; @@ -402,17 +411,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: @@ -425,18 +430,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))) @@ -494,26 +506,52 @@ 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'; + 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