Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for installing static grub configs #543

Merged
merged 2 commits into from
Oct 19, 2023
Merged
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
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