Files
u-boot/cmd/bootflow.c
Simon Glass 0a2b56c8ce boot: Show an indication for encrypted bootflows
We don't support storing the OS on an encrypted partition, but in some
cases the root partition may be encrypted. Add an indication of this
when listing the bootflows.

Signed-off-by: Simon Glass <simon.glass@canonical.com>
2025-11-15 09:50:22 -07:00

639 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* 'bootflow' command
*
* Copyright 2021 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <bootdev.h>
#include <bootflow.h>
#include <bootm.h>
#include <bootstd.h>
#include <command.h>
#include <console.h>
#include <dm.h>
#include <efi_device_path.h>
#include <expo.h>
#include <log.h>
#include <mapmem.h>
static void show_header(void)
{
printf("Seq Method State Uclass Part E Name Filename\n");
printf("--- ----------- ------ -------- ---- - ------------------------ ----------------\n");
}
static void show_footer(int count, int num_valid)
{
printf("--- ----------- ------ -------- ---- - ------------------------ ----------------\n");
printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "",
num_valid);
}
/**
* bootflow_handle_menu() - Handle running the menu and updating cur bootflow
*
* This shows the menu, allows the user to select something and then prints
* what happened
*
* @std: bootstd information
* @text_mode: true to run the menu in text mode
* @bflowp: Returns selected bootflow, on success
* Return: 0 on success (a bootflow was selected), -EAGAIN if nothing was
* chosen, other -ve value on other error
*/
__maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std,
bool text_mode,
struct bootflow **bflowp)
{
struct expo *exp;
struct bootflow *bflow;
int ret, seq;
ret = bootflow_menu_start(std, text_mode, &exp);
if (ret)
return log_msg_ret("bhs", ret);
expo_enter_mode(exp);
ret = -ERESTART;
do {
if (ret == -ERESTART) {
ret = expo_arrange(exp);
if (ret) {
expo_exit_mode(exp);
return log_msg_ret("bha", ret);
}
ret = expo_render(exp);
if (ret) {
expo_exit_mode(exp);
return log_msg_ret("bhr", ret);
}
}
ret = bootflow_menu_poll(exp, &seq);
} while (ret == -EAGAIN || ret == -ERESTART || ret == -EREMCHG);
expo_exit_mode(exp);
if (ret == -EPIPE) {
printf("Nothing chosen\n");
std->cur_bootflow = NULL;
} else if (ret) {
printf("Menu failed (err=%d)\n", ret);
} else {
bflow = alist_getw(&std->bootflows, seq, struct bootflow);
printf("Selected: %s\n", bflow->os_name ? bflow->os_name :
bflow->name);
std->cur_bootflow = bflow;
*bflowp = bflow;
}
expo_destroy(exp);
if (ret)
return ret;
return 0;
}
static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow_iter iter;
struct udevice *dev = NULL;
struct bootflow bflow;
bool all = false, boot = false, errors = false, no_global = false;
bool list = false, no_hunter = false, menu = false, text_mode = false;
bool any_part = false;
int num_valid = 0;
const char *label = NULL;
bool has_args;
int ret, i;
int flags;
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
has_args = argc > 1 && *argv[1] == '-';
if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) {
if (has_args) {
all = strchr(argv[1], 'a');
boot = strchr(argv[1], 'b');
errors = strchr(argv[1], 'e');
no_global = strchr(argv[1], 'G');
list = strchr(argv[1], 'l');
no_hunter = strchr(argv[1], 'H');
menu = strchr(argv[1], 'm');
any_part = strchr(argv[1], 'p');
text_mode = strchr(argv[1], 't');
argc--;
argv++;
}
if (argc > 1)
label = argv[1];
if (!label)
dev = std->cur_bootdev;
} else {
if (has_args) {
printf("Flags not supported: enable CONFIG_BOOTSTD_FULL\n");
return CMD_RET_USAGE;
}
boot = true;
}
std->cur_bootflow = NULL;
flags = 0;
if (!any_part)
flags |= BOOTFLOWIF_ONLY_BOOTABLE;
if (list)
flags |= BOOTFLOWIF_SHOW;
if (all)
flags |= BOOTFLOWIF_ALL;
if (no_global)
flags |= BOOTFLOWIF_SKIP_GLOBAL;
if (!no_hunter)
flags |= BOOTFLOWIF_HUNT;
/*
* If we have a device, just scan for bootflows attached to that device
*/
if (list) {
printf("Scanning for bootflows ");
if (dev)
printf("in bootdev '%s'\n", dev->name);
else if (label)
printf("with label '%s'\n", label);
else
printf("in all bootdevs\n");
show_header();
}
if (dev)
bootstd_clear_bootflows_for_bootdev(dev);
else
bootstd_clear_glob();
for (i = 0,
ret = bootflow_scan_first(dev, label, &iter, flags, &bflow);
i < 1000 && ret != -ENODEV;
i++, ret = bootflow_scan_next(&iter, &bflow)) {
bflow.err = ret;
if (!ret)
num_valid++;
ret = bootstd_add_bootflow(&bflow);
if (ret < 0) {
printf("Out of memory\n");
return CMD_RET_FAILURE;
}
if (list)
bootflow_show(i, &bflow, errors);
if (!menu && boot && !bflow.err)
bootflow_run_boot(&iter, &bflow);
}
bootflow_iter_uninit(&iter);
if (list)
show_footer(i, num_valid);
if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && IS_ENABLED(CONFIG_EXPO)) {
if (!num_valid && !list) {
printf("No bootflows found; try again with -l\n");
} else if (menu) {
struct bootflow *sel_bflow;
ret = bootflow_handle_menu(std, text_mode, &sel_bflow);
if (!ret && boot) {
ret = console_clear();
if (ret) {
log_err("Failed to clear console: %dE\n",
ret);
return ret;
}
bootflow_run_boot(NULL, sel_bflow);
}
}
}
return 0;
}
#ifdef CONFIG_CMD_BOOTFLOW_FULL
static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct udevice *dev;
struct bootflow *bflow;
int num_valid = 0;
bool errors = false;
int ret, i;
if (argc > 1 && *argv[1] == '-')
errors = strchr(argv[1], 'e');
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
dev = std->cur_bootdev;
/* If we have a device, just list bootflows attached to that device */
if (dev) {
printf("Showing bootflows for bootdev '%s'\n", dev->name);
show_header();
for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
!ret;
ret = bootdev_next_bootflow(&bflow), i++) {
num_valid += bflow->state == BOOTFLOWST_READY;
bootflow_show(i, bflow, errors);
}
} else {
printf("Showing all bootflows\n");
show_header();
for (ret = bootflow_first_glob(&bflow), i = 0;
!ret;
ret = bootflow_next_glob(&bflow), i++) {
num_valid += bflow->state == BOOTFLOWST_READY;
bootflow_show(i, bflow, errors);
}
}
show_footer(i, num_valid);
return 0;
}
static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow, *found;
struct udevice *dev;
const char *name;
char *endp;
int seq, i;
int ret;
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
;
if (argc < 2) {
std->cur_bootflow = NULL;
return 0;
}
dev = std->cur_bootdev;
name = argv[1];
seq = simple_strtol(name, &endp, 16);
found = NULL;
/*
* If we have a bootdev device, only allow selection of bootflows
* attached to that device
*/
if (dev) {
for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
!ret;
ret = bootdev_next_bootflow(&bflow), i++) {
if (*endp ? !strcmp(bflow->name, name) : i == seq) {
found = bflow;
break;
}
}
} else {
for (ret = bootflow_first_glob(&bflow), i = 0;
!ret;
ret = bootflow_next_glob(&bflow), i++) {
if (*endp ? !strcmp(bflow->name, name) : i == seq) {
found = bflow;
break;
}
}
}
if (!found) {
printf("Cannot find bootflow '%s' ", name);
if (dev)
printf("in bootdev '%s' ", dev->name);
printf("(err=%d)\n", ret);
return CMD_RET_FAILURE;
}
std->cur_bootflow = found;
if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
if (env_set("bootargs", found->cmdline)) {
printf("Cannot set bootargs\n");
return CMD_RET_FAILURE;
}
}
return 0;
}
static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow;
bool x86_setup = false;
bool dump = false;
int ret;
if (argc > 1 && *argv[1] == '-') {
dump = strchr(argv[1], 'd');
x86_setup = strchr(argv[1], 's');
}
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
if (!std->cur_bootflow) {
printf("No bootflow selected\n");
return CMD_RET_FAILURE;
}
bflow = std->cur_bootflow;
if (IS_ENABLED(CONFIG_X86) && x86_setup) {
struct bootm_info bmi;
bootm_init(&bmi);
/* we don't know this at present */
bootm_x86_set(&bmi, bzimage_addr, 0);
bootm_x86_set(&bmi, base_ptr, bflow->x86_setup);
zimage_dump(&bmi, false);
return 0;
}
printf("Name: %s\n", bflow->name);
printf("Device: %s\n", bflow->dev->name);
printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)");
printf("Method: %s\n", bflow->method ? bflow->method->name : "(none)");
printf("State: %s\n", bootflow_state_get_name(bflow->state));
printf("Partition: %d\n", bflow->part);
/* Show encryption status with LUKS version if applicable */
if (IS_ENABLED(CONFIG_BLK_LUKS)) {
if (bflow->flags & BOOTFLOWF_ENCRYPTED)
printf("Encrypted: LUKSv%d\n", bflow->luks_version);
else
printf("Encrypted: no\n");
}
printf("Subdir: %s\n", bflow->subdir ? bflow->subdir : "(none)");
printf("Filename: %s\n", bflow->fname);
printf("Buffer: ");
if (bflow->buf)
printf("%lx\n", (ulong)map_to_sysmem(bflow->buf));
else
printf("(not loaded)\n");
printf("Size: %x (%d bytes)\n", bflow->size, bflow->size);
printf("OS: %s\n", bflow->os_name ? bflow->os_name : "(none)");
printf("Cmdline: ");
if (bflow->cmdline)
puts(bflow->cmdline);
else
puts("(none)");
putc('\n');
if (bflow->x86_setup)
printf("X86 setup: %lx\n",
(ulong)map_to_sysmem(bflow->x86_setup));
printf("Logo: %s\n", bflow->logo ?
simple_xtoa((ulong)map_to_sysmem(bflow->logo)) : "(none)");
if (bflow->logo) {
printf("Logo size: %zx (%zd bytes)\n", bflow->logo_size,
bflow->logo_size);
}
printf("FDT: %s\n", bflow->fdt_fname);
if (bflow->fdt_fname) {
printf("FDT size: %x (%d bytes)\n", bflow->fdt_size,
bflow->fdt_size);
printf("FDT addr: %lx\n", bflow->fdt_addr);
}
printf("Error: %d\n", bflow->err);
if (IS_ENABLED(CONFIG_BOOTMETH_EFI) &&
bflow->method->driver == DM_DRIVER_GET(bootmeth_4efi)) {
struct efi_device_path *dp;
bool alloced;
ret = efi_dp_from_bootflow(bflow, &dp, &alloced);
printf("EFI path ");
if (!ret) {
printf("%pD\n", dp);
if (alloced)
efi_free_pool(dp);
} else {
printf("(err %dE)\n", ret);
}
}
if (dump && bflow->buf) {
/* Set some sort of maximum on the size */
int size = min(bflow->size, 10 << 10);
int i;
printf("Contents:\n\n");
for (i = 0; i < size; i++) {
putc(bflow->buf[i]);
if (!(i % 128) && ctrlc()) {
printf("...interrupted\n");
break;
}
}
}
return 0;
}
static int do_bootflow_read(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow;
int ret;
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
/*
* Require a current bootflow. Users can use 'bootflow scan -b' to
* automatically scan and boot, if needed.
*/
if (!std->cur_bootflow) {
printf("No bootflow selected\n");
return CMD_RET_FAILURE;
}
bflow = std->cur_bootflow;
ret = bootflow_read_all(bflow);
if (ret) {
printf("Failed: err=%dE\n", ret);
return CMD_RET_FAILURE;
}
return 0;
}
static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow;
bool fake = false;
int ret;
if (IS_ENABLED(CONFIG_BOOTM_FAKE_GO) && argc > 1 && *argv[1] == '-')
fake = strchr(argv[1], 'f');
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
/*
* Require a current bootflow. Users can use 'bootflow scan -b' to
* automatically scan and boot, if needed.
*/
if (!std->cur_bootflow) {
printf("No bootflow selected\n");
return CMD_RET_FAILURE;
}
bflow = std->cur_bootflow;
if (IS_ENABLED(CONFIG_BOOTM_FAKE_GO)) {
if (fake)
bflow->flags |= BOOTFLOWF_FAKE_GO;
else
bflow->flags &= ~BOOTFLOWF_FAKE_GO;
}
log_debug("cmd bflow flags %x\n", bflow->flags);
ret = bootflow_run_boot(NULL, bflow);
if (ret)
return CMD_RET_FAILURE;
return 0;
}
static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow;
bool text_mode = false;
int ret;
if (!IS_ENABLED(CONFIG_EXPO)) {
printf("Menu not supported\n");
return CMD_RET_FAILURE;
}
if (argc > 1 && *argv[1] == '-')
text_mode = strchr(argv[1], 't');
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
ret = bootflow_handle_menu(std, text_mode, &bflow);
if (ret)
return CMD_RET_FAILURE;
return 0;
}
static int do_bootflow_cmdline(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct bootstd_priv *std;
struct bootflow *bflow;
const char *op, *arg, *val = NULL;
int ret;
if (argc < 3)
return CMD_RET_USAGE;
ret = bootstd_get_priv(&std);
if (ret)
return CMD_RET_FAILURE;
bflow = std->cur_bootflow;
if (!bflow) {
printf("No bootflow selected\n");
return CMD_RET_FAILURE;
}
op = argv[1];
arg = argv[2];
if (*op == 's') {
val = argv[3] ?: (const char *)BOOTFLOWCL_EMPTY;
}
switch (*op) {
case 'c': /* clear */
val = "";
fallthrough;
case 's': /* set */
case 'd': /* delete */
ret = bootflow_cmdline_set_arg(bflow, arg, val, true);
break;
case 'g': /* get */
ret = bootflow_cmdline_get_arg(bflow, arg, &val);
if (ret >= 0)
printf("%.*s\n", ret, val);
break;
case 'a': /* auto */
ret = bootflow_cmdline_auto(bflow, arg);
break;
}
switch (ret) {
case -E2BIG:
printf("Argument too long\n");
break;
case -ENOENT:
printf("Argument not found\n");
break;
case -EINVAL:
printf("Mismatched quotes\n");
break;
case -EBADF:
printf("Value must be quoted\n");
break;
default:
if (ret < 0)
printf("Unknown error: %dE\n", ret);
}
if (ret < 0)
return CMD_RET_FAILURE;
return 0;
}
#endif /* CONFIG_CMD_BOOTFLOW_FULL */
U_BOOT_LONGHELP(bootflow,
#ifdef CONFIG_CMD_BOOTFLOW_FULL
"scan [-abeGHlmpt] [bdev] - scan for valid bootflows (-l list, -a all,\n"
" -e errors, -b boot, -G no global, -H no hunters\n"
" -m menu, -t text-only\n"
"bootflow list [-e] - list scanned bootflows (-e errors)\n"
"bootflow select [<num>|<name>] - select a bootflow\n"
"bootflow info [-ds] - show info on current bootflow (-d dump bootflow)\n"
"bootflow read - read all current-bootflow files\n"
"bootflow boot [-f] - boot current bootflow (-f fake)\n"
"bootflow menu [-t] - show a menu of available bootflows\n"
"bootflow cmdline [set|get|clear|delete|auto] <param> [<value>] - update cmdline"
#else
"scan - boot first available bootflow\n"
#endif
);
U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan),
#ifdef CONFIG_CMD_BOOTFLOW_FULL
U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list),
U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select),
U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info),
U_BOOT_SUBCMD_MKENT(read, 1, 1, do_bootflow_read),
U_BOOT_SUBCMD_MKENT(boot, 2, 1, do_bootflow_boot),
U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu),
U_BOOT_SUBCMD_MKENT(cmdline, 4, 1, do_bootflow_cmdline),
#endif
);