diff --git a/man/bootctl.xml b/man/bootctl.xml index 42c4b9a8e6..4fec552ca8 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -341,6 +341,22 @@ systemd-boot being installed. + + + + + Print the path to the block device node backing the root file system of the local + OS. This prints a path such as /dev/nvme0n1p5. If the root file system is backed + by dm-crypt/LUKS or dm-verity the underlying block device is returned. If the root file system is + backed by multiple block devices (as supported by btrfs) the operation will fail. If the switch is + specified twice (i.e. ) and the discovered block device is a partition device the + "whole" block device it belongs to is determined and printed + (e.g. /dev/nvme0n1). If the root file system is tmpfs (or a + similar in-memory file system), the block device backing /usr/ is returned if + applicable. If the root file system is a network file system (e.g. NFS, CIFS) the operation will + fail. + + Do not touch the firmware's boot loader list stored in EFI variables. @@ -462,7 +478,9 @@ Exit status - On success, 0 is returned, a non-zero failure code otherwise. + On success, 0 is returned, a non-zero failure code otherwise. bootctl + --print-root-device returns exit status 80 in case the root file system is not backed by single + block device, and other non-zero exit statusses on other errors. diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 53794d1b7a..d8de09cab5 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -2,6 +2,7 @@ #include +#include "blockdev-util.h" #include "bootctl.h" #include "bootctl-install.h" #include "bootctl-random-seed.h" @@ -11,6 +12,7 @@ #include "bootctl-systemd-efi-options.h" #include "bootctl-uki.h" #include "build.h" +#include "devnum-util.h" #include "dissect-image.h" #include "escape.h" #include "find-esp.h" @@ -33,6 +35,7 @@ char *arg_esp_path = NULL; char *arg_xbootldr_path = NULL; bool arg_print_esp_path = false; bool arg_print_dollar_boot_path = false; +unsigned arg_print_root_device = 0; bool arg_touch_variables = true; PagerFlags arg_pager_flags = 0; bool arg_graceful = false; @@ -167,8 +170,10 @@ static int help(int argc, char *argv[], void *userdata) { " --image=PATH Operate on disk image as filesystem root\n" " --install-source=auto|image|host\n" " Where to pick files when using --root=/--image=\n" - " -p --print-esp-path Print path to the EFI System Partition\n" - " -x --print-boot-path Print path to the $BOOT partition\n" + " -p --print-esp-path Print path to the EFI System Partition mount point\n" + " -x --print-boot-path Print path to the $BOOT partition mount point\n" + " -R --print-root-device\n" + " Print path to the root device node\n" " --no-variables Don't touch EFI variables\n" " --no-pager Do not pipe output into a pager\n" " --graceful Don't fail when the ESP cannot be found or EFI\n" @@ -227,6 +232,7 @@ static int parse_argv(int argc, char *argv[]) { { "print-esp-path", no_argument, NULL, 'p' }, { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ { "print-boot-path", no_argument, NULL, 'x' }, + { "print-root-device", no_argument, NULL, 'R' }, { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "graceful", no_argument, NULL, ARG_GRACEFUL }, @@ -247,7 +253,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpx", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hpxR", options, NULL)) >= 0) switch (c) { case 'h': @@ -295,19 +301,17 @@ static int parse_argv(int argc, char *argv[]) { break; case 'p': - if (arg_print_dollar_boot_path) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--print-boot-path/-x cannot be combined with --print-esp-path/-p"); arg_print_esp_path = true; break; case 'x': - if (arg_print_esp_path) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--print-boot-path/-x cannot be combined with --print-esp-path/-p"); arg_print_dollar_boot_path = true; break; + case 'R': + arg_print_root_device ++; + break; + case ARG_NO_VARIABLES: arg_touch_variables = false; break; @@ -398,6 +402,10 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R cannot be combined."); + if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -458,6 +466,32 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_print_root_device > 0) { + _cleanup_free_ char *path = NULL; + dev_t devno; + + r = blockdev_get_root(LOG_ERR, &devno); + if (r < 0) + return r; + if (r == 0) { + log_error("Root file system not backed by a (single) whole block device."); + return 80; /* some recognizable error code */ + } + + if (arg_print_root_device > 1) { + r = block_get_whole_disk(devno, &devno); + if (r < 0) + log_debug_errno(r, "Unable to find whole block device for root block device, ignoring: %m"); + } + + r = device_path_make_canonical(S_IFBLK, devno, &path); + if (r < 0) + return log_oom(); + + puts(path); + return EXIT_SUCCESS; + } + /* Open up and mount the image */ if (arg_image) { assert(!arg_root); diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h index 311b954c2c..9012bf932b 100644 --- a/src/boot/bootctl.h +++ b/src/boot/bootctl.h @@ -24,6 +24,7 @@ extern char *arg_esp_path; extern char *arg_xbootldr_path; extern bool arg_print_esp_path; extern bool arg_print_dollar_boot_path; +extern unsigned arg_print_root_device; extern bool arg_touch_variables; extern PagerFlags arg_pager_flags; extern bool arg_graceful; diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 21b9284f8a..34f67b7fcb 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -773,40 +773,15 @@ static int enumerate_partitions(dev_t devnum) { } static int add_mounts(void) { - _cleanup_free_ char *p = NULL; - int r; dev_t devno; + int r; - /* If the root mount has been replaced by some form of volatile file system (overlayfs), the - * original root block device node is symlinked in /run/systemd/volatile-root. Let's read that - * here. */ - r = readlink_malloc("/run/systemd/volatile-root", &p); - if (r == -ENOENT) { /* volatile-root not found */ - r = get_block_device_harder("/", &devno); - if (r == -EUCLEAN) - return btrfs_log_dev_root(LOG_ERR, r, "root file system"); - if (r < 0) - return log_error_errno(r, "Failed to determine block device of root file system: %m"); - if (r == 0) { /* Not backed by a single block device. (Could be NFS or so, or could be multi-device RAID or so) */ - r = get_block_device_harder("/usr", &devno); - if (r == -EUCLEAN) - return btrfs_log_dev_root(LOG_ERR, r, "/usr"); - if (r < 0) - return log_error_errno(r, "Failed to determine block device of /usr/ file system: %m"); - if (r == 0) { /* /usr/ not backed by single block device, either. */ - log_debug("Neither root nor /usr/ file system are on a (single) block device."); - return 0; - } - } - } else if (r < 0) - return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m"); - else { - mode_t m; - r = device_path_parse_major_minor(p, &m, &devno); - if (r < 0) - return log_error_errno(r, "Failed to parse major/minor device node: %m"); - if (!S_ISBLK(m)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Volatile root device is of wrong type."); + r = blockdev_get_root(LOG_ERR, &devno); + if (r < 0) + return r; + if (r == 0) { + log_debug("Skipping automatic GPT dissection logic, root file system not backed by a (single) whole block device."); + return 0; } return enumerate_partitions(devno); diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index ee134146a1..3d03eedcf0 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -17,6 +17,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "missing_magic.h" #include "parse-util.h" @@ -777,3 +778,53 @@ int blockdev_get_sector_size(int fd, uint32_t *ret) { *ret = ssz; return 0; } + +int blockdev_get_root(int level, dev_t *ret) { + _cleanup_free_ char *p = NULL; + dev_t devno; + int r; + + /* Returns the device node backing the root file system. Traces through + * dm-crypt/dm-verity/... Returns > 0 and the devno of the device on success. If there's no block + * device (or multiple) returns 0 and a devno of 0. Failure otherwise. + * + * If the root mount has been replaced by some form of volatile file system (overlayfs), the original + * root block device node is symlinked in /run/systemd/volatile-root. Let's read that here. */ + r = readlink_malloc("/run/systemd/volatile-root", &p); + if (r == -ENOENT) { /* volatile-root not found */ + r = get_block_device_harder("/", &devno); + if (r == -EUCLEAN) + return btrfs_log_dev_root(level, r, "root file system"); + if (r < 0) + return log_full_errno(level, r, "Failed to determine block device of root file system: %m"); + if (r == 0) { /* Not backed by a single block device. (Could be NFS or so, or could be multi-device RAID or so) */ + r = get_block_device_harder("/usr", &devno); + if (r == -EUCLEAN) + return btrfs_log_dev_root(level, r, "/usr"); + if (r < 0) + return log_full_errno(level, r, "Failed to determine block device of /usr/ file system: %m"); + if (r == 0) { /* /usr/ not backed by single block device, either. */ + log_debug("Neither root nor /usr/ file system are on a (single) block device."); + + if (ret) + *ret = 0; + + return 0; + } + } + } else if (r < 0) + return log_full_errno(level, r, "Failed to read symlink /run/systemd/volatile-root: %m"); + else { + mode_t m; + r = device_path_parse_major_minor(p, &m, &devno); + if (r < 0) + return log_full_errno(level, r, "Failed to parse major/minor device node: %m"); + if (!S_ISBLK(m)) + return log_full_errno(level, SYNTHETIC_ERRNO(ENOTBLK), "Volatile root device is of wrong type."); + } + + if (ret) + *ret = devno; + + return 1; +} diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 5b27d23e8a..ea093c49a6 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -56,3 +56,5 @@ int block_device_has_partitions(sd_device *dev); int blockdev_reread_partition_table(sd_device *dev); int blockdev_get_sector_size(int fd, uint32_t *ret); + +int blockdev_get_root(int level, dev_t *ret); diff --git a/test/test-bootctl-json.sh b/test/test-bootctl-json.sh index 7a660a8ea7..fde5fbd1de 100755 --- a/test/test-bootctl-json.sh +++ b/test/test-bootctl-json.sh @@ -22,3 +22,20 @@ command -v jq >/dev/null || { "$bootctl" list --json=pretty | jq . >/dev/null "$bootctl" list --json=short | jq . >/dev/null + +# bootctl --print-root-device should either succeed or fail with exit status 80 +# (because not backed by a single block device), but not fail otherwise. +"$bootctl" -R || test "$?" -eq 80 +"$bootctl" -RR || test "$?" -eq 80 + +if "$bootctl" -R > /dev/null ; then + P=$("$bootctl" -R) + PP=$("$bootctl" -RR) + + echo "$P vs $PP" + test -b "$P" + test -b "$PP" + + # $P must be a prefix of $PP + [[ $P = $PP* ]] +fi