Skip to content

Commit d33ddd2

Browse files
boot/config: Add method to get boot artifact name
Add a method in BLSConfig and Grub Menuconfig to get the boot artifact name, i.e. get the name of the UKI or the name of the directory containing the Kernel + Initrd. The names are stripped of all our custom prefixes and suffixes, so basically they return the verity digest part of the name. This is useful for GC-ing Kernel + Initrd that are shared among multiple deployments since we can't rely on the composefs= parameter in the options as the cmdline verity digest might be different than the verity digest of the shared Kernel + Initrd. Tests written by Claude Code (Opus) Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 0018400 commit d33ddd2

2 files changed

Lines changed: 286 additions & 4 deletions

File tree

crates/lib/src/parsers/bls_config.rs

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
//!
33
//! This module parses the config files for the spec.
44
5-
#![allow(dead_code)]
6-
75
use anyhow::{Result, anyhow};
86
use bootc_kernel_cmdline::utf8::{Cmdline, CmdlineOwned};
97
use camino::Utf8PathBuf;
@@ -15,7 +13,7 @@ use std::fmt::Display;
1513
use uapi_version::Version;
1614

1715
use crate::bootc_composefs::status::ComposefsCmdline;
18-
use crate::composefs_consts::UKI_NAME_PREFIX;
16+
use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, UKI_NAME_PREFIX};
1917

2018
#[derive(Debug, PartialEq, Eq, Default)]
2119
pub enum BLSConfigType {
@@ -173,6 +171,9 @@ impl BLSConfig {
173171
self
174172
}
175173

174+
/// Get the fs-verity digest from a BLS config
175+
/// For EFI BLS entries, this returns the name of the UKI
176+
/// For Non-EFI BLS entries, this returns the fs-verity digest in the "options" field
176177
pub(crate) fn get_verity(&self) -> Result<String> {
177178
match &self.cfg_type {
178179
BLSConfigType::EFI { efi } => {
@@ -205,6 +206,59 @@ impl BLSConfig {
205206
}
206207
}
207208

209+
/// Returns name of UKI in case of EFI config
210+
/// Returns name of the directory containing Kernel + Initrd in case of Non-EFI config
211+
///
212+
/// The names are stripped of our custom prefix and suffixes, so this basically returns
213+
/// the verity digest part of the name
214+
pub(crate) fn boot_artifact_name(&self) -> Result<&str> {
215+
match &self.cfg_type {
216+
BLSConfigType::EFI { efi } => {
217+
let file_name = efi
218+
.file_name()
219+
.ok_or_else(|| anyhow::anyhow!("EFI path missing file name: {}", efi))?;
220+
221+
let without_prefix = file_name.strip_prefix(UKI_NAME_PREFIX).ok_or_else(|| {
222+
anyhow::anyhow!(
223+
"EFI file name missing expected prefix '{}': {}",
224+
UKI_NAME_PREFIX,
225+
file_name
226+
)
227+
})?;
228+
229+
without_prefix.strip_suffix(EFI_EXT).ok_or_else(|| {
230+
anyhow::anyhow!(
231+
"EFI file name missing expected suffix '{}': {}",
232+
EFI_EXT,
233+
file_name
234+
)
235+
})
236+
}
237+
238+
BLSConfigType::NonEFI { linux, .. } => {
239+
let parent_dir = linux.parent().ok_or_else(|| {
240+
anyhow::anyhow!("Linux kernel path has no parent directory: {}", linux)
241+
})?;
242+
243+
let dir_name = parent_dir.file_name().ok_or_else(|| {
244+
anyhow::anyhow!("Parent directory has no file name: {}", parent_dir)
245+
})?;
246+
247+
dir_name.strip_prefix(TYPE1_BOOT_DIR_PREFIX).ok_or_else(|| {
248+
anyhow::anyhow!(
249+
"Boot directory missing expected prefix '{}': {}",
250+
TYPE1_BOOT_DIR_PREFIX,
251+
dir_name
252+
)
253+
})
254+
}
255+
256+
BLSConfigType::Unknown => {
257+
anyhow::bail!("Cannot extract boot artifact name from unknown config type")
258+
}
259+
}
260+
}
261+
208262
/// Gets the `options` field from the config
209263
/// Returns an error if the field doesn't exist
210264
/// or if the config is of type `EFI`
@@ -585,4 +639,128 @@ mod tests {
585639
assert!(config_final < config_rc1);
586640
Ok(())
587641
}
642+
643+
#[test]
644+
fn test_boot_artifact_name_efi_success() -> Result<()> {
645+
use camino::Utf8PathBuf;
646+
647+
let efi_path = Utf8PathBuf::from("bootc_composefs-abcd1234.efi");
648+
let config = BLSConfig {
649+
cfg_type: BLSConfigType::EFI { efi: efi_path },
650+
version: "1".to_string(),
651+
..Default::default()
652+
};
653+
654+
let artifact_name = config.boot_artifact_name()?;
655+
assert_eq!(artifact_name, "abcd1234");
656+
Ok(())
657+
}
658+
659+
#[test]
660+
fn test_boot_artifact_name_non_efi_success() -> Result<()> {
661+
use camino::Utf8PathBuf;
662+
663+
let linux_path = Utf8PathBuf::from("/boot/bootc_composefs-xyz5678/vmlinuz");
664+
let config = BLSConfig {
665+
cfg_type: BLSConfigType::NonEFI {
666+
linux: linux_path,
667+
initrd: vec![],
668+
options: None,
669+
},
670+
version: "1".to_string(),
671+
..Default::default()
672+
};
673+
674+
let artifact_name = config.boot_artifact_name()?;
675+
assert_eq!(artifact_name, "xyz5678");
676+
Ok(())
677+
}
678+
679+
#[test]
680+
fn test_boot_artifact_name_efi_missing_prefix() {
681+
use camino::Utf8PathBuf;
682+
683+
let efi_path = Utf8PathBuf::from("invalid-abcd1234.efi");
684+
let config = BLSConfig {
685+
cfg_type: BLSConfigType::EFI { efi: efi_path },
686+
version: "1".to_string(),
687+
..Default::default()
688+
};
689+
690+
let result = config.boot_artifact_name();
691+
assert!(result.is_err());
692+
assert!(
693+
result
694+
.unwrap_err()
695+
.to_string()
696+
.contains("missing expected prefix")
697+
);
698+
}
699+
700+
#[test]
701+
fn test_boot_artifact_name_efi_missing_suffix() {
702+
use camino::Utf8PathBuf;
703+
704+
let efi_path = Utf8PathBuf::from("bootc_composefs-abcd1234");
705+
let config = BLSConfig {
706+
cfg_type: BLSConfigType::EFI { efi: efi_path },
707+
version: "1".to_string(),
708+
..Default::default()
709+
};
710+
711+
let result = config.boot_artifact_name();
712+
assert!(result.is_err());
713+
assert!(
714+
result
715+
.unwrap_err()
716+
.to_string()
717+
.contains("missing expected suffix")
718+
);
719+
}
720+
721+
#[test]
722+
fn test_boot_artifact_name_non_efi_missing_prefix() {
723+
use camino::Utf8PathBuf;
724+
725+
let linux_path = Utf8PathBuf::from("/boot/invalid-xyz5678/vmlinuz");
726+
let config = BLSConfig {
727+
cfg_type: BLSConfigType::NonEFI {
728+
linux: linux_path,
729+
initrd: vec![],
730+
options: None,
731+
},
732+
version: "1".to_string(),
733+
..Default::default()
734+
};
735+
736+
let result = config.boot_artifact_name();
737+
assert!(result.is_err());
738+
assert!(
739+
result
740+
.unwrap_err()
741+
.to_string()
742+
.contains("missing expected prefix")
743+
);
744+
}
745+
746+
#[test]
747+
fn test_boot_artifact_name_efi_no_filename() {
748+
use camino::Utf8PathBuf;
749+
750+
let efi_path = Utf8PathBuf::from("/");
751+
let config = BLSConfig {
752+
cfg_type: BLSConfigType::EFI { efi: efi_path },
753+
version: "1".to_string(),
754+
..Default::default()
755+
};
756+
757+
let result = config.boot_artifact_name();
758+
assert!(result.is_err());
759+
assert!(
760+
result
761+
.unwrap_err()
762+
.to_string()
763+
.contains("missing file name")
764+
);
765+
}
588766
}

crates/lib/src/parsers/grub_menuconfig.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ impl<'a> MenuEntry<'a> {
114114
let name = to_path
115115
.components()
116116
.last()
117-
.ok_or(anyhow::anyhow!("Empty efi field"))?
117+
.ok_or_else(|| anyhow::anyhow!("Empty efi field"))?
118118
.to_string()
119119
.strip_prefix(UKI_NAME_PREFIX)
120120
.ok_or_else(|| anyhow::anyhow!("efi does not start with custom prefix"))?
@@ -124,6 +124,39 @@ impl<'a> MenuEntry<'a> {
124124

125125
Ok(name)
126126
}
127+
128+
/// Returns name of UKI in case of EFI config
129+
///
130+
/// The names are stripped of our custom prefix and suffixes, so this basically returns
131+
/// the verity digest part of the name
132+
pub(crate) fn boot_artifact_name(&self) -> Result<String> {
133+
let chainloader_path = Utf8PathBuf::from(&self.body.chainloader);
134+
135+
let file_name = chainloader_path.file_name().ok_or_else(|| {
136+
anyhow::anyhow!(
137+
"Chainloader path missing file name: {}",
138+
&self.body.chainloader
139+
)
140+
})?;
141+
142+
let without_prefix = file_name.strip_prefix(UKI_NAME_PREFIX).ok_or_else(|| {
143+
anyhow::anyhow!(
144+
"Chainloader file name missing expected prefix '{}': {}",
145+
UKI_NAME_PREFIX,
146+
file_name
147+
)
148+
})?;
149+
150+
let artifact_name = without_prefix.strip_suffix(EFI_EXT).ok_or_else(|| {
151+
anyhow::anyhow!(
152+
"Chainloader file name missing expected suffix '{}': {}",
153+
EFI_EXT,
154+
file_name
155+
)
156+
})?;
157+
158+
Ok(artifact_name.to_string())
159+
}
127160
}
128161

129162
/// Parser that takes content until balanced brackets, handling nested brackets and escapes.
@@ -547,4 +580,75 @@ mod test {
547580
assert_eq!(result[1].body.chainloader, "/EFI/Linux/second.efi");
548581
assert_eq!(result[1].body.search, "--set=root --fs-uuid \"some-uuid\"");
549582
}
583+
584+
#[test]
585+
fn test_menuentry_boot_artifact_name_success() {
586+
let body = MenuentryBody {
587+
insmod: vec!["fat", "chain"],
588+
chainloader: "/EFI/bootc_composefs/bootc_composefs-abcd1234.efi".to_string(),
589+
search: "--no-floppy --set=root --fs-uuid test",
590+
version: 0,
591+
extra: vec![],
592+
};
593+
594+
let entry = MenuEntry {
595+
title: "Test Entry".to_string(),
596+
body,
597+
};
598+
599+
let artifact_name = entry
600+
.boot_artifact_name()
601+
.expect("Should extract artifact name");
602+
assert_eq!(artifact_name, "abcd1234");
603+
}
604+
605+
#[test]
606+
fn test_menuentry_boot_artifact_name_missing_prefix() {
607+
let body = MenuentryBody {
608+
insmod: vec!["fat", "chain"],
609+
chainloader: "/EFI/Linux/invalid-abcd1234.efi".to_string(),
610+
search: "--no-floppy --set=root --fs-uuid test",
611+
version: 0,
612+
extra: vec![],
613+
};
614+
615+
let entry = MenuEntry {
616+
title: "Test Entry".to_string(),
617+
body,
618+
};
619+
620+
let result = entry.boot_artifact_name();
621+
assert!(result.is_err());
622+
assert!(
623+
result
624+
.unwrap_err()
625+
.to_string()
626+
.contains("missing expected prefix")
627+
);
628+
}
629+
630+
#[test]
631+
fn test_menuentry_boot_artifact_name_missing_suffix() {
632+
let body = MenuentryBody {
633+
insmod: vec!["fat", "chain"],
634+
chainloader: "/EFI/bootc_composefs/bootc_composefs-abcd1234".to_string(),
635+
search: "--no-floppy --set=root --fs-uuid test",
636+
version: 0,
637+
extra: vec![],
638+
};
639+
640+
let entry = MenuEntry {
641+
title: "Test Entry".to_string(),
642+
body,
643+
};
644+
645+
let result = entry.boot_artifact_name();
646+
assert!(result.is_err());
647+
assert!(
648+
result
649+
.unwrap_err()
650+
.to_string()
651+
.contains("missing expected suffix")
652+
);
653+
}
550654
}

0 commit comments

Comments
 (0)