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 12, 2023
1 parent 6d46360 commit f9ce09c
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 38 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
28 changes: 17 additions & 11 deletions src/efi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::util::CommandRunExt;
use crate::{component::*, packagesystem};

/// Well-known paths to the ESP that may have been mounted external to us.
pub(crate) const ESP_MOUNTS: &[&str] = &["boot", "boot/efi", "efi"];
pub(crate) const ESP_MOUNTS: &[&str] = &["boot/efi", "efi", "boot"];

/// The ESP partition label on Fedora CoreOS derivatives
pub(crate) const COREOS_ESP_PART_LABEL: &str = "EFI-SYSTEM";
Expand Down Expand Up @@ -54,7 +54,7 @@ impl Efi {
Ok(esp)
}

fn ensure_mounted_esp(&self, root: &Path) -> Result<PathBuf> {
pub(crate) fn ensure_mounted_esp(&self, root: &Path) -> Result<PathBuf> {
let mut mountpoint = self.mountpoint.borrow_mut();
if let Some(mountpoint) = mountpoint.as_deref() {
return Ok(mountpoint.to_owned());
Expand Down Expand Up @@ -84,16 +84,21 @@ impl Efi {
}
}
let esp_device = esp_device.ok_or_else(|| anyhow::anyhow!("Failed to find ESP device"))?;
let tmppath = tempfile::tempdir_in("/tmp")?.into_path();
let status = std::process::Command::new("mount")
.arg(&esp_device)
.arg(&tmppath)
.status()?;
if !status.success() {
anyhow::bail!("Failed to mount {:?}", esp_device);
for &mnt in ESP_MOUNTS.iter() {
let mnt = root.join(mnt);
if !mnt.exists() {
continue;
}
let status = std::process::Command::new("mount")
.arg(&esp_device)
.arg(&mnt)
.status()?;
if !status.success() {
anyhow::bail!("Failed to mount {:?}", esp_device);
}
log::debug!("Mounted at {mnt:?}");
*mountpoint = Some(mnt);
}
log::debug!("Mounted at {tmppath:?}");
*mountpoint = Some(tmppath);
Ok(mountpoint.as_deref().unwrap().to_owned())
}

Expand Down Expand Up @@ -380,6 +385,7 @@ impl Component for Efi {

impl Drop for Efi {
fn drop(&mut self) {
log::debug!("Unmounting");
let _ = self.unmount();
}
}
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
105 changes: 105 additions & 0 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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))?;
for ent in dropindir.list_dir(".")? {
let ent = ent?;
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 f9ce09c

Please sign in to comment.