console: Add the basic pager implementation

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>
This commit is contained in:
Simon Glass
2025-08-21 18:06:07 -06:00
parent c2bc3cbc57
commit 8a18343bdc
4 changed files with 306 additions and 0 deletions

View File

@@ -332,6 +332,18 @@ 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
imply CMD_CONSOLE_EXTRA
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"

View File

@@ -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

144
common/pager.c Normal file
View File

@@ -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 PAGER_PROMPT;
case PAGERST_WAIT_USER:
if (key != ' ')
return PAGER_WAITING;
pag->state = PAGERST_CLEAR_PROMPT;
return PAGER_BLANK;
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;
}

148
include/pager.h Normal file
View File

@@ -0,0 +1,148 @@
/* 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 waiting for user input */
#define PAGER_WAITING ((const char *)1)
/* Prompt shown to user when pager reaches page limit */
#define PAGER_PROMPT "\n: Press SPACE to continue"
/* String used to blank/clear the pager prompt */
#define PAGER_BLANK "\r \r"
/**
* 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
* @state: current state of the pager state-machine
*/
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
* @page_len: 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