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
@@ -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"
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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