Skip to content

Commit fcf0dab

Browse files
committed
Add support for installing static grub configs
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]>
1 parent d299ba4 commit fcf0dab

File tree

8 files changed

+163
-27
lines changed

8 files changed

+163
-27
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ install: install-units
4646
ln -s ../bootupd.socket "${DESTDIR}$(PREFIX)/lib/systemd/system/multi-user.target.wants"
4747

4848
install-grub-static:
49-
install -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
49+
install -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
50+
install -d ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static/configs.d

src/bootupd.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ pub(crate) fn install(
3030
source_root: &str,
3131
dest_root: &str,
3232
device: Option<&str>,
33+
with_static_configs: bool,
3334
target_components: Option<&[String]>,
3435
) -> Result<()> {
3536
// TODO: Change this to an Option<&str>; though this probably balloons into having
3637
// DeviceComponent and FileBasedComponent
3738
let device = device.unwrap_or("");
38-
let source_root = openat::Dir::open(source_root)?;
39+
let source_root = openat::Dir::open(source_root).context("Opening source root")?;
3940
SavedState::ensure_not_present(dest_root)
4041
.context("failed to install, invalid re-install attempted")?;
4142

@@ -62,7 +63,8 @@ pub(crate) fn install(
6263
}
6364

6465
let mut state = SavedState::default();
65-
for component in target_components {
66+
let mut installed_efi = false;
67+
for &component in target_components.iter() {
6668
// skip for BIOS if device is empty
6769
if component.name() == "BIOS" && device.is_empty() {
6870
println!(
@@ -77,10 +79,22 @@ pub(crate) fn install(
7779
.with_context(|| format!("installing component {}", component.name()))?;
7880
log::info!("Installed {} {}", component.name(), meta.meta.version);
7981
state.installed.insert(component.name().into(), meta);
82+
// Yes this is a hack...the Component thing just turns out to be too generic.
83+
if component.name() == "EFI" {
84+
installed_efi = true;
85+
}
86+
}
87+
let sysroot = &openat::Dir::open(dest_root)?;
88+
89+
if with_static_configs {
90+
crate::grubconfigs::install(sysroot, installed_efi)?;
8091
}
8192

82-
let sysroot = openat::Dir::open(dest_root)?;
83-
let mut state_guard = SavedState::unlocked(sysroot).context("failed to acquire write lock")?;
93+
// Unmount the ESP, etc.
94+
drop(target_components);
95+
96+
let mut state_guard =
97+
SavedState::unlocked(sysroot.try_clone()?).context("failed to acquire write lock")?;
8498
state_guard
8599
.update_state(&state)
86100
.context("failed to update state")?;

src/cli/bootupd.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ pub struct InstallOpts {
5252
#[clap(long)]
5353
device: Option<String>,
5454

55+
/// Enable installation of the built-in static config files
56+
#[clap(long)]
57+
with_static_configs: bool,
58+
5559
#[clap(long = "component")]
5660
/// Only install these components
5761
components: Option<Vec<String>>,
@@ -90,6 +94,7 @@ impl DCommand {
9094
&opts.src_root,
9195
&opts.dest_root,
9296
opts.device.as_deref(),
97+
opts.with_static_configs,
9398
opts.components.as_deref(),
9499
)
95100
.context("boot data installation failed")?;

src/grub2/configs.d/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add drop-in grub fragments into this directory to have
2+
them be installed into the final config.
3+
4+
The filenames must end in `.cfg`.

src/grub2/grub-static-post.cfg

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
if [ x$feature_timeout_style = xy ] ; then
2+
set timeout_style=menu
3+
set timeout=1
4+
# Fallback normal timeout code in case the timeout_style feature is
5+
# unavailable.
6+
else
7+
set timeout=1
8+
fi
9+
10+
# Import user defined configuration
11+
# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
12+
if [ -f $prefix/user.cfg ]; then
13+
source $prefix/user.cfg
14+
fi
15+
16+
blscfg
17+
Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,4 @@ function load_video {
5959
fi
6060
}
6161

62-
# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
63-
if [ -f $prefix/platform.cfg ]; then
64-
source $prefix/platform.cfg
65-
fi
66-
67-
if [ x$feature_timeout_style = xy ] ; then
68-
set timeout_style=menu
69-
set timeout=1
70-
# Fallback normal timeout code in case the timeout_style feature is
71-
# unavailable.
72-
else
73-
set timeout=1
74-
fi
75-
76-
# Import user defined configuration
77-
# tracker: https://github.com/coreos/fedora-coreos-tracker/issues/805
78-
if [ -f $prefix/user.cfg ]; then
79-
source $prefix/user.cfg
80-
fi
81-
82-
blscfg
83-
62+
# Other package code will be injected from here

src/grubconfigs.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::fmt::Write;
2+
use std::os::unix::prelude::OsStrExt;
3+
use std::path::{Path, PathBuf};
4+
5+
use anyhow::{anyhow, Context, Result};
6+
use fn_error_context::context;
7+
use openat_ext::OpenatDirExt;
8+
9+
/// The subdirectory of /boot we use
10+
const GRUB2DIR: &str = "grub2";
11+
const CONFIGDIR: &str = "/usr/lib/bootupd/grub2-static";
12+
const DROPINDIR: &str = "configs.d";
13+
14+
#[context("Locating EFI vendordir")]
15+
pub(crate) fn find_efi_vendordir(efidir: &openat::Dir) -> Result<PathBuf> {
16+
for d in efidir.list_dir(".")? {
17+
let d = d?;
18+
if d.file_name().as_bytes() == b"BOOT" {
19+
continue;
20+
}
21+
let meta = efidir.metadata(d.file_name())?;
22+
if !meta.is_dir() {
23+
continue;
24+
}
25+
return Ok(d.file_name().into());
26+
}
27+
anyhow::bail!("Failed to find EFI vendor dir")
28+
}
29+
30+
/// Install the static GRUB config files.
31+
#[context("Installing static GRUB configs")]
32+
pub(crate) fn install(target_root: &openat::Dir, efi: bool) -> Result<()> {
33+
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
34+
35+
let mut config = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-pre.cfg"))?;
36+
37+
let dropindir = openat::Dir::open(&Path::new(CONFIGDIR).join(DROPINDIR))?;
38+
// Sort the files for reproducibility
39+
let mut entries = dropindir
40+
.list_dir(".")?
41+
.map(|e| e.map_err(anyhow::Error::msg))
42+
.collect::<Result<Vec<_>>>()?;
43+
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
44+
for ent in entries {
45+
let name = ent.file_name();
46+
let name = name
47+
.to_str()
48+
.ok_or_else(|| anyhow!("Invalid UTF-8: {name:?}"))?;
49+
if !name.ends_with(".cfg") {
50+
log::debug!("Ignoring {name}");
51+
continue;
52+
}
53+
// SAFETY: Cannot fail
54+
writeln!(config, "source $prefix/{name}").unwrap();
55+
dropindir.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))?;
56+
println!("Installed {name}");
57+
}
58+
59+
{
60+
let post = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-post.cfg"))?;
61+
config.push_str(post.as_str());
62+
}
63+
64+
bootdir
65+
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
66+
.context("Copying grub-static.cfg")?;
67+
println!("Installed: grub.cfg");
68+
69+
let efidir = efi
70+
.then(|| {
71+
target_root
72+
.sub_dir_optional("boot/efi/EFI")
73+
.context("Opening /boot/efi/EFI")
74+
})
75+
.transpose()?
76+
.flatten();
77+
if let Some(efidir) = efidir.as_ref() {
78+
let vendordir = find_efi_vendordir(efidir)?;
79+
log::debug!("vendordir={:?}", &vendordir);
80+
let target = &vendordir.join("grub.cfg");
81+
efidir
82+
.copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target)
83+
.context("Copying static EFI")?;
84+
println!("Installed: {target:?}");
85+
}
86+
87+
Ok(())
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
#[ignore]
96+
fn test_install() -> Result<()> {
97+
env_logger::init();
98+
let td = tempfile::tempdir()?;
99+
let tdp = td.path();
100+
let td = openat::Dir::open(tdp)?;
101+
std::fs::create_dir_all(tdp.join("boot/grub2"))?;
102+
std::fs::create_dir_all(tdp.join("boot/efi/EFI/BOOT"))?;
103+
std::fs::create_dir_all(tdp.join("boot/efi/EFI/fedora"))?;
104+
install(&td, true).unwrap();
105+
106+
assert!(td.exists("boot/grub2/grub.cfg")?);
107+
assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?);
108+
Ok(())
109+
}
110+
}

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ mod daemon;
2424
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
2525
mod efi;
2626
mod filetree;
27+
#[cfg(any(
28+
target_arch = "x86_64",
29+
target_arch = "aarch64",
30+
target_arch = "powerpc64"
31+
))]
32+
mod grubconfigs;
2733
mod ipc;
2834
mod model;
2935
mod model_legacy;

0 commit comments

Comments
 (0)