@@ -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