From d40ebdcb0074f863f0c02c9bc24219e50207761b Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 10 Nov 2023 10:35:24 -0500 Subject: [PATCH] Add support for root=boot (with EFI) and writing UUID file In FCOS we never tried to support root=boot, but for bootupd to do alongside installs in the general case we have to. There were two things to fix here: - Tweak the EFI "trampoline" to check for both $prefix/grub.cfg and $prefix/boot/grub.cfg - Add support for writing the boot UUID into both places (This avoids higher level tools like bootupd needing to know about how to find the EFI vendor dir) This more fully replicates the logic in coreos-installer. --- src/bootupd.rs | 37 ++++++++++++++++++++++------- src/cli/bootupd.rs | 16 +++++++++++-- src/filesystem.rs | 44 +++++++++++++++++++++++++++++++++++ src/grub2/grub-static-efi.cfg | 6 ++++- src/grubconfigs.rs | 26 +++++++++++++++++++-- src/main.rs | 1 + 6 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/filesystem.rs diff --git a/src/bootupd.rs b/src/bootupd.rs index 560f38bf..4e41ee37 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -26,11 +26,27 @@ pub(crate) enum ClientRequest { Status, } +pub(crate) enum ConfigMode { + None, + Static, + WithUUID, +} + +impl ConfigMode { + pub(crate) fn enabled_with_uuid(&self) -> Option { + match self { + ConfigMode::None => None, + ConfigMode::Static => Some(false), + ConfigMode::WithUUID => Some(true), + } + } +} + pub(crate) fn install( source_root: &str, dest_root: &str, device: Option<&str>, - with_static_configs: bool, + configs: ConfigMode, target_components: Option<&[String]>, auto_components: bool, ) -> Result<()> { @@ -89,14 +105,17 @@ pub(crate) fn install( } let sysroot = &openat::Dir::open(dest_root)?; - if with_static_configs { - #[cfg(any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "powerpc64" - ))] - crate::grubconfigs::install(sysroot, installed_efi)?; - // On other architectures, assume that there's nothing to do. + match configs.enabled_with_uuid() { + Some(uuid) => { + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64" + ))] + crate::grubconfigs::install(sysroot, installed_efi, uuid)?; + // On other architectures, assume that there's nothing to do. + } + None => {} } // Unmount the ESP, etc. diff --git a/src/cli/bootupd.rs b/src/cli/bootupd.rs index b2cfd598..8647cb39 100644 --- a/src/cli/bootupd.rs +++ b/src/cli/bootupd.rs @@ -1,4 +1,4 @@ -use crate::bootupd; +use crate::bootupd::{self, ConfigMode}; use anyhow::{Context, Result}; use clap::Parser; use log::LevelFilter; @@ -56,6 +56,11 @@ pub struct InstallOpts { #[clap(long)] with_static_configs: bool, + /// Implies `--with-static-configs`. When present, this also writes a + /// file with the UUID of the target filesystems. + #[clap(long)] + write_uuid: bool, + #[clap(long = "component", conflicts_with = "auto")] /// Only install these components components: Option>, @@ -97,11 +102,18 @@ impl DCommand { /// Runner for `install` verb. pub(crate) fn run_install(opts: InstallOpts) -> Result<()> { + let configmode = if opts.write_uuid { + ConfigMode::WithUUID + } else if opts.with_static_configs { + ConfigMode::Static + } else { + ConfigMode::None + }; bootupd::install( &opts.src_root, &opts.dest_root, opts.device.as_deref(), - opts.with_static_configs, + configmode, opts.components.as_deref(), opts.auto, ) diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 00000000..f09ceb34 --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,44 @@ +use std::os::fd::AsRawFd; +use std::os::unix::process::CommandExt; +use std::process::Command; + +use anyhow::{Context, Result}; +use fn_error_context::context; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +#[allow(dead_code)] +pub(crate) struct Filesystem { + pub(crate) source: String, + pub(crate) fstype: String, + pub(crate) options: String, + pub(crate) uuid: Option, +} + +#[derive(Deserialize, Debug)] +pub(crate) struct Findmnt { + pub(crate) filesystems: Vec, +} + +#[context("Inspecting filesystem {path:?}")] +pub(crate) fn inspect_filesystem(root: &openat::Dir, path: &str) -> Result { + let rootfd = root.as_raw_fd(); + // SAFETY: This is unsafe just for the pre_exec, when we port to cap-std we can use cap-std-ext + let o = unsafe { + Command::new("findmnt") + .args(["-J", "-v", "--output-all", path]) + .pre_exec(move || nix::unistd::fchdir(rootfd).map_err(Into::into)) + .output()? + }; + let st = o.status; + if !st.success() { + anyhow::bail!("findmnt failed: {st:?}"); + } + let o: Findmnt = serde_json::from_reader(std::io::Cursor::new(&o.stdout)) + .context("Parsing findmnt output")?; + o.filesystems + .into_iter() + .next() + .ok_or_else(|| anyhow::anyhow!("findmnt returned no data")) +} diff --git a/src/grub2/grub-static-efi.cfg b/src/grub2/grub-static-efi.cfg index bf38ca90..4464828e 100644 --- a/src/grub2/grub-static-efi.cfg +++ b/src/grub2/grub-static-efi.cfg @@ -14,6 +14,10 @@ else fi fi set prefix=($prefix)/grub2 -configfile $prefix/grub.cfg +if [ -d grub2 ]; then + configfile $prefix/grub.cfg +else + configfile $prefix/boot/grub.cfg +fi boot diff --git a/src/grubconfigs.rs b/src/grubconfigs.rs index dcdf986c..0c527bf4 100644 --- a/src/grubconfigs.rs +++ b/src/grubconfigs.rs @@ -29,7 +29,7 @@ pub(crate) fn find_efi_vendordir(efidir: &openat::Dir) -> Result { /// Install the static GRUB config files. #[context("Installing static GRUB configs")] -pub(crate) fn install(target_root: &openat::Dir, efi: bool) -> Result<()> { +pub(crate) fn install(target_root: &openat::Dir, efi: bool, write_uuid: bool) -> Result<()> { let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?; let mut config = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-pre.cfg"))?; @@ -71,6 +71,21 @@ pub(crate) fn install(target_root: &openat::Dir, efi: bool) -> Result<()> { .context("Copying grub-static.cfg")?; println!("Installed: grub.cfg"); + let uuid_path = if write_uuid { + let bootfs_meta = crate::filesystem::inspect_filesystem(bootdir, ".")?; + let bootfs_uuid = bootfs_meta + .uuid + .ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?; + let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n"); + let uuid_path = format!("{GRUB2DIR}/bootuuid.cfg"); + bootdir + .write_file_contents(&uuid_path, 0o644, grub2_uuid_contents) + .context("Writing bootuuid.cfg")?; + Some(uuid_path) + } else { + None + }; + let efidir = efi .then(|| { target_root @@ -87,6 +102,13 @@ pub(crate) fn install(target_root: &openat::Dir, efi: bool) -> Result<()> { .copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target) .context("Copying static EFI")?; println!("Installed: {target:?}"); + if let Some(uuid_path) = uuid_path { + // SAFETY: we always have a filename + let filename = Path::new(&uuid_path).file_name().unwrap(); + bootdir + .copy_file_at(&uuid_path, efidir, filename) + .context("Writing bootuuid.cfg to efi dir")?; + } } Ok(()) @@ -106,7 +128,7 @@ mod tests { std::fs::create_dir_all(tdp.join("boot/grub2"))?; std::fs::create_dir_all(tdp.join("boot/efi/EFI/BOOT"))?; std::fs::create_dir_all(tdp.join("boot/efi/EFI/fedora"))?; - install(&td, true).unwrap(); + install(&td, true, false).unwrap(); assert!(td.exists("boot/grub2/grub.cfg")?); assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?); diff --git a/src/main.rs b/src/main.rs index 61b73ff8..133e9bbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod coreos; mod daemon; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] mod efi; +mod filesystem; mod filetree; #[cfg(any( target_arch = "x86_64",