]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: add new --print-root-device option
authorLennart Poettering <lennart@poettering.net>
Mon, 20 Feb 2023 16:25:14 +0000 (17:25 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Feb 2023 17:19:38 +0000 (18:19 +0100)
We already have this nice code in system that determines the block
device backing the root file system, but it's only used internally in
systemd-gpt-generator. Let's make this more accessible and expose it
directly in bootctl.

It doesn't fit immediately into the topic of bootctl, but I think it's
close enough and behaves very similar to the existing "bootctl
--print-boot-path" and "--print-esp-path" tools.

If --print-root-device (or -R) is specified once, will show the block device
backing the root fs, and if specified twice (probably easier: -RR) it
will show the whole block device that block device belongs to in case it
is a partition block device.

Suggested use:

        # cfdisk `bootctl -RR`

To get access to the partition table, behind the OS install, for
whatever it might be.

man/bootctl.xml
src/boot/bootctl.c
src/boot/bootctl.h
src/gpt-auto-generator/gpt-auto-generator.c
src/shared/blockdev-util.c
src/shared/blockdev-util.h
test/test-bootctl-json.sh

index 42c4b9a8e67c64905459f30936bb6debdf46b0de..4fec552ca89ac5a0481d7ae1c2423f5b65fc62a6 100644 (file)
         <command>systemd-boot</command> being installed.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>-R</option></term>
+        <term><option>--print-root-device</option></term>
+
+        <listitem><para>Print the path to the block device node backing the root file system of the local
+        OS. This prints a path such as <filename>/dev/nvme0n1p5</filename>. 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. <option>-RR</option>) and the discovered block device is a partition device the
+        "whole" block device it belongs to is determined and printed
+        (e.g. <filename>/dev/nvme0n1</filename>). If the root file system is <literal>tmpfs</literal> (or a
+        similar in-memory file system), the block device backing <filename>/usr/</filename> is returned if
+        applicable. If the root file system is a network file system (e.g. NFS, CIFS) the operation will
+        fail.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--no-variables</option></term>
         <listitem><para>Do not touch the firmware's boot loader list stored in EFI variables.</para></listitem>
 
   <refsect1>
     <title>Exit status</title>
-    <para>On success, 0 is returned, a non-zero failure code otherwise.</para>
+    <para>On success, 0 is returned, a non-zero failure code otherwise. <command>bootctl
+    --print-root-device</command> 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.</para>
   </refsect1>
 
   <refsect1>
index 53794d1b7a1851a73f17a6f9f1e3ade5211f6904..d8de09cab5470b2b7cd561e0d7688637c23b018c 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <getopt.h>
 
+#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);
index 311b954c2c8b3fdec90e1589c2e1bf591b03eb16..9012bf932bbde6bb6295b6dcb96986127f86478c 100644 (file)
@@ -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;
index 21b9284f8a729d18386091784435681f711ac022..34f67b7fcbbb293932262c8d5e58285f99527c0d 100644 (file)
@@ -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);
index ee134146a152695e21407b0556dbac09c71d61d7..3d03eedcf0316865a6267ce1cec7c7a6b4525b39 100644 (file)
@@ -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;
+}
index 5b27d23e8ae77230c2ac7cb8ca5153fa1e8c8c78..ea093c49a6b2d92e7489c1439f83384e68b9adb6 100644 (file)
@@ -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);
index 7a660a8ea76060e0c8069eccc8161a35bc1c262b..fde5fbd1de175d6017cf91e33b8448ad340bd7d8 100755 (executable)
@@ -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