Files
u-boot/common/pager.c
Simon Glass dd80fd963c console: Add a way to quit pager with no further output
Add support for pressing 'q' to throw away any further output until the
prompt is reached.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-09-22 11:23:04 -06:00

254 lines
5.0 KiB
C

// 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 <env.h>
#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, bool use_pager, const char *s)
{
struct membuf old;
int ret, len;
if (!pag || !use_pager || pag->test_bypass ||
pag->state == PAGERST_BYPASS)
return s;
if (pag->state == PAGERST_QUIT_SUPPRESS)
return NULL;
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, true, 0);
}
const char *pager_next(struct pager *pag, bool use_pager, int key)
{
char *str, *p, *end;
int ret;
if (!use_pager || (pag && pag->test_bypass))
return NULL;
/* 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 == 'Q') {
pag->state = PAGERST_BYPASS;
return PAGER_BLANK;
}
if (key == 'q') {
pag->state = PAGERST_QUIT_SUPPRESS;
return "\r \r";
}
if (key != ' ')
return PAGER_WAITING;
pag->state = PAGERST_CLEAR_PROMPT;
return PAGER_BLANK;
case PAGERST_CLEAR_PROMPT:
pag->state = PAGERST_OK;
break;
case PAGERST_BYPASS:
break;
case PAGERST_QUIT_SUPPRESS:
membuf_purge(&pag->mb);
return NULL;
}
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;
}
end = str + ret;
if (pag->state != PAGERST_BYPASS) {
/* return lines until we reach the limit */
for (p = str; 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;
}
}
} else {
p = end;
}
/* 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;
}
bool pager_set_bypass(struct pager *pag, bool bypass)
{
bool was_bypassed = false;
if (!pag)
return false;
was_bypassed = pag->state == PAGERST_BYPASS;
if (bypass)
pag->state = PAGERST_BYPASS;
else
pag->state = PAGERST_OK;
return was_bypassed;
}
bool pager_set_test_bypass(struct pager *pag, bool bypass)
{
bool was_bypassed = false;
if (!pag)
return false;
was_bypassed = pag->test_bypass;
pag->test_bypass = bypass;
return was_bypassed;
}
void pager_set_page_len(struct pager *pag, int page_len)
{
if (page_len < 2)
return;
pag->page_len = page_len;
pag->line_count = 0;
}
void pager_reset(struct pager *pag)
{
pag->line_count = 0;
}
void pager_clear_quit(struct pager *pag)
{
if (!pag)
return;
if (pag->state == PAGERST_QUIT_SUPPRESS)
pag->state = PAGERST_OK;
}
static int on_pager(const char *name, const char *value, enum env_op op,
int flags)
{
struct pager *pag = gd_pager();
int new_page_len;
if (!IS_ENABLED(CONFIG_CONSOLE_PAGER) || !pag)
return 0;
switch (op) {
case env_op_create:
case env_op_overwrite:
if (value) {
new_page_len = simple_strtoul(value, NULL, 16);
pager_set_page_len(pag, new_page_len);
pager_set_bypass(pag, false);
}
break;
case env_op_delete:
/* Reset to default when deleted */
pager_set_page_len(pag, CONFIG_CONSOLE_PAGER_LINES);
break;
}
return 0;
}
U_BOOT_ENV_CALLBACK(pager, on_pager);
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;
}