Skip to content

Commit b1fe5d6

Browse files
ckyrouaccgwalters
authored andcommitted
blockdev: Handle ESP discovery on Intel VROC RAID devices
Intel VROC creates a topology where NVMe disks have MD RAID arrays as children, with the actual partitions (including the ESP) nested under the RAID array: nvme0n1 → md126 → md126p1 (ESP). Previously find_partition_of_esp_optional() only searched direct children, so it would miss the ESP in this layout. Now it recurses into children that carry their own partition table, which handles the firmware RAID case naturally. Add a lsblk JSON fixture representing a dual-NVMe firmware RAID1 layout and a test validating that ESP discovery works through the RAID layer. Assisted-by: Claude Code (Opus 4) Signed-off-by: ckyrouac <ckyrouac@redhat.com>
1 parent f3d2d4e commit b1fe5d6

3 files changed

Lines changed: 2095 additions & 5 deletions

File tree

crates/blockdev/src/blockdev.rs

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,19 @@ impl Device {
172172
/// For GPT disks, this matches by the ESP partition type GUID.
173173
/// For MBR (dos) disks, this matches by the MBR partition type IDs (0x06 or 0xEF).
174174
///
175+
/// If no ESP is found among direct children, this recurses into children
176+
/// that have their own partition table (e.g. firmware RAID arrays where the
177+
/// hierarchy is disk → md array → partitions).
178+
///
175179
/// Returns `Ok(None)` when there are no children or no ESP partition
176180
/// is present. Returns `Err` only for genuinely unexpected conditions
177181
/// (e.g. an unsupported partition table type).
178182
pub fn find_partition_of_esp_optional(&self) -> Result<Option<&Device>> {
179183
let Some(children) = self.children.as_ref() else {
180184
return Ok(None);
181185
};
182-
match self.pttype.as_deref() {
183-
Some("dos") => Ok(children.iter().find(|child| {
186+
let direct = match self.pttype.as_deref() {
187+
Some("dos") => children.iter().find(|child| {
184188
child
185189
.parttype
186190
.as_ref()
@@ -189,12 +193,25 @@ impl Device {
189193
u8::from_str_radix(pt, 16).ok()
190194
})
191195
.is_some_and(|pt| ESP_ID_MBR.contains(&pt))
192-
})),
196+
}),
193197
// When pttype is None (e.g. older lsblk or partition devices), default
194198
// to GPT UUID matching which will simply not match MBR hex types.
195-
Some("gpt") | None => Ok(self.find_partition_of_type(ESP)),
196-
Some(other) => Err(anyhow!("Unsupported partition table type: {other}")),
199+
Some("gpt") | None => self.find_partition_of_type(ESP),
200+
Some(other) => return Err(anyhow!("Unsupported partition table type: {other}")),
201+
};
202+
if direct.is_some() {
203+
return Ok(direct);
204+
}
205+
// Recurse into children that carry their own partition table, such as
206+
// firmware RAID arrays (disk → md array → partitions).
207+
for child in children {
208+
if child.pttype.is_some() {
209+
if let Some(esp) = child.find_partition_of_esp_optional()? {
210+
return Ok(Some(esp));
211+
}
212+
}
197213
}
214+
Ok(None)
198215
}
199216

200217
/// Find the EFI System Partition (ESP) among children, or error if absent.
@@ -708,6 +725,69 @@ mod test {
708725
}
709726
}
710727

728+
#[test]
729+
fn test_parse_lsblk_vroc() {
730+
let fixture = include_str!("../tests/fixtures/lsblk-vroc.json");
731+
let devs: DevicesOutput = serde_json::from_str(fixture).unwrap();
732+
assert_eq!(devs.blockdevices.len(), 2);
733+
734+
// find_partition_of_esp recurses through the md126 RAID array to
735+
// locate the ESP (md126p1) even though it is not a direct child of
736+
// the NVMe disk.
737+
for nvme in &devs.blockdevices {
738+
let esp = nvme.find_partition_of_esp().unwrap();
739+
assert_eq!(esp.name, "md126p1");
740+
assert_eq!(esp.partn, Some(1));
741+
assert_eq!(esp.parttype.as_deref().unwrap(), ESP);
742+
assert_eq!(esp.fstype.as_deref().unwrap(), "vfat");
743+
}
744+
}
745+
746+
#[test]
747+
fn test_parse_lsblk_swraid() {
748+
let fixture = include_str!("../tests/fixtures/lsblk-swraid.json");
749+
let devs: DevicesOutput = serde_json::from_str(fixture).unwrap();
750+
assert_eq!(devs.blockdevices.len(), 2);
751+
752+
// In a software RAID (mdadm) setup each disk is individually
753+
// partitioned with its own GPT table and ESP. The root partition
754+
// (sda3/sdb3) is a linux_raid_member assembled into md0.
755+
// find_partition_of_esp should locate the ESP as a direct child of
756+
// each disk — no recursion through an md array is needed here.
757+
let sda = &devs.blockdevices[0];
758+
let esp = sda.find_partition_of_esp().unwrap();
759+
assert_eq!(esp.name, "sda1");
760+
assert_eq!(esp.partn, Some(1));
761+
assert_eq!(esp.parttype.as_deref().unwrap(), ESP);
762+
assert_eq!(esp.fstype.as_deref().unwrap(), "vfat");
763+
764+
let sdb = &devs.blockdevices[1];
765+
let esp = sdb.find_partition_of_esp().unwrap();
766+
assert_eq!(esp.name, "sdb1");
767+
assert_eq!(esp.partn, Some(1));
768+
assert_eq!(esp.parttype.as_deref().unwrap(), ESP);
769+
assert_eq!(esp.fstype.as_deref().unwrap(), "vfat");
770+
771+
// Verify the md0 RAID array is visible as a child of the root
772+
// partition on each disk.
773+
let sda3 = sda
774+
.children
775+
.as_ref()
776+
.unwrap()
777+
.iter()
778+
.find(|c| c.name == "sda3")
779+
.unwrap();
780+
assert_eq!(sda3.fstype.as_deref().unwrap(), "linux_raid_member");
781+
let md0 = sda3
782+
.children
783+
.as_ref()
784+
.unwrap()
785+
.iter()
786+
.find(|c| c.name == "md0")
787+
.unwrap();
788+
assert_eq!(md0.fstype.as_deref().unwrap(), "ext4");
789+
}
790+
711791
#[test]
712792
fn test_mbr_esp_detection() {
713793
// 0x06 (FAT16) is recognized as ESP

0 commit comments

Comments
 (0)