Skip to content

Commit 11ddd18

Browse files
cgwaltersjmarrero
authored andcommitted
blockdev: Clean up ESP discovery code
Several improvements to ESP partition discovery: Add find_partition_of_esp_optional() returning Result<Option<&Device>> to cleanly separate three outcomes: found, absent, and genuinely unexpected errors (like unsupported partition table types). The existing find_partition_of_esp() is now a thin wrapper that converts None to Err. Add find_first_colocated_esp() helper to replace a 10-line pattern that was repeated verbatim 5 times across boot.rs and store/mod.rs. Deduplicate roots in find_all_roots() using a seen-set: in complex topologies like multipath, multiple parent branches can converge on the same physical disk. find_colocated_esps() now uses the optional variant to properly propagate real errors while treating absence normally. Also extract the match-on-if-else in setup_composefs_bls_boot into a let binding for readability. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org> Signed-off-by: Chris Kyrouac <ckyrouac@redhat.com>
1 parent 15d28e1 commit 11ddd18

File tree

4 files changed

+75
-89
lines changed

4 files changed

+75
-89
lines changed

crates/blockdev/src/blockdev.rs

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashSet;
12
use std::env;
23
use std::path::Path;
34
use std::process::{Command, Stdio};
@@ -123,15 +124,26 @@ impl Device {
123124
/// Calls find_all_roots() to discover physical disks, then searches each for an ESP.
124125
/// Returns None if no ESPs are found.
125126
pub fn find_colocated_esps(&self) -> Result<Option<Vec<Device>>> {
126-
let esps: Vec<_> = self
127-
.find_all_roots()?
128-
.iter()
129-
.flat_map(|root| root.find_partition_of_esp().ok())
130-
.cloned()
131-
.collect();
127+
let mut esps = Vec::new();
128+
for root in &self.find_all_roots()? {
129+
if let Some(esp) = root.find_partition_of_esp_optional()? {
130+
esps.push(esp.clone());
131+
}
132+
}
132133
Ok((!esps.is_empty()).then_some(esps))
133134
}
134135

136+
/// Find a single ESP partition among all root devices backing this device.
137+
///
138+
/// Walks the parent chain to find all backing disks, then looks for ESP
139+
/// partitions on each. Returns the first ESP found. This is the common
140+
/// case for composefs/UKI boot paths where exactly one ESP is expected.
141+
pub fn find_first_colocated_esp(&self) -> Result<Device> {
142+
self.find_colocated_esps()?
143+
.and_then(|mut v| Some(v.remove(0)))
144+
.ok_or_else(|| anyhow!("No ESP partition found among backing devices"))
145+
}
146+
135147
/// Find all BIOS boot partitions across all root devices backing this device.
136148
/// Calls find_all_roots() to discover physical disks, then searches each for a BIOS boot partition.
137149
/// Returns None if no BIOS boot partitions are found.
@@ -159,34 +171,41 @@ impl Device {
159171
///
160172
/// For GPT disks, this matches by the ESP partition type GUID.
161173
/// For MBR (dos) disks, this matches by the MBR partition type IDs (0x06 or 0xEF).
162-
pub fn find_partition_of_esp(&self) -> Result<&Device> {
163-
let children = self
164-
.children
165-
.as_ref()
166-
.ok_or_else(|| anyhow!("Device has no children"))?;
174+
///
175+
/// Returns `Ok(None)` when there are no children or no ESP partition
176+
/// is present. Returns `Err` only for genuinely unexpected conditions
177+
/// (e.g. an unsupported partition table type).
178+
pub fn find_partition_of_esp_optional(&self) -> Result<Option<&Device>> {
179+
let Some(children) = self.children.as_ref() else {
180+
return Ok(None);
181+
};
167182
match self.pttype.as_deref() {
168-
Some("dos") => children
169-
.iter()
170-
.find(|child| {
171-
child
172-
.parttype
173-
.as_ref()
174-
.and_then(|pt| {
175-
let pt = pt.strip_prefix("0x").unwrap_or(pt);
176-
u8::from_str_radix(pt, 16).ok()
177-
})
178-
.is_some_and(|pt| ESP_ID_MBR.contains(&pt))
179-
})
180-
.ok_or_else(|| anyhow!("ESP not found in MBR partition table")),
183+
Some("dos") => Ok(children.iter().find(|child| {
184+
child
185+
.parttype
186+
.as_ref()
187+
.and_then(|pt| {
188+
let pt = pt.strip_prefix("0x").unwrap_or(pt);
189+
u8::from_str_radix(pt, 16).ok()
190+
})
191+
.is_some_and(|pt| ESP_ID_MBR.contains(&pt))
192+
})),
181193
// When pttype is None (e.g. older lsblk or partition devices), default
182194
// to GPT UUID matching which will simply not match MBR hex types.
183-
Some("gpt") | None => self
184-
.find_partition_of_type(ESP)
185-
.ok_or_else(|| anyhow!("ESP not found in GPT partition table")),
195+
Some("gpt") | None => Ok(self.find_partition_of_type(ESP)),
186196
Some(other) => Err(anyhow!("Unsupported partition table type: {other}")),
187197
}
188198
}
189199

200+
/// Find the EFI System Partition (ESP) among children, or error if absent.
201+
///
202+
/// This is a convenience wrapper around [`Self::find_partition_of_esp_optional`]
203+
/// for callers that require an ESP to be present.
204+
pub fn find_partition_of_esp(&self) -> Result<&Device> {
205+
self.find_partition_of_esp_optional()?
206+
.ok_or_else(|| anyhow!("ESP partition not found on {}", self.path()))
207+
}
208+
190209
/// Find a child partition by partition number (1-indexed).
191210
pub fn find_device_by_partno(&self, partno: u32) -> Result<&Device> {
192211
self.children
@@ -308,15 +327,21 @@ impl Device {
308327
};
309328

310329
let mut roots = Vec::new();
330+
let mut seen = HashSet::new();
311331
let mut queue = parents;
312332
while let Some(mut device) = queue.pop() {
313333
match device.children.take() {
314334
Some(grandparents) if !grandparents.is_empty() => {
315335
queue.extend(grandparents);
316336
}
317337
_ => {
318-
// Found a root; re-query to populate its actual children
319-
roots.push(list_dev(Utf8Path::new(&device.path()))?);
338+
// Deduplicate: in complex topologies (e.g. multipath)
339+
// multiple branches can converge on the same physical disk.
340+
let name = device.name.clone();
341+
if seen.insert(name) {
342+
// Found a new root; re-query to populate its actual children
343+
roots.push(list_dev(Utf8Path::new(&device.path()))?);
344+
}
320345
}
321346
}
322347
}

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -549,17 +549,7 @@ pub(crate) fn setup_composefs_bls_boot(
549549
}
550550

551551
// Locate ESP partition device by walking up to the root disk(s)
552-
let esp_part = root_setup
553-
.device_info
554-
.find_colocated_esps()?
555-
.and_then(|mut v| {
556-
if v.is_empty() {
557-
None
558-
} else {
559-
Some(v.remove(0))
560-
}
561-
})
562-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
552+
let esp_part = root_setup.device_info.find_first_colocated_esp()?;
563553

564554
(
565555
root_setup.physical_root_path.clone(),
@@ -598,16 +588,7 @@ pub(crate) fn setup_composefs_bls_boot(
598588

599589
// Locate ESP partition device by walking up to the root disk(s)
600590
let root_dev = bootc_blockdev::list_dev_by_dir(&storage.physical_root)?;
601-
let esp_dev = root_dev
602-
.find_colocated_esps()?
603-
.and_then(|mut v| {
604-
if v.is_empty() {
605-
None
606-
} else {
607-
Some(v.remove(0))
608-
}
609-
})
610-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
591+
let esp_dev = root_dev.find_first_colocated_esp()?;
611592

612593
(
613594
Utf8PathBuf::from("/sysroot"),
@@ -1116,17 +1097,7 @@ pub(crate) fn setup_composefs_uki_boot(
11161097
state.require_no_kargs_for_uki()?;
11171098

11181099
// Locate ESP partition device by walking up to the root disk(s)
1119-
let esp_part = root_setup
1120-
.device_info
1121-
.find_colocated_esps()?
1122-
.and_then(|mut v| {
1123-
if v.is_empty() {
1124-
None
1125-
} else {
1126-
Some(v.remove(0))
1127-
}
1128-
})
1129-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
1100+
let esp_part = root_setup.device_info.find_first_colocated_esp()?;
11301101

11311102
(
11321103
root_setup.physical_root_path.clone(),
@@ -1143,16 +1114,7 @@ pub(crate) fn setup_composefs_uki_boot(
11431114

11441115
// Locate ESP partition device by walking up to the root disk(s)
11451116
let root_dev = bootc_blockdev::list_dev_by_dir(&storage.physical_root)?;
1146-
let esp_dev = root_dev
1147-
.find_colocated_esps()?
1148-
.and_then(|mut v| {
1149-
if v.is_empty() {
1150-
None
1151-
} else {
1152-
Some(v.remove(0))
1153-
}
1154-
})
1155-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
1117+
let esp_dev = root_dev.find_first_colocated_esp()?;
11561118

11571119
(
11581120
sysroot,

crates/lib/src/bootloader.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fs::create_dir_all;
22
use std::process::Command;
33

4-
use anyhow::{anyhow, bail, Context, Result};
4+
use anyhow::{Context, Result, anyhow, bail};
55
use bootc_utils::{BwrapCmd, CommandRunExt};
66
use camino::Utf8Path;
77
use cap_std_ext::cap_std::fs::Dir;
@@ -10,8 +10,8 @@ use fn_error_context::context;
1010

1111
use bootc_mount as mount;
1212

13-
use crate::bootc_composefs::boot::{mount_esp, SecurebootKeys};
14-
use crate::{discoverable_partition_specification, utils};
13+
use crate::bootc_composefs::boot::{SecurebootKeys, mount_esp};
14+
use crate::utils;
1515

1616
/// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel)
1717
pub(crate) const EFI_DIR: &str = "efi";
@@ -53,13 +53,17 @@ pub(crate) fn mount_esp_part(root: &Dir, root_path: &Utf8Path, is_ostree: bool)
5353

5454
let roots = bootc_blockdev::list_dev_by_dir(physical_root)?.find_all_roots()?;
5555
for dev in &roots {
56-
if let Some(esp_dev) = dev.find_partition_of_type(bootc_blockdev::ESP) {
56+
if let Some(esp_dev) = dev.find_partition_of_esp_optional()? {
5757
let esp_path = esp_dev.path();
5858
bootc_mount::mount(&esp_path, &root_path.join(&efi_path))?;
5959
tracing::debug!("Mounted {esp_path} at /boot/efi");
6060
return Ok(());
6161
}
6262
}
63+
tracing::debug!(
64+
"No ESP partition found among {} root device(s)",
65+
roots.len()
66+
);
6367
Ok(())
6468
}
6569

@@ -228,10 +232,14 @@ pub(crate) fn install_systemd_boot(
228232
autoenroll: Option<SecurebootKeys>,
229233
) -> Result<()> {
230234
let roots = device.find_all_roots()?;
231-
let esp_part = roots
232-
.iter()
233-
.find_map(|root| root.find_partition_of_type(discoverable_partition_specification::ESP))
234-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
235+
let mut esp_part = None;
236+
for root in &roots {
237+
if let Some(esp) = root.find_partition_of_esp_optional()? {
238+
esp_part = Some(esp);
239+
break;
240+
}
241+
}
242+
let esp_part = esp_part.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
235243

236244
let esp_mount = mount_esp(&esp_part.path()).context("Mounting ESP")?;
237245
let esp_path = Utf8Path::from_path(esp_mount.dir.path())

crates/lib/src/store/mod.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,7 @@ impl BootedStorage {
200200

201201
// Locate ESP by walking up to the root disk(s)
202202
let root_dev = bootc_blockdev::list_dev_by_dir(&physical_root)?;
203-
let esp_dev = root_dev
204-
.find_colocated_esps()?
205-
.and_then(|mut v| {
206-
if v.is_empty() {
207-
None
208-
} else {
209-
Some(v.remove(0))
210-
}
211-
})
212-
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
203+
let esp_dev = root_dev.find_first_colocated_esp()?;
213204
let esp_mount = mount_esp(&esp_dev.path())?;
214205

215206
let boot_dir = match get_bootloader()? {

0 commit comments

Comments
 (0)