Skip to content

Commit

Permalink
Add support for installing static grub configs
Browse files Browse the repository at this point in the history
Currently these are duplicated in osbuild and coreos-assembler.
We will aim to deduplicate them here.

Ideally we'd add support for "day 2" updates of these; I started
on a patch for that but it's sadly messy.

This is an incremental improvement.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Oct 17, 2023
1 parent d299ba4 commit fcf0dab
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ install: install-units
ln -s ../bootupd.socket "${DESTDIR}$(PREFIX)/lib/systemd/system/multi-user.target.wants"

install-grub-static:
install -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
install -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
install -d ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static/configs.d
22 changes: 18 additions & 4 deletions src/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ pub(crate) fn install(
source_root: &str,
dest_root: &str,
device: Option<&str>,
with_static_configs: bool,
target_components: Option<&[String]>,
) -> Result<()> {
// TODO: Change this to an Option<&str>; though this probably balloons into having
// DeviceComponent and FileBasedComponent
let device = device.unwrap_or("");
let source_root = openat::Dir::open(source_root)?;
let source_root = openat::Dir::open(source_root).context("Opening source root")?;
SavedState::ensure_not_present(dest_root)
.context("failed to install, invalid re-install attempted")?;

Expand All @@ -62,7 +63,8 @@ pub(crate) fn install(
}

let mut state = SavedState::default();
for component in target_components {
let mut installed_efi = false;
for &component in target_components.iter() {
// skip for BIOS if device is empty
if component.name() == "BIOS" && device.is_empty() {
println!(
Expand All @@ -77,10 +79,22 @@ pub(crate) fn install(
.with_context(|| format!("installing component {}", component.name()))?;
log::info!("Installed {} {}", component.name(), meta.meta.version);
state.installed.insert(component.name().into(), meta);
// Yes this is a hack...the Component thing just turns out to be too generic.
if component.name() == "EFI" {
installed_efi = true;
}
}
let sysroot = &openat::Dir::open(dest_root)?;

if with_static_configs {
crate::grubconfigs::install(sysroot, installed_efi)?;
}

let sysroot = openat::Dir::open(dest_root)?;
let mut state_guard = SavedState::unlocked(sysroot).context("failed to acquire write lock")?;
// Unmount the ESP, etc.
drop(target_components);

let mut state_guard =
SavedState::unlocked(sysroot.try_clone()?).context("failed to acquire write lock")?;
state_guard
.update_state(&state)
.context("failed to update state")?;
Expand Down
5 changes: 5 additions & 0 deletions src/cli/bootupd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub struct InstallOpts {
#[clap(long)]
device: Option<String>,

/// Enable installation of the built-in static config files
#[clap(long)]
with_static_configs: bool,

#[clap(long = "component")]
/// Only install these components
components: Option<Vec<String>>,
Expand Down Expand Up @@ -90,6 +94,7 @@ impl DCommand {
&opts.src_root,
&opts.dest_root,
opts.device.as_deref(),
opts.with_static_configs,
opts.components.as_deref(),
)
.context("boot data installation failed")?;
Expand Down
4 changes: 4 additions & 0 deletions src/grub2/configs.d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add drop-in grub fragments into this directory to have
them be installed into the final config.

The filenames must end in `.cfg`.
17 changes: 17 additions & 0 deletions src/grub2/grub-static-post.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=1
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=1
fi

# Import user defined configuration
# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
if [ -f $prefix/user.cfg ]; then
source $prefix/user.cfg
fi

blscfg

23 changes: 1 addition & 22 deletions src/grub2/grub-static.cfg → src/grub2/grub-static-pre.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,4 @@ function load_video {
fi
}

# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
if [ -f $prefix/platform.cfg ]; then
source $prefix/platform.cfg
fi

if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=1
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=1
fi

# Import user defined configuration
# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
if [ -f $prefix/user.cfg ]; then
source $prefix/user.cfg
fi

blscfg

# Other package code will be injected from here
110 changes: 110 additions & 0 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::fmt::Write;
use std::os::unix::prelude::OsStrExt;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context, Result};
use fn_error_context::context;
use openat_ext::OpenatDirExt;

/// The subdirectory of /boot we use
const GRUB2DIR: &str = "grub2";
const CONFIGDIR: &str = "/usr/lib/bootupd/grub2-static";
const DROPINDIR: &str = "configs.d";

#[context("Locating EFI vendordir")]
pub(crate) fn find_efi_vendordir(efidir: &openat::Dir) -> Result<PathBuf> {
for d in efidir.list_dir(".")? {
let d = d?;
if d.file_name().as_bytes() == b"BOOT" {
continue;
}
let meta = efidir.metadata(d.file_name())?;
if !meta.is_dir() {
continue;
}
return Ok(d.file_name().into());
}
anyhow::bail!("Failed to find EFI vendor dir")
}

/// Install the static GRUB config files.
#[context("Installing static GRUB configs")]
pub(crate) fn install(target_root: &openat::Dir, efi: 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"))?;

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;
}
// SAFETY: Cannot fail
writeln!(config, "source $prefix/{name}").unwrap();
dropindir.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))?;
println!("Installed {name}");
}

{
let post = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-post.cfg"))?;
config.push_str(post.as_str());
}

bootdir
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
.context("Copying grub-static.cfg")?;
println!("Installed: grub.cfg");

let efidir = efi
.then(|| {
target_root
.sub_dir_optional("boot/efi/EFI")
.context("Opening /boot/efi/EFI")
})
.transpose()?
.flatten();
if let Some(efidir) = efidir.as_ref() {
let vendordir = find_efi_vendordir(efidir)?;
log::debug!("vendordir={:?}", &vendordir);
let target = &vendordir.join("grub.cfg");
efidir
.copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target)
.context("Copying static EFI")?;
println!("Installed: {target:?}");
}

Ok(())
}

#[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, true).unwrap();

assert!(td.exists("boot/grub2/grub.cfg")?);
assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?);
Ok(())
}
}
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ mod daemon;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
mod efi;
mod filetree;
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
))]
mod grubconfigs;
mod ipc;
mod model;
mod model_legacy;
Expand Down

0 comments on commit fcf0dab

Please sign in to comment.