mirror of
https://github.com/clearlinux/clr-boot-manager.git
synced 2026-06-16 02:35:51 +00:00
74dd4636e4
Calling stat() on some filesystems, like Btrfs, results in a dev_t with no relation to the backing device(s), and subsequent failure to detect the root fs. Add support such filesystems by retrieving the block device from `/proc/self/mounts` and working on from there. Resolves #61, #182 and #193.
491 lines
16 KiB
C
491 lines
16 KiB
C
/*
|
|
* This file is part of clr-boot-manager.
|
|
*
|
|
* This test suite is designed to resolve issue #54:
|
|
* https://github.com/ikeydoherty/clr-boot-manager/issues/54
|
|
*
|
|
* The basic logic is, create a suitable test environment, and then have
|
|
* CBM initialise a BootManager for the root, which will fall through:
|
|
*
|
|
* - cbm_inspect_root
|
|
* - boot_manager_select_bootloader
|
|
*
|
|
* We know the update logic works based on the other tests. Here, we are
|
|
* trying to ascertain that the correct bootloader is selected in every case
|
|
* for the given system configuration.
|
|
*
|
|
* Copyright © 2016-2018 Intel Corporation
|
|
*
|
|
* clr-boot-manager is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1
|
|
* of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <check.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/sysmacros.h>
|
|
|
|
#include "bootloader.h"
|
|
#include "bootman.h"
|
|
#define _BOOTMAN_INTERNAL_
|
|
#include "bootman_private.h"
|
|
#undef _BOOTMAN_INTERNAL_
|
|
#include "config.h"
|
|
#include "files.h"
|
|
#include "log.h"
|
|
#include "nica/array.h"
|
|
#include "nica/files.h"
|
|
#include "util.h"
|
|
#include "writer.h"
|
|
|
|
#include "blkid-harness.h"
|
|
#include "harness.h"
|
|
#include "system-harness.h"
|
|
|
|
/**
|
|
* Define a single name for the UEFI bootloader to reduce complexity further down
|
|
*/
|
|
#if defined(HAVE_SHIM_SYSTEMD_BOOT)
|
|
#define UEFI_BOOTLOADER_NAME "shim-systemd"
|
|
#elif defined(HAVE_SYSTEMD_BOOT)
|
|
#define UEFI_BOOTLOADER_NAME "systemd"
|
|
#endif
|
|
|
|
#define PLAYGROUND_ROOT TOP_BUILD_DIR "/tests/update_playground"
|
|
|
|
/**
|
|
* Restore the default testing vtables
|
|
*/
|
|
static void bootman_select_set_default_vtables(void)
|
|
{
|
|
cbm_blkid_set_vtable(&BlkidTestOps);
|
|
cbm_system_set_vtable(&SystemTestOps);
|
|
}
|
|
|
|
/**
|
|
* Simple assertion that the bootloader is the one we've asked for
|
|
*/
|
|
static void ensure_bootloader_is(BootManager *manager, const char *expected)
|
|
{
|
|
fail_if(manager == NULL, "No BootManager");
|
|
fail_if(!manager->bootloader, "No bootloader is selected. Expected %s", expected);
|
|
const char *name = manager->bootloader->name;
|
|
fail_if(!streq(name, expected), "Expected bootloader '%s', got '%s'", expected, name);
|
|
}
|
|
|
|
/**
|
|
* Harness helps us out by creating the required "bits" within the prepared root.
|
|
* This method will forcibly break that logic by making the root device go away
|
|
*
|
|
* The blkid tests themselves will still work fine as blkid has been encapsulated
|
|
* already.
|
|
*/
|
|
static void nuke_boot_device(struct PlaygroundConfig *config)
|
|
{
|
|
if (config->uefi) {
|
|
fail_if(!nc_rm_rf(PLAYGROUND_ROOT "/sys/firmware/efi/efivars"),
|
|
"Failed to remove efivars");
|
|
}
|
|
fail_if(!nc_rm_rf(PLAYGROUND_ROOT "/dev"), "Failed to remove /dev");
|
|
}
|
|
|
|
/**
|
|
* ############ BEGIN UEFI TESTS #################
|
|
*/
|
|
|
|
/**
|
|
* We're operating in native mode (!image) with UEFI, and a boot device
|
|
*/
|
|
START_TEST(bootman_select_uefi_native_with_boot)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true };
|
|
setenv("CBM_TEST_FSTYPE", "vfat", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_default_vtables();
|
|
|
|
m = prepare_playground(&config);
|
|
ensure_bootloader_is(m, UEFI_BOOTLOADER_NAME);
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* We're operating in native mode (!image) with UEFI, and NO boot device
|
|
*/
|
|
START_TEST(bootman_select_uefi_native_without_boot)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true };
|
|
setenv("CBM_TEST_FSTYPE", "vfat", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_default_vtables();
|
|
|
|
/* Reinit the boot manager with no supporting boot device */
|
|
m = prepare_playground(&config);
|
|
nuke_boot_device(&config);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
ensure_bootloader_is(m, UEFI_BOOTLOADER_NAME);
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* We're operating in image mode with UEFI, and a boot device
|
|
*/
|
|
START_TEST(bootman_select_uefi_image_with_boot)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true };
|
|
setenv("CBM_TEST_FSTYPE", "vfat", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_default_vtables();
|
|
|
|
m = prepare_playground(&config);
|
|
boot_manager_set_image_mode(m, true);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
ensure_bootloader_is(m, UEFI_BOOTLOADER_NAME);
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* We're operating in image mode with UEFI, and NO boot device
|
|
*/
|
|
START_TEST(bootman_select_uefi_image_without_boot)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true };
|
|
setenv("CBM_TEST_FSTYPE", "vfat", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_default_vtables();
|
|
|
|
/* Reinit the boot manager with no supporting boot device */
|
|
m = prepare_playground(&config);
|
|
nuke_boot_device(&config);
|
|
boot_manager_set_image_mode(m, true);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
ensure_bootloader_is(m, UEFI_BOOTLOADER_NAME);
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* Coerce legacy lookup
|
|
*/
|
|
static inline int legacy_devno_to_wholedisk(__cbm_unused__ dev_t dev, __cbm_unused__ char *diskname,
|
|
__cbm_unused__ size_t len,
|
|
__cbm_unused__ dev_t *diskdevno)
|
|
{
|
|
*diskdevno = makedev(8, 8);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Coerce legacy lookup
|
|
*/
|
|
static inline dev_t legacy_probe_get_wholedisk_devno(__cbm_unused__ blkid_probe pr)
|
|
{
|
|
return makedev(8, 8);
|
|
}
|
|
|
|
/**
|
|
* Forces detection of GPT legacy boot partition
|
|
*/
|
|
static inline unsigned long long legacy_partition_get_flags(__cbm_unused__ blkid_partition par)
|
|
{
|
|
return (1ULL << 2);
|
|
}
|
|
|
|
/**
|
|
* Force the detection of a boot partition GPT UUID
|
|
*/
|
|
static inline const char *legacy_partition_get_uuid(__cbm_unused__ blkid_partition par)
|
|
{
|
|
return DEFAULT_PART_UUID;
|
|
}
|
|
|
|
/**
|
|
* This will force the tests to use the legacy detection codepaths
|
|
*/
|
|
static CbmBlkidOps legacy_blkid_ops = {
|
|
.probe_new_from_filename = test_blkid_new_probe_from_filename,
|
|
.probe_enable_superblocks = test_blkid_probe_enable_superblocks,
|
|
.probe_set_superblocks_flags = test_blkid_probe_set_superblocks_flags,
|
|
.probe_enable_partitions = test_blkid_probe_enable_partitions,
|
|
.probe_set_partitions_flags = test_blkid_probe_set_partitions_flags,
|
|
.probe_get_wholedisk_devno = legacy_probe_get_wholedisk_devno,
|
|
.probe_lookup_value = test_blkid_probe_lookup_value,
|
|
.do_safeprobe = test_blkid_do_safeprobe,
|
|
.free_probe = test_blkid_free_probe,
|
|
.probe_get_partitions = test_blkid_probe_get_partitions,
|
|
.partlist_numof_partitions = test_blkid_partlist_numof_partitions,
|
|
.partlist_get_partition = test_blkid_partlist_get_partition,
|
|
.partition_get_flags = legacy_partition_get_flags,
|
|
.partition_get_uuid = legacy_partition_get_uuid,
|
|
.partlist_get_table = test_blkid_partlist_get_table,
|
|
.parttable_get_type = test_blkid_parttable_get_type,
|
|
.devno_to_wholedisk = legacy_devno_to_wholedisk,
|
|
.devno_to_devname = test_blkid_devno_to_devname,
|
|
};
|
|
|
|
static void bootman_select_set_legacy_vtables(void)
|
|
{
|
|
/* override test ops for legacy testing */
|
|
cbm_blkid_set_vtable(&legacy_blkid_ops);
|
|
cbm_system_set_vtable(&SystemTestOps);
|
|
}
|
|
|
|
/**
|
|
* ############ BEGIN EXTLINUX TESTS #################
|
|
*/
|
|
|
|
/**
|
|
* We're operating in native mode (!image) with a legacy device available
|
|
*
|
|
* extlinux mode will only be activated when we find a legacy device through
|
|
* blkid inspection of the root device. Thus we *must* have a boot device,
|
|
* and the other tests will ensure extlinux isn't detected.
|
|
*/
|
|
START_TEST(bootman_select_extlinux_native_with_boot)
|
|
{
|
|
PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = false };
|
|
setenv("CBM_TEST_FSTYPE", "ext4", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_legacy_vtables();
|
|
|
|
m = prepare_playground(&config);
|
|
ensure_bootloader_is(m, "extlinux");
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* We're operating in image mode with a legacy device available
|
|
*/
|
|
START_TEST(bootman_select_extlinux_image_with_boot)
|
|
{
|
|
PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = false };
|
|
setenv("CBM_TEST_FSTYPE", "ext4", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_legacy_vtables();
|
|
|
|
m = prepare_playground(&config);
|
|
boot_manager_set_image_mode(m, true);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
ensure_bootloader_is(m, "extlinux");
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* ############ BEGIN GRUB2 TESTS #################
|
|
*/
|
|
|
|
/**
|
|
* We only permit UUID in our tests.
|
|
*/
|
|
static int grub2_blkid_probe_lookup_value(__cbm_unused__ blkid_probe pr, const char *name,
|
|
const char **data, size_t *len)
|
|
{
|
|
if (!name || !data) {
|
|
return -1;
|
|
}
|
|
if (streq(name, "UUID")) {
|
|
*data = DEFAULT_UUID;
|
|
} else {
|
|
return -1;
|
|
}
|
|
if (!*data) {
|
|
abort();
|
|
}
|
|
if (len) {
|
|
*len = strlen(*data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Force blkid ops to work for GRUB2 (UUID/MBR system, no PartUUID)
|
|
*/
|
|
static CbmBlkidOps grub2_blkid_ops = {
|
|
.probe_new_from_filename = test_blkid_new_probe_from_filename,
|
|
.probe_enable_superblocks = test_blkid_probe_enable_superblocks,
|
|
.probe_set_superblocks_flags = test_blkid_probe_set_superblocks_flags,
|
|
.probe_enable_partitions = test_blkid_probe_enable_partitions,
|
|
.probe_set_partitions_flags = test_blkid_probe_set_partitions_flags,
|
|
.probe_get_wholedisk_devno = test_blkid_probe_get_wholedisk_devno,
|
|
.probe_lookup_value = grub2_blkid_probe_lookup_value,
|
|
.do_safeprobe = test_blkid_do_safeprobe,
|
|
.free_probe = test_blkid_free_probe,
|
|
.probe_get_partitions = test_blkid_probe_get_partitions,
|
|
.partlist_numof_partitions = test_blkid_partlist_numof_partitions,
|
|
.partlist_get_partition = test_blkid_partlist_get_partition,
|
|
.partition_get_flags = test_blkid_partition_get_flags,
|
|
.partition_get_uuid = test_blkid_partition_get_uuid,
|
|
.partlist_get_table = test_blkid_partlist_get_table,
|
|
.parttable_get_type = test_blkid_parttable_get_type,
|
|
.devno_to_wholedisk = test_blkid_devno_to_wholedisk,
|
|
.devno_to_devname = test_blkid_devno_to_devname,
|
|
};
|
|
|
|
static void bootman_select_set_grub2_vtables(void)
|
|
{
|
|
/* override test ops for legacy testing */
|
|
cbm_blkid_set_vtable(&grub2_blkid_ops);
|
|
cbm_system_set_vtable(&SystemTestOps);
|
|
}
|
|
|
|
/**
|
|
* We're operating in native mode (!image) with GRUB2, and NO boot device
|
|
* We ONLY support GRUB2 in "native" mode, i.e. via chroot or actually native.
|
|
*/
|
|
START_TEST(bootman_select_grub2_native_without_boot)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = false };
|
|
setenv("CBM_TEST_FSTYPE", "ext4", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_grub2_vtables();
|
|
|
|
/* Reinit the boot manager with no supporting boot device */
|
|
m = prepare_playground(&config);
|
|
nuke_boot_device(&config);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
ensure_bootloader_is(m, "grub2");
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* ############ BEGIN EDGE CASE TESTS #################
|
|
*/
|
|
|
|
/**
|
|
* We have a GPT disk that contains a legacy partition, but we're in native
|
|
* mode with UEFI available
|
|
* We should select UEFI bootloader when /sys/firmware/efi exists
|
|
*/
|
|
START_TEST(bootman_select_edge_uefi_with_legacy_part_native)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = false };
|
|
setenv("CBM_TEST_FSTYPE", "ext4", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_legacy_vtables();
|
|
|
|
/* At this point we would've selected extlinux */
|
|
m = prepare_playground(&config);
|
|
ensure_bootloader_is(m, "extlinux");
|
|
|
|
/* Construct /sys/firmware/efi. Now we need it to be UEFI bootloader */
|
|
fail_if(!nc_mkdir_p(PLAYGROUND_ROOT "/sys/firmware/efi", 00755), "Failed to emulate EFI");
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
ensure_bootloader_is(m, UEFI_BOOTLOADER_NAME);
|
|
}
|
|
END_TEST
|
|
|
|
/**
|
|
* We have a GPT disk that contains a legacy partition, but we're in image
|
|
* mode with UEFI available
|
|
* We should only ever select extlinux
|
|
*/
|
|
START_TEST(bootman_select_edge_uefi_with_legacy_part_image)
|
|
{
|
|
static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = false };
|
|
setenv("CBM_TEST_FSTYPE", "ext4", 1);
|
|
autofree(BootManager) *m = NULL;
|
|
bootman_select_set_legacy_vtables();
|
|
|
|
/* At this point we would've selected extlinux */
|
|
m = prepare_playground(&config);
|
|
ensure_bootloader_is(m, "extlinux");
|
|
|
|
/* Ensure it's still extlinux in image mode */
|
|
boot_manager_set_image_mode(m, true);
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
ensure_bootloader_is(m, "extlinux");
|
|
|
|
/* Construct /sys/firmware/efi. */
|
|
fail_if(!nc_mkdir_p(PLAYGROUND_ROOT "/sys/firmware/efi", 00755), "Failed to emulate EFI");
|
|
boot_manager_set_prefix(m, PLAYGROUND_ROOT);
|
|
|
|
/* We're in image mode, the firmware is not relevant */
|
|
ensure_bootloader_is(m, "extlinux");
|
|
}
|
|
END_TEST
|
|
|
|
static Suite *core_suite(void)
|
|
{
|
|
Suite *s = NULL;
|
|
TCase *tc = NULL;
|
|
|
|
s = suite_create("bootman_select");
|
|
|
|
/* UEFI tests */
|
|
tc = tcase_create("bootman_select_uefi_functions");
|
|
tcase_add_test(tc, bootman_select_uefi_native_with_boot);
|
|
tcase_add_test(tc, bootman_select_uefi_native_without_boot);
|
|
tcase_add_test(tc, bootman_select_uefi_image_with_boot);
|
|
tcase_add_test(tc, bootman_select_uefi_image_without_boot);
|
|
suite_add_tcase(s, tc);
|
|
|
|
/* extlinux tests */
|
|
tc = tcase_create("bootman_select_extlinux_functions");
|
|
tcase_add_test(tc, bootman_select_extlinux_native_with_boot);
|
|
tcase_add_test(tc, bootman_select_extlinux_image_with_boot);
|
|
suite_add_tcase(s, tc);
|
|
|
|
/* grub2 tests */
|
|
tc = tcase_create("bootman_select_grub2_functions");
|
|
tcase_add_test(tc, bootman_select_grub2_native_without_boot);
|
|
suite_add_tcase(s, tc);
|
|
|
|
/* edge cases */
|
|
tc = tcase_create("Bootman_select_edge_cases");
|
|
tcase_add_test(tc, bootman_select_edge_uefi_with_legacy_part_native);
|
|
tcase_add_test(tc, bootman_select_edge_uefi_with_legacy_part_image);
|
|
suite_add_tcase(s, tc);
|
|
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
Suite *s;
|
|
SRunner *sr;
|
|
int fail;
|
|
|
|
/* syncing can be problematic during test suite runs */
|
|
cbm_set_sync_filesystems(false);
|
|
|
|
/* Ensure that logging is set up properly. */
|
|
setenv("CBM_DEBUG", "1", 1);
|
|
cbm_log_init(stderr);
|
|
|
|
/* Turn off the EFI variable manipulation. */
|
|
setenv("CBM_BOOTVAR_TEST_MODE", "yes", 1);
|
|
|
|
s = core_suite();
|
|
sr = srunner_create(s);
|
|
srunner_run_all(sr, CK_VERBOSE);
|
|
fail = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
|
|
if (fail > 0) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=8 tabstop=8 expandtab:
|
|
* :indentSize=8:tabSize=8:noTabs=true:
|
|
*/
|