// SPDX-License-Identifier: GPL-2.0+ /* * Deals with splitting up text output into separate screenfuls * * Copyright 2025 Simon Glass */ #define LOG_CATEGORY LOGC_CONSOLE #include #include #include #include #include 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; }