Skip to content

Commit

Permalink
Merge pull request #543 from cgwalters/install-grub-configs-simpler
Browse files Browse the repository at this point in the history
 Add support for installing static grub configs
  • Loading branch information
cgwalters authored Oct 19, 2023
2 parents 9a81173 + 1475f25 commit 66735ba
Show file tree
Hide file tree
Showing 9 changed files with 182 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 -m 644 -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
install -m 755 -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
29 changes: 18 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,22 @@ 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);
break;
}
log::debug!("Mounted at {tmppath:?}");
*mountpoint = Some(tmppath);
Ok(mountpoint.as_deref().unwrap().to_owned())
}

Expand Down Expand Up @@ -380,6 +386,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
111 changes: 111 additions & 0 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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;
}
writeln!(config, "source $prefix/{name}")?;
dropindir
.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))
.with_context(|| format!("Copying {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 66735ba

Please sign in to comment.