bootctl: Initial experimentation

This provides a basic prototype for boot control.

Some documentation is in boot/bootctl/README.rst
This commit is contained in:
Simon Glass
2025-03-14 07:47:28 +00:00
parent 8bae99245b
commit d9152ea75e
30 changed files with 3371 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright 2025 Canonical Ltd
obj-y += bootctl.o

327
test/boot/bootctl/bootctl.c Normal file
View File

@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Tests for bootctl
*
* For now this is just samples, showing how the different functions can be
* tested
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#include <stdbool.h>
#include <bootctl.h>
#include <bootmeth.h>
#include <dm.h>
#include <os.h>
#include <test/ut.h>
#include "bootctl_common.h"
#include <bootctl/measure.h>
#include <bootctl/oslist.h>
#include <bootctl/state.h>
#include "../bootstd_common.h"
/* test that expected devices are available and can be probed */
static int bootctl_base(struct unit_test_state *uts)
{
struct udevice *dev;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_UI, &dev));
ut_asserteq_str("ui", dev->name);
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
ut_asserteq_str("oslist", dev->name);
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
ut_asserteq_str("state", dev->name);
return 0;
}
BOOTCTL_TEST(bootctl_base, UTF_DM | UTF_SCAN_FDT);
/* test finding an OS */
static int bootctl_oslist(struct unit_test_state *uts)
{
struct oslist_iter iter;
struct osinfo info;
struct bootflow *bflow = &info.bflow;
struct udevice *dev;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
ut_asserteq_str("oslist", dev->name);
/* initially we should only see Fedora */
bc_oslist_setup_iter(&iter);
ut_assertok(bc_oslist_next(dev, &iter, &info));
ut_asserteq_str("mmc1.bootdev.part_1", bflow->name);
ut_asserteq_strn("Fedora-Workstation", bflow->os_name);
ut_asserteq(-ENODEV, bc_oslist_next(dev, &iter, &info));
return 0;
}
BOOTCTL_TEST(bootctl_oslist, UTF_DM | UTF_SCAN_FDT);
/* test finding OSes on mmc and usb */
static int bootctl_oslist_usb(struct unit_test_state *uts)
{
struct oslist_iter iter;
struct osinfo info;
struct bootflow *bflow = &info.bflow;
struct udevice *dev;
test_set_skip_delays(true);
bootstd_reset_usb();
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
ut_asserteq_str("oslist", dev->name);
/* include usb in the bootdev order */
ut_assertok(bootdev_set_order("mmc usb"));
bc_oslist_setup_iter(&iter);
ut_assertok(bc_oslist_next(dev, &iter, &info));
ut_asserteq_str("mmc1.bootdev.part_1", bflow->name);
ut_assertok(bc_oslist_next(dev, &iter, &info));
ut_asserteq_str("hub1.p4.usb_mass_storage.lun0.bootdev.part_1", bflow->name);
ut_asserteq(-ENODEV, bc_oslist_next(dev, &iter, &info));
return 0;
}
BOOTCTL_TEST(bootctl_oslist_usb, UTF_DM | UTF_SCAN_FDT);
/* test basic use of state */
static int bootctl_simple_state_base(struct unit_test_state *uts)
{
struct udevice *dev;
const char *sval;
struct abuf buf;
bool bval;
long ival;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
ut_assertok(bc_state_write_bool(dev, "fred", false));
ut_assertok(bc_state_write_bool(dev, "mary", true));
ut_assertok(bc_state_write_int(dev, "alex", 123));
ut_assertok(bc_state_write_str(dev, "john", "abc"));
ut_assertok(bc_state_read_bool(dev, "fred", &bval));
ut_asserteq(false, bval);
ut_assertok(bc_state_read_bool(dev, "mary", &bval));
ut_asserteq(true, bval);
ut_assertok(bc_state_read_int(dev, "alex", &ival));
ut_asserteq(123, ival);
ut_assertok(bc_state_read_str(dev, "john", &sval));
ut_asserteq_str("abc", sval);
/* check the buffer contents, including the nul terminator */
ut_assertok(bc_state_save_to_buf(dev, &buf));
ut_asserteq_str("fred=0\nmary=1\nalex=123\njohn=abc\n", buf.data);
ut_asserteq(strlen("fred=0\nmary=1\nalex=123\njohn=abc\n") + 1,
buf.size);
ut_asserteq(0, *((char *)buf.data + buf.size - 1));
abuf_uninit(&buf);
/* overwrite */
ut_assertok(bc_state_write_str(dev, "fred", "def"));
ut_assertok(bc_state_read_str(dev, "fred", &sval));
ut_asserteq_str("def", sval);
ut_assertok(bc_state_clear(dev));
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "fred", &bval));
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "mary", &bval));
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "john", &bval));
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "alex", &bval));
return 0;
}
BOOTCTL_TEST(bootctl_simple_state_base, UTF_DM | UTF_SCAN_FDT);
/* test loading / saving state */
static int bootctl_simple_state_loadsave(struct unit_test_state *uts)
{
struct udevice *dev;
char *buf;
int size;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
ut_assertok(bc_state_write_bool(dev, "fred", false));
ut_assertok(bc_state_write_bool(dev, "mary", true));
ut_assertok(bc_state_save(dev));
/* check the file contents, including the nul terminator */
ut_assertok(os_read_file("bootctl.ini", (void **)&buf, &size));
ut_asserteq_str("fred=0\nmary=1\n", buf);
ut_asserteq(strlen("fred=0\nmary=1\n") + 1, size);
ut_asserteq(0, buf[size - 1]);
os_free(buf);
ut_assertok(bc_state_load(dev));
return 0;
}
BOOTCTL_TEST(bootctl_simple_state_loadsave, UTF_DM | UTF_SCAN_FDT);
/* test limits */
static int bootctl_simple_state_limits(struct unit_test_state *uts)
{
struct udevice *dev;
char long_key[32]; /* avoid using constants from impl */
struct abuf buf;
char *data;
int ch;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
/* cannot use NULL as a key or value */
ut_asserteq(-EINVAL, bc_state_write_bool(dev, NULL, false));
ut_asserteq(-EINVAL, bc_state_write_str(dev, "key", NULL));
/* empty key and value */
ut_asserteq(-EINVAL, bc_state_write_str(dev, "", "val"));
ut_assertok(bc_state_write_str(dev, "empty", ""));
/* no spaces allowed in a key */
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, "my key", "val"));
/* check key characters */
for (ch = 1; ch < 256; ch++) {
char key[4] = "key";
bool ok;
ok = ch == '_' || (ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9');
key[1] = ch;
printf("checking ch %x\n", ch);
if (ok)
ut_assertok(bc_state_write_str(dev, key, "val"));
else
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, key, "val"));
}
/* key too long */
strcpy(long_key, "1234567890123456789012345678901");
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, long_key, "val"));
long_key[30] = '\0';
ut_assertok(bc_state_write_str(dev, long_key, "val"));
/* value too long */
abuf_init(&buf);
ut_asserteq(true, abuf_realloc(&buf, 0x1002));
data = buf.data;
memset(data, 'x', 0x1001);
data[0x1001] = '\0';
ut_asserteq(-E2BIG, bc_state_write_str(dev, "try", data));
data[0x1000] = '\0';
ut_assertok(bc_state_write_str(dev, "try", data));
abuf_uninit(&buf);
return 0;
}
BOOTCTL_TEST(bootctl_simple_state_limits, UTF_DM | UTF_SCAN_FDT);
/* test integers */
static int bootctl_simple_state_int(struct unit_test_state *uts)
{
struct udevice *dev;
long ival;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
/* basic integers */
ut_assertok(bc_state_write_int(dev, "val", 0));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq(0, ival);
ut_assertok(bc_state_write_int(dev, "val", 1));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq(1, ival);
ut_assertok(bc_state_write_int(dev, "val", -1));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq(-1, ival);
/* large ints */
ut_assertok(bc_state_write_int(dev, "val", 0xffffffffl));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq(0xffffffffl, ival);
ut_assertok(bc_state_write_int(dev, "val", -0xffffffffl));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq_64(-0xffffffffl, ival);
ut_assertok(bc_state_write_int(dev, "val", 0x7fffffffffffffffll));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq_64(0x7fffffffffffffffll, ival);
ut_assertok(bc_state_write_int(dev, "val", -0x7fffffffffffffffll));
ut_assertok(bc_state_read_int(dev, "val", &ival));
ut_asserteq_64(-0x7fffffffffffffffll, ival);
return 0;
}
BOOTCTL_TEST(bootctl_simple_state_int, UTF_DM | UTF_SCAN_FDT);
/* test measurement */
static int bootctl_simple_measure(struct unit_test_state *uts)
{
struct bootflow_img *img[3];
struct osinfo osinfo;
struct bootflow *bflow = &osinfo.bflow;
const struct measure_info *info;
struct udevice *dev;
struct alist result;
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_MEASURE, &dev));
ut_assertok(bc_measure_start(dev));
/* set up some data */
memset(&osinfo, '\0', sizeof(struct osinfo));
alist_init_struct(&bflow->images, struct bootflow_img);
/* add a few images */
img[0] = bootflow_img_add(bflow, "kernel",
(enum bootflow_img_t)IH_TYPE_KERNEL, 0,
0x100);
ut_assertnonnull(img);
img[1] = bootflow_img_add(bflow, "initrd",
(enum bootflow_img_t)IH_TYPE_RAMDISK, 0x100,
0x200);
ut_assertnonnull(img);
/* the fdt is missing so this should fail */
ut_asserteq(-ENOENT, bc_measure_process(dev, &osinfo, &result));
if (IS_ENABLED(CONFIG_LOGF_FUNC))
ut_assert_nextline(" simple_process() Missing image 'flat_dt'");
else
ut_assert_nextline("Missing image 'flat_dt'");
ut_assert_console_end();
alist_uninit(&result);
img[2] = bootflow_img_add(bflow, "fdt",
(enum bootflow_img_t)IH_TYPE_FLATDT, 0x300,
0x30);
ut_assertok(bc_measure_process(dev, &osinfo, &result));
/* check the result */
ut_asserteq(3, result.count);
info = alist_get(&result, 0, struct measure_info);
ut_asserteq_ptr(img[0], info[0].img);
ut_asserteq_ptr(img[1], info[1].img);
ut_asserteq_ptr(img[2], info[2].img);
/* TODO: We should also a) read out the TPM log and b) check TPM PCRs */
ut_assertnonnull(img);
return 0;
}
BOOTCTL_TEST(bootctl_simple_measure, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE);

View File

@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Common definitions for bootctl tests
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#ifndef __bootctl_common_h
#define __bootctl_common_h
#define BOOTCTL_TEST(_name, _flags) UNIT_TEST(_name, _flags, bootctl)
#endif