diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index 6126cef9e..bb9edbf69 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -8,6 +8,8 @@ use fn_error_context::context; use bootc_blockdev::{Partition, PartitionTable}; use bootc_mount as mount; +use rustix::mount::UnmountFlags; +use rustix::path::Arg; use crate::bootc_composefs::boot::mount_esp; use crate::{discoverable_partition_specification, utils}; @@ -50,22 +52,70 @@ pub(crate) fn install_via_bootupd( // bootc defaults to only targeting the platform boot method. let bootupd_opts = (!configopts.generic_image).then_some(["--update-firmware", "--auto"]); - let abs_deployment_path = deployment_path.map(|v| rootfs.join(v)); - let src_root_arg = if let Some(p) = abs_deployment_path.as_deref() { - vec!["--src-root", p.as_str()] + let abs_deployment_path = deployment_path.map(|deploy| rootfs.join(deploy)); + // When not running inside the target container (through `--src-imgref`) we chroot + // into the deployment before running bootupd. This makes sure we use binaries + // from the target image rather than the buildroot + // In some cases (e.g. --write-uuid), bootupd needs to find the underlying device + // for /boot. But since we don't control where the destination rootfs is mounted + // let's bind mount it to a temp mountpoint under /run + // so it gets carried over in the chroot. + + let rootfs_mount = if rootfs.starts_with("/run") { + rootfs.to_path_buf().into_std_path_buf() + } else { + let rootfs_mount = tempfile::tempdir_in("/run")?.keep(); + rustix::mount::mount_bind_recursive(rootfs.as_std_path(), &rootfs_mount)?; + rootfs_mount + }; + let bind_mount_dirs = ["/dev", "/run", "/proc", "/sys"]; + let chroot_args = if let Some(target_root) = abs_deployment_path.as_deref() { + tracing::debug!("Setting up bind-mounts before chrooting to the target deployment"); + for src in bind_mount_dirs { + let dest = target_root + // joining an absolute path + // makes it replace self, so we strip the prefix + .join_os(src.strip_prefix("/").unwrap()); + tracing::debug!("bind mounting {}", dest.display()); + rustix::mount::mount_bind_recursive(src, dest)?; + } + // Append the `bootupctl` command, it will be passed as + // an argument to chroot + vec![target_root.as_str(), "bootupctl"] } else { vec![] }; + let devpath = device.path(); println!("Installing bootloader via bootupd"); - Command::new("bootupctl") + let mut bootupctl = if abs_deployment_path.is_some() { + Command::new("chroot") + } else { + Command::new("bootupctl") + }; + let install_result = bootupctl + .args(chroot_args) .args(["backend", "install", "--write-uuid"]) .args(verbose) .args(bootupd_opts.iter().copied().flatten()) - .args(src_root_arg) - .args(["--device", devpath.as_str(), rootfs.as_str()]) + .args(["--device", devpath.as_str(), rootfs_mount.as_str()?]) .log_debug() - .run_inherited_with_cmd_context() + .run_inherited_with_cmd_context(); + + // Clean up the mounts after ourselves + if let Some(target_root) = abs_deployment_path { + for dir in bind_mount_dirs { + let mount = target_root + .join(dir.strip_prefix("/").unwrap()) + .into_std_path_buf(); + if let Err(e) = rustix::mount::unmount(&mount, UnmountFlags::DETACH) { + // let's not propagate the error up because in some cases we can't unmount + // e.g. when running `to-existing-root` + tracing::warn!("Error unmounting {}: {e}", mount.display()); + } + } + } + install_result } #[context("Installing bootloader")] diff --git a/crates/tests-integration/src/install.rs b/crates/tests-integration/src/install.rs index 8487c0354..63a9a154a 100644 --- a/crates/tests-integration/src/install.rs +++ b/crates/tests-integration/src/install.rs @@ -9,7 +9,15 @@ use fn_error_context::context; use libtest_mimic::Trial; use xshell::{cmd, Shell}; -pub(crate) const BASE_ARGS: &[&str] = &["podman", "run", "--rm", "--privileged", "--pid=host"]; +pub(crate) const BASE_ARGS: &[&str] = &[ + "podman", + "run", + "--rm", + "--privileged", + "--pid=host", + "--env", + "BOOTC_BOOTLOADER_DEBUG=true", +]; // Arbitrary const NON_DEFAULT_STATEROOT: &str = "foo";