180 lines
6.2 KiB
Rust
Executable file
180 lines
6.2 KiB
Rust
Executable file
use std::fmt::Write;
|
|
use std::os::unix::io::AsRawFd;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use bootc_internal_utils::CommandRunExt;
|
|
use fn_error_context::context;
|
|
use openat_ext::OpenatDirExt;
|
|
|
|
use crate::freezethaw::fsfreeze_thaw_cycle;
|
|
|
|
/// The subdirectory of /boot we use
|
|
const GRUB2DIR: &str = "grub2";
|
|
const CONFIGDIR: &str = "/usr/lib/bootupd/grub2-static";
|
|
const DROPINDIR: &str = "configs.d";
|
|
// The related grub files
|
|
const GRUBENV: &str = "grubenv";
|
|
pub(crate) const GRUBCONFIG: &str = "grub.cfg";
|
|
pub(crate) const GRUBCONFIG_BACKUP: &str = "grub.cfg.backup";
|
|
// File mode for /boot/grub2/grub.config
|
|
// https://github.com/coreos/bootupd/issues/952
|
|
const GRUBCONFIG_FILE_MODE: u32 = 0o600;
|
|
|
|
/// Install the static GRUB config files.
|
|
#[context("Installing static GRUB configs")]
|
|
pub(crate) fn install(
|
|
target_root: &openat::Dir,
|
|
installed_efi_vendor: Option<&str>,
|
|
write_uuid: bool,
|
|
) -> Result<()> {
|
|
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
|
|
let boot_is_mount = {
|
|
let root_dev = target_root.self_metadata()?.stat().st_dev;
|
|
let boot_dev = bootdir.self_metadata()?.stat().st_dev;
|
|
log::debug!("root_dev={root_dev} boot_dev={boot_dev}");
|
|
root_dev != boot_dev
|
|
};
|
|
|
|
if !bootdir.exists(GRUB2DIR)? {
|
|
bootdir.create_dir(GRUB2DIR, 0o700)?;
|
|
}
|
|
|
|
let mut config = String::from("# Generated by bootupd / do not edit\n\n");
|
|
|
|
let pre = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-pre.cfg"))?;
|
|
config.push_str(pre.as_str());
|
|
|
|
let dropindir = openat::Dir::open(&Path::new(CONFIGDIR).join(DROPINDIR))?;
|
|
// Sort the files for reproducibility
|
|
let mut entries = dropindir
|
|
.list_dir(".")?
|
|
.map(|e| e.map_err(anyhow::Error::msg))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
|
|
for ent in entries {
|
|
let name = ent.file_name();
|
|
let name = name
|
|
.to_str()
|
|
.ok_or_else(|| anyhow!("Invalid UTF-8: {name:?}"))?;
|
|
if !name.ends_with(".cfg") {
|
|
log::debug!("Ignoring {name}");
|
|
continue;
|
|
}
|
|
writeln!(config, "\n### BEGIN {name} ###")?;
|
|
let dropin = std::fs::read_to_string(Path::new(CONFIGDIR).join(DROPINDIR).join(name))?;
|
|
config.push_str(dropin.as_str());
|
|
writeln!(config, "### END {name} ###")?;
|
|
println!("Added {name}");
|
|
}
|
|
|
|
let grub2dir = bootdir.sub_dir(GRUB2DIR)?;
|
|
grub2dir
|
|
.write_file_contents("grub.cfg", GRUBCONFIG_FILE_MODE, config.as_bytes())
|
|
.context("Copying grub-static.cfg")?;
|
|
println!("Installed: grub.cfg");
|
|
|
|
write_grubenv(&bootdir).context("Create grubenv")?;
|
|
|
|
let uuid_path = if write_uuid {
|
|
let target_fs = if boot_is_mount { bootdir } else { target_root };
|
|
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs, ".")?;
|
|
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 = "bootuuid.cfg";
|
|
grub2dir
|
|
.write_file_contents(uuid_path, 0o644, grub2_uuid_contents)
|
|
.context("Writing bootuuid.cfg")?;
|
|
println!("Installed: bootuuid.cfg");
|
|
Some(uuid_path)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
fsfreeze_thaw_cycle(grub2dir.open_file(".")?)?;
|
|
|
|
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 {
|
|
efidir
|
|
.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 {
|
|
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(".")?)?;
|
|
} else {
|
|
println!("Could not find /boot/efi/EFI when installing {target:?}");
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[context("Create file boot/grub2/grubenv")]
|
|
fn write_grubenv(bootdir: &openat::Dir) -> Result<()> {
|
|
let grubdir = &bootdir.sub_dir(GRUB2DIR).context("Opening boot/grub2")?;
|
|
|
|
if grubdir.exists(GRUBENV)? {
|
|
return Ok(());
|
|
}
|
|
let editenv = Path::new("/usr/bin/grub2-editenv");
|
|
if !editenv.exists() {
|
|
anyhow::bail!("Failed to find {:?}", editenv);
|
|
}
|
|
|
|
std::process::Command::new(editenv)
|
|
.args([GRUBENV, "create"])
|
|
.current_dir(format!("/proc/self/fd/{}", grubdir.as_raw_fd()))
|
|
.run_with_cmd_context()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_install() -> Result<()> {
|
|
env_logger::init();
|
|
let td = tempfile::tempdir()?;
|
|
let tdp = td.path();
|
|
let td = openat::Dir::open(tdp)?;
|
|
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, Some("fedora"), false).unwrap();
|
|
|
|
assert!(td.exists("boot/grub2/grub.cfg")?);
|
|
assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?);
|
|
Ok(())
|
|
}
|
|
#[test]
|
|
fn test_write_grubenv() -> Result<()> {
|
|
// Skip this test if grub2-editenv is not installed
|
|
let editenv = Path::new("/usr/bin/grub2-editenv");
|
|
if !editenv.try_exists()? {
|
|
return Ok(());
|
|
}
|
|
let td = tempfile::tempdir()?;
|
|
let tdp = td.path();
|
|
std::fs::create_dir_all(tdp.join("boot/grub2"))?;
|
|
let td = openat::Dir::open(&tdp.join("boot"))?;
|
|
write_grubenv(&td)?;
|
|
|
|
assert!(td.exists("grub2/grubenv")?);
|
|
Ok(())
|
|
}
|
|
}
|