Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ impl Component for Bios {
.context("Failed to backup GRUB config")?;
}

crate::grubconfigs::install(&destdir, None, None, true)?;
crate::grubconfigs::install(&destdir, None, None, true, &[])?;

// Remove the real config if it is symlink and will not
// if /boot/grub2/grub.cfg is file
Expand Down
19 changes: 19 additions & 0 deletions src/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,30 @@ pub(crate) fn install(
Some(uuid) => {
let meta = get_static_config_meta()?;
state.static_configs = Some(meta);

// On multi-device setups (e.g. RAID 1 with an ESP per disk) we
// need every ESP to carry grub.cfg and bootuuid.cfg so the system
// can boot from any disk. Gather the additional ESP device paths
// now and pass them into grubconfigs::install so it writes to all
// of them.
let additional_esps: Vec<String> = if installed_efi_vendor.is_some() {
devices
.first()
.and_then(|dev| dev.find_colocated_esps().ok().flatten())
.unwrap_or_default()
.iter()
.map(|dev| dev.path())
.collect()
} else {
Vec::new()
};

crate::grubconfigs::install(
sysroot,
Some(&source_root_dir),
installed_efi_vendor.as_deref(),
uuid,
&additional_esps,
)?;
// On other architectures, assume that there's nothing to do.
}
Expand Down
2 changes: 1 addition & 1 deletion src/efi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ impl Component for Efi {
.context("Failed to backup GRUB config")?;
}

grubconfigs::install(&sysroot, None, Some(&vendor), true)?;
grubconfigs::install(&sysroot, None, Some(&vendor), true, &[])?;
// Synchronize the filesystem containing /boot/efi/EFI/{vendor} to disk.
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;

Expand Down
88 changes: 73 additions & 15 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ const GRUB_FILES: [&str; 3] = ["bootuuid.cfg", GRUBCONFIG, GRUBENV];
pub(crate) const GRUBCONFIG_FILE_MODE: u32 = 0o600;

/// Install the static GRUB config files.
///
/// When `additional_esp_devices` is non-empty, grub.cfg and bootuuid.cfg are
/// also written to every listed ESP partition (each is mounted temporarily,
/// written to, then unmounted).
#[context("Installing static GRUB configs")]
pub(crate) fn install(
target_root: &openat::Dir,
src_root: Option<&openat::Dir>,
installed_efi_vendor: Option<&str>,
write_uuid: bool,
additional_esp_devices: &[String],
) -> Result<()> {
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
let boot_is_mount = {
Expand Down Expand Up @@ -117,32 +122,85 @@ pub(crate) fn install(

if let Some(vendordir) = installed_efi_vendor {
log::debug!("vendordir={:?}", &vendordir);
let vendor = PathBuf::from(vendordir);
let target = &vendor.join("grub.cfg");
let dest_efidir = target_root
.sub_dir_optional("boot/efi/EFI")
.context("Opening /boot/efi/EFI")?;
if let Some(efidir) = dest_efidir {
configdir
.copy_file_at("grub-static-efi.cfg", &efidir, target)
.context("Copying static EFI")?;
println!("Installed: {target:?}");
if let Some(uuid_path) = uuid_path {
let target = &vendor.join(uuid_path);
grub2dir
.copy_file_at(uuid_path, &efidir, target)
.context("Writing bootuuid.cfg to efi dir")?;
println!("Installed: {target:?}");
}
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
write_esp_configs(&configdir, &grub2dir, &efidir, vendordir, uuid_path)?;
} else {
let target = PathBuf::from(vendordir).join("grub.cfg");
println!("Could not find /boot/efi/EFI when installing {target:?}");
}

// Write grub configs to every additional ESP so that any disk can
// boot independently in multi-device setups.
if !additional_esp_devices.is_empty() {
let tmpdir = tempfile::tempdir().context("Creating temporary mount point")?;
let tmpmnt = tmpdir.path();

for esp_path in additional_esp_devices {
log::info!("Installing GRUB configs to ESP {esp_path}");

std::process::Command::new("mount")
.arg(esp_path)
.arg(tmpmnt)
.run_inherited()
.with_context(|| format!("Mounting ESP {esp_path}"))?;

let result = (|| -> Result<()> {
let efi_vendor = tmpmnt.join("EFI").join(vendordir);
std::fs::create_dir_all(&efi_vendor)
.with_context(|| format!("Creating {:?}", efi_vendor))?;

let efidir = openat::Dir::open(&tmpmnt.join("EFI"))
.context("Opening EFI directory on ESP")?;

write_esp_configs(&configdir, &grub2dir, &efidir, vendordir, uuid_path)
})();

// Always unmount, even on error
std::process::Command::new("umount")
.arg(tmpmnt)
.run_inherited()
.with_context(|| format!("Unmounting ESP {esp_path}"))?;

result?;
}
}
}

Ok(())
}

/// Write grub.cfg and optionally bootuuid.cfg to an ESP's EFI directory.
///
/// `efidir` should be the `EFI/` directory on the mounted ESP.
/// `configdir` is the static grub config source (e.g. /usr/lib/bootupd/grub2-static/).
/// `grub2dir` is the boot partition's grub2 directory (contains bootuuid.cfg).
fn write_esp_configs(
configdir: &openat::Dir,
grub2dir: &openat::Dir,
efidir: &openat::Dir,
vendordir: &str,
uuid_path: Option<&str>,
) -> Result<()> {
let vendor = PathBuf::from(vendordir);
let target = &vendor.join("grub.cfg");
configdir
.copy_file_at("grub-static-efi.cfg", efidir, target)
.context("Copying static EFI")?;
println!("Installed: {target:?}");
if let Some(uuid_path) = uuid_path {
let target = &vendor.join(uuid_path);
grub2dir
.copy_file_at(uuid_path, efidir, target)
.context("Writing bootuuid.cfg to efi dir")?;
println!("Installed: {target:?}");
}
fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
Ok(())
}

#[context("Create file boot/grub2/grubenv")]
fn write_grubenv(grubdir: &openat::Dir) -> Result<()> {
if grubdir.exists(GRUBENV)? {
Expand Down Expand Up @@ -203,7 +261,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, None, Some("fedora"), false).unwrap();
install(&td, None, Some("fedora"), false, &[]).unwrap();

assert!(td.exists("boot/grub2/grub.cfg")?);
assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?);
Expand Down
Loading