From patchwork Sun Feb 8 21:44:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1810 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770587086; bh=ZK0ps4jLu29huA9dLsQyYt70Kehd61V+oRLPA/Hb4gE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Mhqjlpy6BLg4PI/Uze7SWlEN53oR5n9eD0urDuDrDdxLvf84lX2qwfXsu7J+WKbLe WDCyusFG8Mnzj5ifE1TAZ8tWsBGNo3yaAyvnUUgkvPc0tO+vHkdzhgk2oYFmTJ4QY1 iuxF9Bnc4uL6P1LLt/+ig6QslRKTmnGLOHp1b87M1hihNgUnMT1xXRRN5cB0glQUdD DVSIig7UqZSwXGwqfYzvxnx0h9BaBWt3pLr0xV+p+7jHpsvxHPqJfBwHYQG9KWbb+l UClqSWV4xoCG7KmVknC6h6+F/8D/K3+h8tFfBeiEKiUFhEkQGAQIsVnIOEMmN+u4SH iwngV2yXLJY8Q== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A579E69970 for ; Sun, 8 Feb 2026 14:44:46 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id b-O2D1IOoKUI for ; Sun, 8 Feb 2026 14:44:46 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770587086; bh=ZK0ps4jLu29huA9dLsQyYt70Kehd61V+oRLPA/Hb4gE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Mhqjlpy6BLg4PI/Uze7SWlEN53oR5n9eD0urDuDrDdxLvf84lX2qwfXsu7J+WKbLe WDCyusFG8Mnzj5ifE1TAZ8tWsBGNo3yaAyvnUUgkvPc0tO+vHkdzhgk2oYFmTJ4QY1 iuxF9Bnc4uL6P1LLt/+ig6QslRKTmnGLOHp1b87M1hihNgUnMT1xXRRN5cB0glQUdD DVSIig7UqZSwXGwqfYzvxnx0h9BaBWt3pLr0xV+p+7jHpsvxHPqJfBwHYQG9KWbb+l UClqSWV4xoCG7KmVknC6h6+F/8D/K3+h8tFfBeiEKiUFhEkQGAQIsVnIOEMmN+u4SH iwngV2yXLJY8Q== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 95F67699E6 for ; Sun, 8 Feb 2026 14:44:46 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770587084; bh=2jqsGJv0rVAyPM0XNMBOTXipXGEJyp3t8E+BXCFPsJ8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AQoQpIgL1x570nwkh5rSwk/cCFZNoer4fbYMlqy6v+PbHvwSDip9RslX4JUTqpn/f 0cUzlVirqLsmHqn69IF9ppUvIuqCxd9esaQLeZZeVLmX+sc+3BnAN1wA2Q06P9jr08 NlWjP2koajQMfFn66JBTgZJaDFnK81fbkb5JjRQzQ8cH2GVOqDl40ADvehx+rq1zOo OSugSdWifmNZGrAfqWSwP6Qsga7NzQetb8bAV2rMW37N/26Vh858Dm9T6fJ9wo0thL Un4dOJ3gg3IzXP6iBSpJq2A81o6nkBjgQH2PNRgNKXY6n5oWUfV5UpYqxMs8hbTxly 9CkqM28o9Nlvg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A25F369970; Sun, 8 Feb 2026 14:44:44 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 7VuaHu0IjPaM; Sun, 8 Feb 2026 14:44:44 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1770587080; bh=sJA2hPoh3qKpOiEnBhlKiKAaIoNd/jxxHlcvQyru+Ng=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P6klFDl4ju1t7Di4ilv+p54TuxRasoyTB7fMxXsZ7nWLlLO7/20r4jL6sUv4s7isp SEykWPYPDMUxjsa1RLLSBPpyrPB9pIn0hvJ62NJAkRgZbMoMglN9e/FK/bE/UkTciq D+VmHh1SKpVt7/D1JcrT0QsP2sdzx8V1LtONk69LG79Mmbuee+FcvwrYMbRuDgwSJM mqzBWcOpbfI42ICQ2eUtOxYmMhLmRc/8EivQKgv3cv1J1dBxp4nPWmQUWpAZeo7TTB s/L8DwAoLbis+IWKlU93Ap0Ws/WilruDrocsliAzi8GPEn/SwKnBK4XbFiLsjPTcB5 g3xOhAVwjSuMw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id EF4555FC8F; Sun, 8 Feb 2026 14:44:39 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Sun, 8 Feb 2026 14:44:04 -0700 Message-ID: <20260208214411.3445278-4-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260208214411.3445278-1-sjg@u-boot.org> References: <20260208214411.3445278-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 3O43ZVO565BQQOEKOOFC6A2AJMGXWD4W X-Message-ID-Hash: 3O43ZVO565BQQOEKOOFC6A2AJMGXWD4W X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 6" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 3/4] pager: Add length parameter to pager_post() List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Since commit 4e2b66acdd4b ("console: Add the putsn() API for length-based string output") all puts() output goes through putsn(). When the pager is active, console_putsn_pager() cannot pass length-delimited strings to pager_post() because it only accepts nul-terminated strings. The workaround is character-by-character output, which causes a severe performance regression visible in any command that produces significant output (e.g. "dm drivers", "dm compat") when the pager is enabled. Rename pager_post() to pager_postn() with a new int len parameter, so it can accept length-delimited strings directly. Add a pager_post() inline helper for nul-terminated callers. Update console_putsn_pager() to call through the pager path directly, removing the s[len] == '\0' hack and the character-by-character fallback. Rename the internal console_puts() to console_putsn() to reflect its new length parameter. Add a test exercising a non-nul-terminated substring. Fixes: 4e2b66acdd4b ("console: Add the putsn() API for length-based string output") Co-developed-by: Claude Opus 4.6 Signed-off-by: Simon Glass --- common/console.c | 20 ++++++----------- common/pager.c | 13 +++++++---- include/pager.h | 55 ++++++++++++++++++++++++++++++++++++++++----- test/common/pager.c | 19 ++++++++++++++++ 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/common/console.c b/common/console.c index 96e8977ce06..238d464980f 100644 --- a/common/console.c +++ b/common/console.c @@ -361,11 +361,11 @@ int err_printf(bool serial_only, const char *fmt, ...) return ret; } -static void console_puts(int file, bool use_pager, const char *s) +static void console_putsn(int file, bool use_pager, const char *s, int len) { int key = 0; - for (s = pager_post(gd_pager(), use_pager, s); s; + for (s = pager_postn(gd_pager(), use_pager, s, len); s; s = pager_next(gd_pager(), use_pager, key)) { struct stdio_dev *dev; int i; @@ -384,13 +384,8 @@ static void console_puts(int file, bool use_pager, const char *s) static void console_putsn_pager(int file, const char *s, int len) { - if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) { - /* - * Pager only works with nul-terminated strings, so output - * character by character for length-based strings - */ - while (len--) - console_putc_pager(file, *s++); + if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && pager_active(gd_pager())) { + console_putsn(file, true, s, len); } else { struct stdio_dev *dev; int i; @@ -409,11 +404,10 @@ static void console_putsn_pager(int file, const char *s, int len) static void console_puts_pager(int file, const char *s) { - if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) { - console_puts(file, true, s); - } else { + if (IS_ENABLED(CONFIG_CONSOLE_PAGER) && gd_pager()) + console_putsn(file, true, s, strlen(s)); + else console_putsn_pager(file, s, strlen(s)); - } } #ifdef CONFIG_CONSOLE_FLUSH_SUPPORT diff --git a/common/pager.c b/common/pager.c index 60b1adc8571..bb8ea0776bf 100644 --- a/common/pager.c +++ b/common/pager.c @@ -15,10 +15,11 @@ DECLARE_GLOBAL_DATA_PTR; -const char *pager_post(struct pager *pag, bool use_pager, const char *s) +const char *pager_postn(struct pager *pag, bool use_pager, const char *s, + int len) { struct membuf old; - int ret, len; + int ret; if (!pag || !use_pager || pag->test_bypass || pag->state == PAGERST_BYPASS) @@ -27,7 +28,6 @@ const char *pager_post(struct pager *pag, bool use_pager, const char *s) if (pag->state == PAGERST_QUIT_SUPPRESS) return NULL; - len = strlen(s); if (!len) return NULL; @@ -42,8 +42,13 @@ const char *pager_post(struct pager *pag, bool use_pager, const char *s) * can eject the overflow text. * * The buffer is presumably empty, since callers are not allowed - * to call pager_post() unless all the output from the previous + * to call pager_postn() unless all the output from the previous * call was provided via pager_next(). + * + * Note: the overflow path returns @s directly via + * pager_next(), so @s must be nul-terminated. In practice + * this only triggers when len > buf_size, and typical + * console strings are well within the 4K default buffer. */ pag->overflow = s; pag->mb = old; diff --git a/include/pager.h b/include/pager.h index 3f13f82885d..7f475787cd7 100644 --- a/include/pager.h +++ b/include/pager.h @@ -12,6 +12,7 @@ #include #include #include +#include #define PAGER_BUF_SIZE SZ_4K @@ -52,7 +53,7 @@ enum pager_state { * permit passing a string length, only a string, which means that strings must * be nul-terminated. The termination is handled automatically by the pager. * - * If the text passed to pager_post() is too large for @buf then all the next + * If the text passed to pager_postn() is too large for @buf then all the next * will be written at once, without any paging, in the next call to * pager_next(). * @@ -85,12 +86,12 @@ struct pager { #if CONFIG_IS_ENABLED(CONSOLE_PAGER) /** - * pager_post() - Add text to the input buffer for later handling + * pager_postn() - Add text to the input buffer for later handling * * If @use_pager the text is added to the pager buffer and fed out a screenful - * at a time. This function calls pager_post() after storing the text. + * at a time. This function calls pager_postn() after storing the text. * - * After calling pager_post(), if it returns anything other than NULL, you must + * After calling pager_postn(), if it returns anything other than NULL, you must * repeatedly call pager_next() until it returns NULL, otherwise text may be * lost * @@ -99,10 +100,28 @@ struct pager { * @pag: Pager to use, may be NULL * @use_pager: Whether or not to use the pager functionality * @s: Text to add + * @len: Length of @s in bytes * Return: text which should be sent to output, or NULL if there is no more. * If !@use_pager this just returns @s and does not affect the pager state */ -const char *pager_post(struct pager *pag, bool use_pager, const char *s); +const char *pager_postn(struct pager *pag, bool use_pager, const char *s, + int len); + +/** + * pager_post() - Add a nul-terminated string to the pager input buffer + * + * Convenience wrapper around pager_postn() for nul-terminated strings. + * + * @pag: Pager to use, may be NULL + * @use_pager: Whether or not to use the pager functionality + * @s: Nul-terminated text to add + * Return: text which should be sent to output, or NULL if there is no more + */ +static inline const char *pager_post(struct pager *pag, bool use_pager, + const char *s) +{ + return pager_postn(pag, use_pager, s, strlen(s)); +} /** * pager_next() - Returns the next screenful of text to show @@ -127,6 +146,21 @@ const char *pager_post(struct pager *pag, bool use_pager, const char *s); */ const char *pager_next(struct pager *pag, bool use_pager, int ch); +/** + * pager_active() - check if pager needs to process output + * + * Returns true only when the pager is genuinely active and needs to + * process output (not bypassed or in test bypass mode). + * + * @pag: Pager to check, may be NULL + * Return: true if the pager is active + */ +static inline bool pager_active(struct pager *pag) +{ + return pag && !pag->test_bypass && + pag->state != PAGERST_BYPASS; +} + /** * pager_set_bypass() - put the pager into bypass mode * @@ -181,6 +215,12 @@ void pager_clear_quit(struct pager *pag); void pager_uninit(struct pager *pag); #else +static inline const char *pager_postn(struct pager *pag, bool use_pager, + const char *s, int len) +{ + return s; +} + static inline const char *pager_post(struct pager *pag, bool use_pager, const char *s) { @@ -192,6 +232,11 @@ static inline const char *pager_next(struct pager *pag, bool use_pager, int ch) return NULL; } +static inline bool pager_active(struct pager *pag) +{ + return false; +} + static inline bool pager_set_bypass(struct pager *pag, bool bypass) { return true; diff --git a/test/common/pager.c b/test/common/pager.c index ccd5230f3a4..534096a1c4f 100644 --- a/test/common/pager.c +++ b/test/common/pager.c @@ -665,3 +665,22 @@ static int pager_test_quit_keypress(struct unit_test_state *uts) return 0; } COMMON_TEST(pager_test_quit_keypress, 0); + +/* Test pager with non-nul-terminated string using explicit length */ +static int pager_test_non_nul_terminated(struct unit_test_state *uts) +{ + struct pager *pag; + /* "Hello" followed by other data - not nul-terminated at offset 5 */ + static const char data[] = "HelloWorld"; + + ut_assertok(pager_init(&pag, 20, 1024)); + + /* Post only the first 5 bytes */ + ut_asserteq_str("Hello", pager_postn(pag, true, data, 5)); + ut_assertnull(pager_next(pag, true, 0)); + + pager_uninit(pag); + + return 0; +} +COMMON_TEST(pager_test_non_nul_terminated, 0);