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:
@@ -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"
|
||||
|
||||
@@ -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
144
common/pager.c
Normal 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
148
include/pager.h
Normal 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
|
||||
Reference in New Issue
Block a user