diff --git a/.gitignore b/.gitignore index efe4e07..4f8de46 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ check_os_release check_uefi check_grub2 check_select_bootloader +check_probe *.log *.trs diff --git a/Makefile.am b/Makefile.am index e0e915c..6a1b9b1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,7 +160,8 @@ TESTS = \ check_os_release \ check_uefi \ check_grub2 \ - check_select_bootloader + check_select_bootloader \ + check_probe check_PROGRAMS = $(TESTS) @@ -298,6 +299,24 @@ check_select_bootloader_LDADD = \ $(BLKID_LIBS) \ $(CHECK_LIBS) +check_probe_SOURCES = \ + tests/check-probe.c \ + tests/blkid-harness.h \ + tests/system-harness.h \ + tests/harness.h \ + tests/harness.c + +check_probe_CFLAGS = \ + $(BLKID_CFLAGS) \ + $(CHECK_CFLAGS) \ + $(AM_CFLAGS) + +check_probe_LDADD = \ + libcbm.la \ + src/libnica/libnica.la \ + $(BLKID_LIBS) \ + $(CHECK_LIBS) + @VALGRIND_CHECK_RULES@ diff --git a/src/lib/blkid_stub.c b/src/lib/blkid_stub.c index 856d59f..9f087d0 100644 --- a/src/lib/blkid_stub.c +++ b/src/lib/blkid_stub.c @@ -47,6 +47,10 @@ static CbmBlkidOps default_blkid_ops = { .partition_get_flags = blkid_partition_get_flags, .partition_get_uuid = blkid_partition_get_uuid, + /* Partition table functions */ + .partlist_get_table = blkid_partlist_get_table, + .parttable_get_type = blkid_parttable_get_type, + /* Misc */ .devno_to_wholedisk = cbm_blkid_devno_to_wholedisk_wrapped, }; @@ -85,6 +89,10 @@ void cbm_blkid_set_vtable(CbmBlkidOps *ops) assert(blkid_ops->partition_get_flags != NULL); assert(blkid_ops->partition_get_uuid != NULL); + /* partition table functions */ + assert(blkid_ops->partlist_get_table != NULL); + assert(blkid_ops->parttable_get_type != NULL); + /* misc */ assert(blkid_ops->devno_to_wholedisk != NULL); } @@ -160,6 +168,19 @@ const char *cbm_blkid_partition_get_uuid(blkid_partition par) return blkid_ops->partition_get_uuid(par); } +/** + * Partition table related wrappers + */ +blkid_parttable cbm_blkid_partlist_get_table(blkid_partlist ls) +{ + return blkid_ops->partlist_get_table(ls); +} + +const char *cbm_blkid_parttable_get_type(blkid_parttable tab) +{ + return blkid_ops->parttable_get_type(tab); +} + /** * Misc functions */ diff --git a/src/lib/blkid_stub.h b/src/lib/blkid_stub.h index 72cd87b..7e98eca 100644 --- a/src/lib/blkid_stub.h +++ b/src/lib/blkid_stub.h @@ -36,6 +36,10 @@ typedef struct CbmBlkidOps { unsigned long long (*partition_get_flags)(blkid_partition par); const char *(*partition_get_uuid)(blkid_partition par); + /* Partition table functions */ + blkid_parttable (*partlist_get_table)(blkid_partlist ls); + const char *(*parttable_get_type)(blkid_parttable tab); + /* Misc functions */ int (*devno_to_wholedisk)(dev_t dev, char *diskname, size_t len, dev_t *diskdevno); } CbmBlkidOps; @@ -70,6 +74,16 @@ typedef struct CbmBlkidOps { */ #define CBM_BLKID_PARTITION_SET ((blkid_partition)1) +/** + * Define a "set" blkid_parttable for testing + */ +#define CBM_BLKID_PARTTABLE_SET ((blkid_parttable)1) + +/** + * Define an empty blkid_parttable for testing + */ +#define CBM_BLKID_PARTTABLE_NULL ((blkid_parttable)0) + /** * Reset the blkid vtable */ @@ -105,6 +119,12 @@ blkid_partition cbm_blkid_partlist_get_partition(blkid_partlist ls, int n); unsigned long long cbm_blkid_partition_get_flags(blkid_partition par); const char *cbm_blkid_partition_get_uuid(blkid_partition par); +/** + * Partition table related wrappers + */ +blkid_parttable cbm_blkid_partlist_get_table(blkid_partlist ls); +const char *cbm_blkid_parttable_get_type(blkid_parttable tab); + /** * Misc related wrappers */ diff --git a/src/lib/probe.c b/src/lib/probe.c index 04d25a2..8d13238 100644 --- a/src/lib/probe.c +++ b/src/lib/probe.c @@ -22,6 +22,7 @@ #include #include "blkid_stub.h" +#include "files.h" #include "log.h" #include "probe.h" #include "system_stub.h" @@ -123,6 +124,68 @@ clean: return ret; } +/** + * Determine whether the probe lives on a GPT disk or not, + * which is the only instance in which we'll use PartUUID + */ +static bool cbm_probe_is_gpt(const char *path) +{ + autofree(char) *parent_disk = NULL; + blkid_probe probe = NULL; + blkid_partlist parts = NULL; + blkid_parttable table = NULL; + bool ret = false; + const char *table_type = NULL; + + /* Could be a weird image type or --path into chroot */ + parent_disk = get_parent_disk((char *)path); + if (!parent_disk) { + return false; + } + + probe = cbm_blkid_new_probe_from_filename(parent_disk); + if (!probe) { + LOG_ERROR("Unable to blkid probe %s", parent_disk); + return NULL; + } + + cbm_blkid_probe_enable_superblocks(probe, 1); + cbm_blkid_probe_set_superblocks_flags(probe, BLKID_SUBLKS_TYPE); + cbm_blkid_probe_enable_partitions(probe, 1); + cbm_blkid_probe_set_partitions_flags(probe, BLKID_PARTS_ENTRY_DETAILS); + + if (cbm_blkid_do_safeprobe(probe) != 0) { + LOG_ERROR("Error probing filesystem of %s: %s", parent_disk, strerror(errno)); + goto clean; + } + + parts = cbm_blkid_probe_get_partitions(probe); + if (cbm_blkid_partlist_numof_partitions(parts) <= 0) { + /* No partitions */ + goto clean; + } + + /* Grab the partition table */ + table = cbm_blkid_partlist_get_table(parts); + if (!table) { + LOG_ERROR("Unable to discover partitiojn table for %s: %s", + parent_disk, + strerror(errno)); + goto clean; + } + + /* Determine the partition table type. We only care if its GPT. */ + table_type = cbm_blkid_parttable_get_type(table); + if (table_type && streq(table_type, "gpt")) { + ret = true; + } + +clean: + cbm_blkid_free_probe(probe); + errno = 0; + return ret; +} + CbmDeviceProbe *cbm_probe_path(const char *path) { CbmDeviceProbe probe = { 0 }; @@ -177,6 +240,14 @@ CbmDeviceProbe *cbm_probe_path(const char *path) } } + /* If the device isn't GPT, clear out the the PartUUID */ + probe.gpt = cbm_probe_is_gpt(path); + if (!probe.gpt && probe.part_uuid) { + free(probe.part_uuid); + probe.part_uuid = NULL; + } + + /* Now check we have at least one UUID value */ if (!probe.part_uuid && !probe.uuid) { LOG_ERROR("Unable to find UUID for %s: %s", devnode, strerror(errno)); } diff --git a/src/lib/probe.h b/src/lib/probe.h index 021e11d..26bf297 100644 --- a/src/lib/probe.h +++ b/src/lib/probe.h @@ -13,8 +13,11 @@ #define _GNU_SOURCE +#include #include +#include "util.h" + /** * A CbmCbmDeviceProbe is the result of a cbm_probe_path operation, caching * fields useful to clr-boot-manager in terms of partition analysis. @@ -24,6 +27,7 @@ typedef struct CbmDeviceProbe { char *part_uuid; /**< PartUUID for GPT partitions */ char *luks_uuid; /**< Parent LUKS UUID for the partition */ dev_t dev; /**< The device itself */ + bool gpt; /** +#include +#include +#include +#include + +#include "blkid_stub.h" +#include "bootloader.h" +#include "bootman.h" +#include "config.h" +#include "files.h" +#include "log.h" +#include "probe.h" + +#include "blkid-harness.h" +#include "harness.h" +#include "system-harness.h" + +#define PLAYGROUND_ROOT TOP_BUILD_DIR "/tests/update_playground" + +/** + * Restore the default testing vtables + */ +static void bootman_probe_set_default_vtables(void) +{ + cbm_blkid_set_vtable(&BlkidTestOps); + cbm_system_set_vtable(&SystemTestOps); +} + +/** + * Coerce GPT lookup + */ +static inline int gpt_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; +} + +/** + * This will force the tests to use the GPT detection codepaths + */ +static CbmBlkidOps gpt_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_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 = 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 = gpt_devno_to_wholedisk, +}; + +static inline const char *mbr_parttable_get_type(__cbm_unused__ blkid_parttable tab) +{ + /* Return correct mbr identifier */ + return "mbr"; +} +/** + * This will force the tests to use the MBR detection codepaths + */ +static CbmBlkidOps mbr_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_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 = test_blkid_partition_get_flags, + .partition_get_uuid = test_blkid_partition_get_uuid, + .partlist_get_table = test_blkid_partlist_get_table, + .parttable_get_type = mbr_parttable_get_type, + .devno_to_wholedisk = gpt_devno_to_wholedisk, +}; + +static void bootman_probe_set_gpt_vtables(void) +{ + /* override test ops for legacy testing */ + cbm_blkid_set_vtable(&gpt_blkid_ops); + cbm_system_set_vtable(&SystemTestOps); +} + +static void bootman_probe_set_mbr_vtables(void) +{ + /* override test ops for legacy testing */ + cbm_blkid_set_vtable(&mbr_blkid_ops); + cbm_system_set_vtable(&SystemTestOps); +} + +/** + * Ensure we can detect GPT rootfs for a UEFI system. + * Note that `set_test_system_legacy` is only responsible for creating the + * root nodes, when we initialise the playground the UEFI nodes are also + * created. + */ +START_TEST(bootman_probe_basic_gpt) +{ + static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true }; + autofree(BootManager) *m = NULL; + bootman_probe_set_gpt_vtables(); + autofree(CbmDeviceProbe) *probe = NULL; + + /* Let harness prep the root */ + m = prepare_playground(&config); + + set_test_system_legacy(); + probe = cbm_probe_path(PLAYGROUND_ROOT); + + fail_if(!probe, "Failed to get probe for a valid rootfs"); + fail_if(!probe->gpt, "GPT UEFI root not detected as GPT"); + fail_if(!probe->part_uuid, "GPT UEFI root has no PartUUID detected"); + fail_if(!streq(probe->part_uuid, DEFAULT_PART_UUID), + "Expected PartUUID '%s', got '%s'", + DEFAULT_PART_UUID, + probe->part_uuid); +} +END_TEST + +/** + * Much like the previous test, except that we force MBR detection for the root + * disk + */ +START_TEST(bootman_probe_basic_mbr) +{ + static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true }; + autofree(BootManager) *m = NULL; + bootman_probe_set_mbr_vtables(); + autofree(CbmDeviceProbe) *probe = NULL; + + /* Let harness prep the root */ + m = prepare_playground(&config); + + set_test_system_legacy(); + probe = cbm_probe_path(PLAYGROUND_ROOT); + + fail_if(!probe, "Failed to get probe for a valid rootfs"); + fail_if(probe->gpt, "MBR UEFI root not detected as MBR"); + fail_if(probe->part_uuid, "MBR UEFI root has a PartUUID detected"); + fail_if(!streq(probe->uuid, DEFAULT_UUID), + "Expected UUID '%s', got '%s'", + DEFAULT_UUID, + probe->uuid); +} +END_TEST + +/** + * UEFI + unknown partition table + */ +START_TEST(bootman_probe_basic_none) +{ + static PlaygroundConfig config = { "4.2.1-121.kvm", NULL, 0, .uefi = true }; + autofree(BootManager) *m = NULL; + bootman_probe_set_default_vtables(); + autofree(CbmDeviceProbe) *probe = NULL; + + /* Let harness prep the root */ + m = prepare_playground(&config); + + set_test_system_legacy(); + probe = cbm_probe_path(PLAYGROUND_ROOT); + + fail_if(!probe, "Failed to get probe for a valid rootfs"); + fail_if(probe->gpt, "Unknown UEFI root not detected as MBR"); + fail_if(probe->part_uuid, "Unknown UEFI root has a PartUUID detected"); + fail_if(!streq(probe->uuid, DEFAULT_UUID), + "Expected UUID '%s', got '%s'", + DEFAULT_UUID, + probe->uuid); +} +END_TEST + +static Suite *core_suite(void) +{ + Suite *s = NULL; + TCase *tc = NULL; + + s = suite_create("bootman_probe"); + + /* UEFI tests */ + tc = tcase_create("bootman_probe_basic_functions"); + tcase_add_test(tc, bootman_probe_basic_gpt); + tcase_add_test(tc, bootman_probe_basic_mbr); + tcase_add_test(tc, bootman_probe_basic_none); + 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); + + 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: + */ diff --git a/tests/check-select-bootloader.c b/tests/check-select-bootloader.c index 33f51e6..482c3c7 100644 --- a/tests/check-select-bootloader.c +++ b/tests/check-select-bootloader.c @@ -211,6 +211,8 @@ static CbmBlkidOps legacy_blkid_ops = { .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, }; @@ -304,6 +306,8 @@ static CbmBlkidOps grub2_blkid_ops = { .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, }; diff --git a/tests/harness.c b/tests/harness.c index 6f1b07a..f9c706e 100644 --- a/tests/harness.c +++ b/tests/harness.c @@ -566,6 +566,8 @@ void set_test_system_legacy(void) autofree(char) *ddir = NULL; autofree(char) *diskdir = NULL; autofree(char) *diskfile = NULL; + autofree(char) *diskdir_uuid = NULL; + autofree(char) *diskfile_uuid = NULL; autofree(char) *dfile = NULL; autofree(char) *dlink = NULL; const char *devfs_path = cbm_system_get_devfs_path(); @@ -594,11 +596,21 @@ void set_test_system_legacy(void) diskdir = string_printf("%s/disk/by-partuuid", cbm_system_get_devfs_path()); diskfile = string_printf("%s/%s", diskdir, "Test-PartUUID"); + /* Create /dev/disk/by-uuid portions */ + diskdir_uuid = string_printf("%s/disk/by-uuid", cbm_system_get_devfs_path()); + diskfile_uuid = string_printf("%s/%s", diskdir, "Test-UUID"); + nc_mkdir_p(diskdir, 00755); if (!file_set_text(diskfile, "clr-boot-manager Legacy testing")) { DECLARE_OOM(); abort(); } + + nc_mkdir_p(diskdir_uuid, 00755); + if (!file_set_text(diskfile_uuid, "clr-boot-manager Legacy testing")) { + DECLARE_OOM(); + abort(); + } } /*