
From: Simon Glass <sjg@chromium.org> Add an implementation of the pager. It has quite a simple API: - call pager_post() to send some output, which returns what should actually be sent to the console devices - then call pager_next() repeatedly, outputting what it returns, until it returns NULL There is one special case: pager_next() returns PAGER_WAITING if it is waiting for the user to press a key. In that case, the caller should read a key and then pass it to the next pager_next() call. Internally, there is a simple state machine to cope with hitting the limit (and outputing a prompt), waiting for the user and the clearing the prompt, before continuing in the normal PAGEST_OK state. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/Kconfig | 11 ++++ common/Makefile | 2 + common/pager.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ include/pager.h | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 common/pager.c create mode 100644 include/pager.h diff --git a/common/Kconfig b/common/Kconfig index 0db3b6babca..048530adff3 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -332,6 +332,17 @@ config SYS_DEVICE_NULLDEV operation of the console by setting stdout to "nulldev". Enable this to use a serial console under board control. +config CONSOLE_PAGER + bool "Enable console output paging" + depends on CONSOLE_MUX + default y if SANDBOX + help + Enable pager functionality for console output. When enabled, long + output will be paused after a configurable number of lines, waiting + for user input (SPACE) to continue. The number of lines per page is + controlled by the 'pager' environment variable. If the variable is + not set or is empty, paging is disabled. + endmenu menu "Logging" diff --git a/common/Makefile b/common/Makefile index 048e4a6b3e2..7270af457f5 100644 --- a/common/Makefile +++ b/common/Makefile @@ -26,6 +26,8 @@ obj-$(CONFIG_MII) += miiphyutil.o obj-$(CONFIG_CMD_MII) += miiphyutil.o obj-$(CONFIG_PHYLIB) += miiphyutil.o +obj-$(CONFIG_CONSOLE_PAGER) += pager.o + obj-$(CONFIG_USB_HOST) += usb.o usb_hub.o obj-$(CONFIG_USB_GADGET) += usb.o obj-$(CONFIG_USB_STORAGE) += usb_storage.o diff --git a/common/pager.c b/common/pager.c new file mode 100644 index 00000000000..084c741989f --- /dev/null +++ b/common/pager.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Deals with splitting up text output into separate screenfuls + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_CONSOLE + +#include <errno.h> +#include <malloc.h> +#include <pager.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +const char *pager_post(struct pager *pag, const char *s) +{ + struct membuf old; + int ret, len; + + if (!pag) + return s; + + len = strlen(s); + if (!len) + return NULL; + + old = pag->mb; + ret = membuf_put(&pag->mb, s, len); + if (ret == len) { + /* all is well */ + } else { + /* + * We couldn't store any of the text, so we'll store none of + * it. The pager is now in an non-functional state until it + * 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 + * call was provided via pager_next(). + */ + pag->overflow = s; + pag->mb = old; + } + + return pager_next(pag, 0); +} + +const char *pager_next(struct pager *pag, int key) +{ + char *str, *p, *end; + int ret; + + /* replace the real character we overwrite with nul, if needed */ + if (pag->nulch) { + *pag->nulch = pag->oldch; + pag->nulch = NULL; + } + + /* if we're at the limit, wait */ + switch (pag->state) { + case PAGERST_OK: + break; + case PAGERST_AT_LIMIT: + pag->state = PAGERST_WAIT_USER; + return "\n: Press SPACE to continue"; + case PAGERST_WAIT_USER: + if (key != ' ') + return PAGER_WAITING; + pag->state = PAGERST_CLEAR_PROMPT; + return "\r \r"; + case PAGERST_CLEAR_PROMPT: + pag->state = PAGERST_OK; + break; + } + + ret = membuf_getraw(&pag->mb, pag->buf.size - 1, false, &str); + if (!ret) { + if (pag->overflow) { + const char *oflow = pag->overflow; + + pag->overflow = NULL; + return oflow; + } + return NULL; + } + + /* return lines until we reach the limit */ + for (p = str, end = str + ret; p < end; p++) { + if (*p == '\n' && ++pag->line_count == pag->page_len - 1) { + /* remember to display the pager message next time */ + pag->state = PAGERST_AT_LIMIT; + pag->line_count = 0; + + /* skip the newline, since our prompt has one */ + p++; + break; + } + } + + /* remove the used bytes from the membuf */ + ret = membuf_getraw(&pag->mb, p - str, true, &str); + + /* don't output the newline, since our prompt has one */ + if (pag->state == PAGERST_AT_LIMIT) + p--; + + /* terminate the string */ + pag->nulch = p; + pag->oldch = *pag->nulch; + *pag->nulch = '\0'; + + return str; +} + +void pager_uninit(struct pager *pag) +{ + abuf_uninit(&pag->buf); + free(pag); +} + +int pager_init(struct pager **pagp, int page_len, int buf_size) +{ + struct pager *pag; + + pag = malloc(sizeof(struct pager)); + if (!pag) + return log_msg_ret("pag", -ENOMEM); + memset(pag, '\0', sizeof(struct pager)); + pag->page_len = page_len; + if (!abuf_init_size(&pag->buf, buf_size)) + return log_msg_ret("pah", -ENOMEM); + + /* + * nul-terminate the buffer, which will come in handy if we need to + * return up to the last byte + */ + ((char *)pag->buf.data)[buf_size - 1] = '\0'; + membuf_init(&pag->mb, pag->buf.data, buf_size); + *pagp = pag; + + return 0; +} diff --git a/include/pager.h b/include/pager.h new file mode 100644 index 00000000000..16739d41119 --- /dev/null +++ b/include/pager.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Deals with splitting up text output into separate screenfuls + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#ifndef __PAGER_H +#define __PAGER_H + +#include <stdbool.h> +#include <abuf.h> +#include <membuf.h> +#include <linux/sizes.h> + +#define PAGER_BUF_SIZE SZ_4K + +/* Special return value from pager_next() indicating it's waiting for user input */ +#define PAGER_WAITING ((const char *)1) + +/** + * enum pager_state: Tracks the state of the pager + * + * @PAGERST_OK: Normal output is happening + * @PAGERST_AT_LIMIT: No more output can be provided; the next call to + * pager_next() will return a user prompt + * @PAGERST_WAIT_USER: Waiting for the user to press a key + * @PAGERST_CLEAR_PROMPT: Clearing the prompt ready for more output + */ +enum pager_state { + PAGERST_OK, + PAGERST_AT_LIMIT, + PAGERST_WAIT_USER, + PAGERST_CLEAR_PROMPT, +}; + +/** + * struct pager - pager state + * + * The pager uses a buffer @buf to hold text that it is in the process of + * sending out. This helps deal with the stdio puts() interface, which does not + * 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 + * will be written at once, without any paging, in the next call to + * pager_next(). + * + * The membuf @mb is only used to feed out text in chunks, with a pager message + * (and a keypress wait) inserted between each chunk. + * + * @line_count: Number of lines output since last pause + * @page_len: Sets the height of the page in lines. The maximum lines to display + * before pausing is one less than this. Set from 'pager' env variable + * @buf: Buffer containing text to eventually be returned + * @mb: Circular buffer to manage @buf + * @overflow: pointer to overflow text to send nexts + * @nulch: pointer to where a nul character was written, NULL if none + * @oldch: old character that was at @nulch + */ +struct pager { + int line_count; + int page_len; + struct abuf buf; + struct membuf mb; + const char *overflow; + char *nulch; + int oldch; + enum pager_state state; +}; + +#if CONFIG_IS_ENABLED(CONSOLE_PAGER) + +/** + * pager_post() - Add text to the input buffer for later handling + * + * 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. + * + * After calling pager_post(), if it returns anything other than NULL, you must + * repeatedly call pager_next() until it returns NULL, otherwise text may be + * lost + * + * If @pag is NULL, this does nothing but return @s + * + * @pag: Pager to use, may be NULL + * @s: Text to add + * Return: text which should be sent to output, or NULL if there is no more. + */ +const char *pager_post(struct pager *pag, const char *s); + +/** + * pager_next() - Returns the next screenful of text to show + * + * If this function returns PAGER_WAITING then the caller must check for user + * input and pass in the keypress in the next call to pager_next(). It can + * busy-wait for a keypress, if desired, since pager_next() will only ever + * return PAGER_WAITING until @ch is non-zero. + * + * @pag: Pager to use + * @ch: Key that the user has pressed, or 0 if none + * + * Return: text which should be sent to output, or PAGER_WAITING if waiting for + * the user to press a key, or NULL if there is no more text. + */ +const char *pager_next(struct pager *pag, int ch); + +/** + * pager_uninit() - Uninit the pager + * + * Frees all memory and also @pag + * + * @pag: Pager to uninit + */ +void pager_uninit(struct pager *pag); + +#else +static inline const char *pager_post(struct pager *pag, const char *s) +{ + return s; +} + +static inline const char *pager_next(struct pager *pag, int ch) +{ + return NULL; +} + +#endif + +/** + * pager_init() - Set up a new pager + * + * @pagp: Returns allocaed pager, on success + * @pagelen: Number of lines per page + * @buf_size: Buffer size to use in bytes, this is the maximum amount of output + * that can be paged + * Return: 0 if OK, -ENOMEM if out of memory + */ +int pager_init(struct pager **pagp, int page_len, int buf_size); + +#endif -- 2.43.0