bootctl: add new --print-root-device option

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.
This commit is contained in:
Lennart Poettering 2023-02-20 17:25:14 +01:00
parent b469b969f3
commit c56be2c294
7 changed files with 140 additions and 42 deletions

View file

@ -341,6 +341,22 @@
<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>
@ -462,7 +478,9 @@
<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>

View 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);

View 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;

View 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);

View 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;
}

View 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);

View file

@ -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