Skip to content

Commit

Permalink
install: Support gathering more info for host root (including LVM)
Browse files Browse the repository at this point in the history
Teach `install to-existing-root` how to gather kernel arguments
and information we need from `/proc/cmdline` (such as `rd.lvm.lv`).
In this case too, we need to adjust the args to use `-v /dev:/dev`
because we need the stuff udev generates from the real host root.

With these things together, I can do a bootc alongside install
targeting a Fedora Server instance.

Closes: #175
Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Mar 9, 2024
1 parent db93ac8 commit 5b48d07
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/src/bootc-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ support the root storage setup already initialized.
The core command should look like this (root/elevated permission required):

```bash
podman run --rm --privileged -v /var/lib/containers:/var/lib/containers -v /:/target \
podman run --rm --privileged -v /dev:/dev -v /var/lib/containers:/var/lib/containers -v /:/target \
--pid=host --security-opt label=type:unconfined_t \
<image> \
bootc install to-existing-root
Expand Down
4 changes: 3 additions & 1 deletion lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
#[cfg(feature = "install")]
Opt::Install(opts) => match opts {
InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await,
InstallOpts::ToFilesystem(opts) => crate::install::install_to_filesystem(opts).await,
InstallOpts::ToFilesystem(opts) => {
crate::install::install_to_filesystem(opts, false).await
}
InstallOpts::ToExistingRoot(opts) => {
crate::install::install_to_existing_root(opts).await
}
Expand Down
107 changes: 94 additions & 13 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use serde::{Deserialize, Serialize};

use self::baseline::InstallBlockDeviceOpts;
use crate::containerenv::ContainerExecutionInfo;
use crate::mount::Filesystem;
use crate::task::Task;
use crate::utils::sigpolicy_from_opts;

Expand Down Expand Up @@ -1223,9 +1224,46 @@ fn clean_boot_directories(rootfs: &Dir) -> Result<()> {
Ok(())
}

struct RootMountInfo {
mount_spec: String,
kargs: Vec<String>,
}

/// Discover how to mount the root filesystem, using existing kernel arguments and information
/// about the root mount.
fn find_root_args_to_inherit(cmdline: &[&str], root_info: &Filesystem) -> Result<RootMountInfo> {
let cmdline = || cmdline.iter().map(|&s| s);
let root = crate::kernel::find_first_cmdline_arg(cmdline(), "root");
// If we have a root= karg, then use that
let (mount_spec, kargs) = if let Some(root) = root {
let rootflags = cmdline().find(|arg| arg.starts_with(crate::kernel::ROOTFLAGS));
let inherit_kargs =
cmdline().filter(|arg| arg.starts_with(crate::kernel::INITRD_ARG_PREFIX));
(
root.to_owned(),
rootflags
.into_iter()
.chain(inherit_kargs)
.map(ToOwned::to_owned)
.collect(),
)
} else {
let uuid = root_info
.uuid
.as_deref()
.ok_or_else(|| anyhow!("No filesystem uuid found in target root"))?;
(format!("UUID={uuid}"), Vec::new())
};

Ok(RootMountInfo { mount_spec, kargs })
}

/// Implementation of the `bootc install to-filsystem` CLI command.
#[context("Installing to filesystem")]
pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Result<()> {
pub(crate) async fn install_to_filesystem(
opts: InstallToFilesystemOpts,
targeting_host_root: bool,
) -> Result<()> {
let fsopts = opts.filesystem_opts;
let root_path = &fsopts.root_path;

Expand Down Expand Up @@ -1266,25 +1304,39 @@ pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Resu

// We support overriding the mount specification for root (i.e. LABEL vs UUID versus
// raw paths).
let (root_mount_spec, root_extra) = if let Some(s) = fsopts.root_mount_spec {
(s, None)
let root_info = if let Some(s) = fsopts.root_mount_spec {
RootMountInfo {
mount_spec: s.to_string(),
kargs: Vec::new(),
}
} else if targeting_host_root {
// In the to-existing-root case, look at /proc/cmdline
let cmdline = crate::kernel::parse_cmdline()?;
let cmdline = cmdline.iter().map(|s| s.as_str()).collect::<Vec<_>>();
find_root_args_to_inherit(&cmdline, &inspect)?
} else {
// Otherwise, gather metadata from the provided root and use its provided UUID as a
// default root= karg.
let uuid = inspect
.uuid
.as_deref()
.ok_or_else(|| anyhow!("No filesystem uuid found in target root"))?;
let uuid = format!("UUID={uuid}");
tracing::debug!("root {uuid}");
let opts = match inspect.fstype.as_str() {
let kargs = match inspect.fstype.as_str() {
"btrfs" => {
let subvol = crate::utils::find_mount_option(&inspect.options, "subvol");
subvol.map(|vol| format!("rootflags=subvol={vol}"))
subvol
.map(|vol| format!("rootflags=subvol={vol}"))
.into_iter()
.collect::<Vec<_>>()
}
_ => None,
_ => Vec::new(),
};
(uuid, opts)
RootMountInfo {
mount_spec: format!("UUID={uuid}"),
kargs,
}
};
tracing::debug!("Root mount spec: {root_mount_spec}");
tracing::debug!("Root mount: {} {:?}", root_info.mount_spec, root_info.kargs);

let boot_is_mount = {
let root_dev = rootfs_fd.dir_metadata()?.dev();
Expand Down Expand Up @@ -1333,7 +1385,7 @@ pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Resu
};
tracing::debug!("Backing device: {backing_device}");

let rootarg = format!("root={root_mount_spec}");
let rootarg = format!("root={}", root_info.mount_spec);
let mut boot = if let Some(spec) = fsopts.boot_mount_spec {
Some(MountSpec::new(&spec, "/boot"))
} else {
Expand All @@ -1351,7 +1403,7 @@ pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Resu
let bootarg = boot.as_ref().map(|boot| format!("boot={}", &boot.source));
let kargs = [rootarg]
.into_iter()
.chain(root_extra)
.chain(root_info.kargs)
.chain([RW_KARG.to_string()])
.chain(bootarg)
.collect::<Vec<_>>();
Expand Down Expand Up @@ -1390,7 +1442,7 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
config_opts: opts.config_opts,
};

install_to_filesystem(opts).await
install_to_filesystem(opts, true).await
}

#[test]
Expand All @@ -1411,3 +1463,32 @@ fn test_mountspec() {
ms.push_option("relatime");
assert_eq!(ms.to_fstab(), "/dev/vda4 /boot auto ro,relatime 0 0");
}

#[test]
fn test_gather_root_args() {
// A basic filesystem using a UUID
let inspect = Filesystem {
source: "/dev/vda4".into(),
fstype: "xfs".into(),
options: "rw".into(),
uuid: Some("965eb3c7-5a3f-470d-aaa2-1bcf04334bc6".into()),
};
let r = find_root_args_to_inherit(&[], &inspect).unwrap();
assert_eq!(r.mount_spec, "UUID=965eb3c7-5a3f-470d-aaa2-1bcf04334bc6");

// In this case we take the root= from the kernel cmdline
let r = find_root_args_to_inherit(
&[
"root=/dev/mapper/root",
"rw",
"someother=karg",
"rd.lvm.lv=root",
"systemd.debug=1",
],
&inspect,
)
.unwrap();
assert_eq!(r.mount_spec, "/dev/mapper/root");
assert_eq!(r.kargs.len(), 1);
assert_eq!(r.kargs[0], "rd.lvm.lv=root");
}
46 changes: 46 additions & 0 deletions lib/src/kernel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use anyhow::Result;
use fn_error_context::context;

/// This is used by dracut.
pub(crate) const INITRD_ARG_PREFIX: &str = "rd.";
/// The kernel argument for configuring the rootfs flags.
pub(crate) const ROOTFLAGS: &str = "rootflags=";

/// Parse the kernel command line. This is strictly
/// speaking not a correct parser, as the Linux kernel
/// supports quotes. However, we don't yet need that here.
///
/// See systemd's code for one userspace parser.
#[context("Reading /proc/cmdline")]
pub(crate) fn parse_cmdline() -> Result<Vec<String>> {
let cmdline = std::fs::read_to_string("/proc/cmdline")?;
let r = cmdline
.split_ascii_whitespace()
.map(ToOwned::to_owned)
.collect();
Ok(r)
}

/// Return the value for the string in the vector which has the form target_key=value
pub(crate) fn find_first_cmdline_arg<'a>(
args: impl Iterator<Item = &'a str>,
target_key: &str,
) -> Option<&'a str> {
args.filter_map(|arg| {
if let Some((k, v)) = arg.split_once('=') {
if target_key == k {
return Some(v);
}
}
None
})
.next()
}

#[test]
fn test_find_first() {
let kargs = &["foo=bar", "root=/dev/vda", "blah", "root=/dev/other"];
let kargs = || kargs.iter().map(|&s| s);
assert_eq!(find_first_cmdline_arg(kargs(), "root"), Some("/dev/vda"));
assert_eq!(find_first_cmdline_arg(kargs(), "nonexistent"), None);
}
2 changes: 2 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ mod containerenv;
mod install;
mod k8sapitypes;
#[cfg(feature = "install")]
mod kernel;
#[cfg(feature = "install")]
pub(crate) mod mount;
#[cfg(feature = "install")]
mod podman;
Expand Down

0 comments on commit 5b48d07

Please sign in to comment.