bootc_lib/bootc_composefs/
boot_partition.rs1use anyhow::Result;
12use bootc_mount::tempmount::TempMount;
13use camino::{Utf8Path, Utf8PathBuf};
14use cap_std_ext::cap_std::fs::Dir;
15use cap_std_ext::dirext::CapStdExtDirExt;
16
17use crate::composefs_consts::{BLS_ENTRY_FILE_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
18use crate::parsers::bls_config::parse_bls_config;
19use crate::spec::Bootloader;
20
21#[derive(Debug, Clone)]
24pub(crate) enum BootPartition {
25 Esp(Utf8PathBuf),
27 Xbootldr(Utf8PathBuf),
30}
31
32impl BootPartition {
33 pub(crate) fn for_install(esp: Utf8PathBuf, bootloader: Bootloader) -> Result<Self> {
37 Ok(match sibling_xbootldr(&esp, bootloader)? {
38 Some(xbootldr) => Self::Xbootldr(xbootldr),
39 None => Self::Esp(esp),
40 })
41 }
42
43 pub(crate) fn mount_booted(
47 esp: Utf8PathBuf,
48 bootloader: Bootloader,
49 booted_verity: &str,
50 mount: impl Fn(&Utf8Path) -> Result<TempMount>,
51 ) -> Result<(Self, TempMount)> {
52 if let Some(xbootldr) = sibling_xbootldr(&esp, bootloader)? {
53 let mounted = mount(&xbootldr)?;
54 if has_type1_entry_for(&mounted.fd, booted_verity)? {
55 return Ok((Self::Xbootldr(xbootldr), mounted));
56 }
57 tracing::debug!(
58 "The booted deployment has no entry on XBOOTLDR partition {xbootldr}, using {esp}"
59 );
60 }
61 let mounted = mount(&esp)?;
62 Ok((Self::Esp(esp), mounted))
63 }
64
65 pub(crate) fn device(&self) -> &Utf8Path {
67 match self {
68 Self::Esp(device) | Self::Xbootldr(device) => device,
69 }
70 }
71}
72
73fn sibling_xbootldr(esp: &Utf8Path, bootloader: Bootloader) -> Result<Option<Utf8PathBuf>> {
74 if !bootloader.is_bls_conformant() {
75 return Ok(None);
76 }
77 let xbootldr = bootc_blockdev::list_dev(esp)?.find_sibling_xbootldr()?;
78 Ok(xbootldr.map(|xbootldr| {
79 tracing::debug!("Found XBOOTLDR partition {} next to {esp}", xbootldr.path());
80 Utf8PathBuf::from(xbootldr.path())
81 }))
82}
83
84fn has_type1_entry_for(boot_dir: &Dir, verity: &str) -> Result<bool> {
87 for path in [TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED] {
88 let Some(entries) = boot_dir.open_dir_optional(path)? else {
89 continue;
90 };
91 for entry in entries.entries_utf8()? {
92 let name = entry?.file_name()?;
93 if !(name.starts_with(BLS_ENTRY_FILE_PREFIX) && name.ends_with(".conf")) {
94 continue;
95 }
96 let entry = parse_bls_config(&entries.read_to_string(&name)?)?;
97 if entry.get_verity().is_ok_and(|v| v == verity) {
98 return Ok(true);
99 }
100 }
101 }
102 Ok(false)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::testutils::{ChangeType, TestRoot, fake_digest_version};
109
110 #[test]
111 fn test_has_type1_entry_for() -> Result<()> {
112 let mut root = TestRoot::new()?;
113 let installed = root.current().verity.clone();
114 root.upgrade(1, ChangeType::Kernel)?;
115 let upgraded = root.current().verity.clone();
116 let boot_dir = root.boot_dir()?;
117
118 assert!(has_type1_entry_for(&boot_dir, &installed)?);
119 assert!(has_type1_entry_for(&boot_dir, &upgraded)?);
120 assert!(!has_type1_entry_for(&boot_dir, &fake_digest_version(2))?);
121 Ok(())
122 }
123
124 #[test]
125 fn test_has_type1_entry_for_staged() -> Result<()> {
126 let root = TestRoot::new()?;
127 let verity = root.current().verity.clone();
128 let boot_dir = root.boot_dir()?;
129 boot_dir.rename(TYPE1_ENT_PATH, &boot_dir, TYPE1_ENT_PATH_STAGED)?;
130
131 assert!(has_type1_entry_for(&boot_dir, &verity)?);
132 Ok(())
133 }
134
135 #[test]
136 fn test_has_type1_entry_for_other_os() -> Result<()> {
137 let dir = cap_std_ext::cap_tempfile::tempdir(cap_std_ext::cap_std::ambient_authority())?;
138 let verity = fake_digest_version(0);
139 assert!(!has_type1_entry_for(&dir, &verity)?);
140
141 dir.create_dir_all(TYPE1_ENT_PATH)?;
142 dir.atomic_write(
143 format!("{TYPE1_ENT_PATH}/other-os.conf"),
144 "title Other\nlinux /vmlinuz\noptions root=UUID=other rw\n",
145 )?;
146 assert!(!has_type1_entry_for(&dir, &verity)?);
147 Ok(())
148 }
149}