Skip to content

Commit 3bbd4cf

Browse files
parsers/bls: BLS config for UKI
This is in prep for adding config files for BLS compliant bootloaders booting via UKI. Adds a field `cfg_type` to BLSConfig which will contain either of the following sets of keys: cfg_type - NonEFI - linux - initrd - options or cfg_type - EFI - efi Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent ad67368 commit 3bbd4cf

File tree

4 files changed

+160
-68
lines changed

4 files changed

+160
-68
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use serde::{Deserialize, Serialize};
3535
use crate::bootc_composefs::repo::open_composefs_repo;
3636
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
3737
use crate::bootc_composefs::status::get_sorted_uki_boot_entries;
38-
use crate::parsers::bls_config::BLSConfig;
38+
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
3939
use crate::parsers::grub_menuconfig::MenuEntry;
4040
use crate::spec::ImageReference;
4141
use crate::task::Task;
@@ -448,25 +448,31 @@ pub(crate) fn setup_composefs_bls_boot(
448448
.with_title(title)
449449
.with_sort_key(default_sort_key.into())
450450
.with_version(version)
451-
.with_linux(format!(
452-
"/{}/{id_hex}/vmlinuz",
453-
entry_paths.abs_entries_path
454-
))
455-
.with_initrd(vec![format!(
456-
"/{}/{id_hex}/initrd",
457-
entry_paths.abs_entries_path
458-
)])
459-
.with_options(cmdline_refs);
451+
.with_cfg(BLSConfigType::NonEFI {
452+
linux: format!("/{}/{id_hex}/vmlinuz", entry_paths.abs_entries_path),
453+
initrd: vec![format!("/{}/{id_hex}/initrd", entry_paths.abs_entries_path)],
454+
options: Some(cmdline_refs),
455+
});
460456

461457
match find_vmlinuz_initrd_duplicates(&boot_digest)? {
462458
Some(symlink_to) => {
463-
bls_config.linux =
464-
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path);
465-
466-
bls_config.initrd = vec![format!(
467-
"/{}/{symlink_to}/initrd",
468-
entry_paths.abs_entries_path
469-
)];
459+
match bls_config.cfg_type {
460+
BLSConfigType::NonEFI {
461+
ref mut linux,
462+
ref mut initrd,
463+
..
464+
} => {
465+
*linux =
466+
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path);
467+
468+
*initrd = vec![format!(
469+
"/{}/{symlink_to}/initrd",
470+
entry_paths.abs_entries_path
471+
)];
472+
}
473+
474+
_ => unreachable!(),
475+
};
470476
}
471477

472478
None => {

crates/lib/src/bootc_composefs/state.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustix::{
1717
};
1818

1919
use crate::bootc_composefs::boot::BootType;
20+
use crate::parsers::bls_config::BLSConfigType;
2021
use crate::{
2122
composefs_consts::{
2223
COMPOSEFS_CMDLINE, COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR,
@@ -43,14 +44,31 @@ pub(crate) fn get_booted_bls() -> Result<BLSConfig> {
4344

4445
let bls = parse_bls_config(&std::fs::read_to_string(&entry.path())?)?;
4546

46-
let Some(opts) = &bls.options else {
47-
anyhow::bail!("options not found in bls config")
48-
};
49-
let opts = Cmdline::from(opts);
47+
match &bls.cfg_type {
48+
BLSConfigType::EFI { efi } => {
49+
let composfs_param_value = booted.value().ok_or(anyhow::anyhow!(
50+
"Failed to get composefs kernel cmdline value"
51+
))?;
5052

51-
if opts.iter().any(|v| v == booted) {
52-
return Ok(bls);
53-
}
53+
if efi.contains(composfs_param_value) {
54+
return Ok(bls);
55+
}
56+
}
57+
58+
BLSConfigType::NonEFI { options, .. } => {
59+
let Some(opts) = options else {
60+
anyhow::bail!("options not found in bls config")
61+
};
62+
63+
let opts = Cmdline::from(opts);
64+
65+
if opts.iter().any(|v| v == booted) {
66+
return Ok(bls);
67+
}
68+
}
69+
70+
BLSConfigType::Unknown => anyhow::bail!("Unknown BLS Config type"),
71+
};
5472
}
5573

5674
Err(anyhow::anyhow!("Booted BLS not found"))

crates/lib/src/bootc_composefs/status.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, BootType},
1010
composefs_consts::{BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, USER_CFG},
1111
parsers::{
12-
bls_config::{parse_bls_config, BLSConfig},
12+
bls_config::{parse_bls_config, BLSConfig, BLSConfigType},
1313
grub_menuconfig::{parse_grub_menuentry_file, MenuEntry},
1414
},
1515
spec::{BootEntry, BootOrder, Host, HostSpec, ImageReference, ImageStatus},
@@ -376,13 +376,25 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
376376

377377
match boot_type {
378378
BootType::Bls => {
379-
host.status.rollback_queued = !get_sorted_bls_boot_entries(&boot_dir, false)?
379+
let bls_config = get_sorted_bls_boot_entries(&boot_dir, false)?;
380+
let bls_config = bls_config
380381
.first()
381-
.ok_or(anyhow::anyhow!("First boot entry not found"))?
382-
.options
383-
.as_ref()
384-
.ok_or(anyhow::anyhow!("options key not found in bls config"))?
385-
.contains(composefs_digest.as_ref());
382+
.ok_or(anyhow::anyhow!("First boot entry not found"))?;
383+
384+
match &bls_config.cfg_type {
385+
BLSConfigType::EFI { efi } => {
386+
host.status.rollback_queued = efi.contains(composefs_digest.as_ref());
387+
}
388+
389+
BLSConfigType::NonEFI { options, .. } => {
390+
host.status.rollback_queued = !options
391+
.as_ref()
392+
.ok_or(anyhow::anyhow!("options key not found in bls config"))?
393+
.contains(composefs_digest.as_ref());
394+
}
395+
396+
BLSConfigType::Unknown => todo!(),
397+
};
386398
}
387399

388400
BootType::Uki => {
@@ -408,7 +420,7 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
408420
mod tests {
409421
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};
410422

411-
use crate::parsers::grub_menuconfig::MenuentryBody;
423+
use crate::parsers::{bls_config::BLSConfigType, grub_menuconfig::MenuentryBody};
412424

413425
use super::*;
414426

@@ -458,16 +470,20 @@ mod tests {
458470
let mut config1 = BLSConfig::default();
459471
config1.title = Some("Fedora 42.20250623.3.1 (CoreOS)".into());
460472
config1.sort_key = Some("1".into());
461-
config1.linux = "/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10".into();
462-
config1.initrd = vec!["/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img".into()];
463-
config1.options = Some("root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6".into());
473+
config1.cfg_type = BLSConfigType::NonEFI {
474+
linux: "/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10".into(),
475+
initrd: vec!["/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img".into()],
476+
options: Some("root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6".into()),
477+
};
464478

465479
let mut config2 = BLSConfig::default();
466480
config2.title = Some("Fedora 41.20250214.2.0 (CoreOS)".into());
467481
config2.sort_key = Some("2".into());
468-
config2.linux = "/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/vmlinuz-5.14.10".into();
469-
config2.initrd = vec!["/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/initramfs-5.14.10.img".into()];
470-
config2.options = Some("root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01".into());
482+
config2.cfg_type = BLSConfigType::NonEFI {
483+
linux: "/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/vmlinuz-5.14.10".into(),
484+
initrd: vec!["/boot/febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01/initramfs-5.14.10.img".into()],
485+
options: Some("root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01".into())
486+
};
471487

472488
assert_eq!(result[0].sort_key.as_ref().unwrap(), "1");
473489
assert_eq!(result[1].sort_key.as_ref().unwrap(), "2");

crates/lib/src/parsers/bls_config.rs

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,29 @@
55
#![allow(dead_code)]
66

77
use anyhow::{anyhow, Result};
8+
use core::fmt;
89
use std::collections::HashMap;
910
use std::fmt::Display;
1011
use uapi_version::Version;
1112

13+
#[derive(Debug, PartialEq, PartialOrd, Eq, Default)]
14+
pub enum BLSConfigType {
15+
EFI {
16+
/// The path to the EFI binary, usually a UKI
17+
efi: String,
18+
},
19+
NonEFI {
20+
/// The path to the linux kernel to boot.
21+
linux: String,
22+
/// The paths to the initrd images.
23+
initrd: Vec<String>,
24+
/// Kernel command line options.
25+
options: Option<String>,
26+
},
27+
#[default]
28+
Unknown,
29+
}
30+
1231
/// Represents a single Boot Loader Specification config file.
1332
///
1433
/// The boot loader should present the available boot menu entries to the user in a sorted list.
@@ -24,12 +43,9 @@ pub(crate) struct BLSConfig {
2443
///
2544
/// This is hidden and must be accessed via [`Self::version()`];
2645
version: String,
27-
/// The path to the linux kernel to boot.
28-
pub(crate) linux: String,
29-
/// The paths to the initrd images.
30-
pub(crate) initrd: Vec<String>,
31-
/// Kernel command line options.
32-
pub(crate) options: Option<String>,
46+
47+
pub(crate) cfg_type: BLSConfigType,
48+
3349
/// The machine ID of the OS.
3450
pub(crate) machine_id: Option<String>,
3551
/// The sort key for the boot menu.
@@ -79,13 +95,30 @@ impl Display for BLSConfig {
7995
}
8096

8197
writeln!(f, "version {}", self.version)?;
82-
writeln!(f, "linux {}", self.linux)?;
83-
for initrd in self.initrd.iter() {
84-
writeln!(f, "initrd {}", initrd)?;
85-
}
86-
if let Some(options) = self.options.as_deref() {
87-
writeln!(f, "options {}", options)?;
98+
99+
match &self.cfg_type {
100+
BLSConfigType::EFI { efi } => {
101+
writeln!(f, "efi {}", efi)?;
102+
}
103+
104+
BLSConfigType::NonEFI {
105+
linux,
106+
initrd,
107+
options,
108+
} => {
109+
writeln!(f, "linux {}", linux)?;
110+
for initrd in initrd.iter() {
111+
writeln!(f, "initrd {}", initrd)?;
112+
}
113+
114+
if let Some(options) = options.as_deref() {
115+
writeln!(f, "options {}", options)?;
116+
}
117+
}
118+
119+
BLSConfigType::Unknown => return Err(fmt::Error),
88120
}
121+
89122
if let Some(machine_id) = self.machine_id.as_deref() {
90123
writeln!(f, "machine-id {}", machine_id)?;
91124
}
@@ -114,16 +147,8 @@ impl BLSConfig {
114147
self.version = new_val;
115148
self
116149
}
117-
pub(crate) fn with_linux(&mut self, new_val: String) -> &mut Self {
118-
self.linux = new_val;
119-
self
120-
}
121-
pub(crate) fn with_initrd(&mut self, new_val: Vec<String>) -> &mut Self {
122-
self.initrd = new_val;
123-
self
124-
}
125-
pub(crate) fn with_options(&mut self, new_val: String) -> &mut Self {
126-
self.options = Some(new_val);
150+
pub(crate) fn with_cfg(&mut self, config: BLSConfigType) -> &mut Self {
151+
self.cfg_type = config;
127152
self
128153
}
129154
#[allow(dead_code)]
@@ -146,6 +171,7 @@ pub(crate) fn parse_bls_config(input: &str) -> Result<BLSConfig> {
146171
let mut title = None;
147172
let mut version = None;
148173
let mut linux = None;
174+
let mut efi = None;
149175
let mut initrd = Vec::new();
150176
let mut options = None;
151177
let mut machine_id = None;
@@ -168,22 +194,35 @@ pub(crate) fn parse_bls_config(input: &str) -> Result<BLSConfig> {
168194
"options" => options = Some(value),
169195
"machine-id" => machine_id = Some(value),
170196
"sort-key" => sort_key = Some(value),
197+
"efi" => efi = Some(value),
171198
_ => {
172199
extra.insert(key.to_string(), value);
173200
}
174201
}
175202
}
176203
}
177204

178-
let linux = linux.ok_or_else(|| anyhow!("Missing 'linux' value"))?;
179205
let version = version.ok_or_else(|| anyhow!("Missing 'version' value"))?;
180206

207+
let cfg_type = match (linux, efi) {
208+
(None, Some(efi)) => BLSConfigType::EFI { efi },
209+
210+
(Some(linux), None) => BLSConfigType::NonEFI {
211+
linux,
212+
initrd,
213+
options,
214+
},
215+
216+
// The spec makes no mention of whether both can be present or not
217+
// Fow now, for us, we won't have both at the same time
218+
(Some(_), Some(_)) => anyhow::bail!("'linux' and 'efi' values present"),
219+
(None, None) => anyhow::bail!("Missing 'linux' or 'efi' value"),
220+
};
221+
181222
Ok(BLSConfig {
182223
title,
183224
version,
184-
linux,
185-
initrd,
186-
options,
225+
cfg_type,
187226
machine_id,
188227
sort_key,
189228
extra,
@@ -208,14 +247,23 @@ mod tests {
208247

209248
let config = parse_bls_config(input)?;
210249

250+
let BLSConfigType::NonEFI {
251+
linux,
252+
initrd,
253+
options,
254+
} = config.cfg_type
255+
else {
256+
panic!("Expected non EFI variant");
257+
};
258+
211259
assert_eq!(
212260
config.title,
213261
Some("Fedora 42.20250623.3.1 (CoreOS)".to_string())
214262
);
215263
assert_eq!(config.version, "2");
216-
assert_eq!(config.linux, "/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10");
217-
assert_eq!(config.initrd, vec!["/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img"]);
218-
assert_eq!(config.options, Some("root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6".to_string()));
264+
assert_eq!(linux, "/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10");
265+
assert_eq!(initrd, vec!["/boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img"]);
266+
assert_eq!(options, Some("root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6".to_string()));
219267
assert_eq!(config.extra.get("custom1"), Some(&"value1".to_string()));
220268
assert_eq!(config.extra.get("custom2"), Some(&"value2".to_string()));
221269

@@ -235,8 +283,12 @@ mod tests {
235283

236284
let config = parse_bls_config(input)?;
237285

286+
let BLSConfigType::NonEFI { initrd, .. } = config.cfg_type else {
287+
panic!("Expected non EFI variant");
288+
};
289+
238290
assert_eq!(
239-
config.initrd,
291+
initrd,
240292
vec!["/boot/initramfs-1.img", "/boot/initramfs-2.img"]
241293
);
242294

0 commit comments

Comments
 (0)