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