@@ -19,6 +19,9 @@ pub const ESP_ID_MBR: &[u8] = &[0x06, 0xEF];
1919/// EFI System Partition (ESP) for UEFI boot on GPT
2020pub const ESP : & str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" ;
2121
22+ /// BIOS boot partition type GUID for GPT
23+ pub const BIOS_BOOT : & str = "21686148-6449-6e6f-744e-656564454649" ;
24+
2225#[ derive( Debug , Deserialize ) ]
2326struct DevicesOutput {
2427 blockdevices : Vec < Device > ,
@@ -69,6 +72,79 @@ impl Device {
6972 self . children . as_ref ( ) . is_some_and ( |v| !v. is_empty ( ) )
7073 }
7174
75+ // Check if the device is mpath
76+ pub fn is_mpath ( & self ) -> Result < bool > {
77+ let dm_path = Utf8PathBuf :: from_path_buf ( std:: fs:: canonicalize ( self . path ( ) ) ?)
78+ . map_err ( |_| anyhow:: anyhow!( "Non-UTF8 path" ) ) ?;
79+ let dm_name = dm_path. file_name ( ) . unwrap_or ( "" ) ;
80+ let uuid_path = Utf8PathBuf :: from ( format ! ( "/sys/class/block/{dm_name}/dm/uuid" ) ) ;
81+
82+ if uuid_path. exists ( ) {
83+ let uuid = std:: fs:: read_to_string ( & uuid_path)
84+ . with_context ( || format ! ( "Failed to read {uuid_path}" ) ) ?;
85+ if uuid. trim_start ( ) . starts_with ( "mpath-" ) {
86+ return Ok ( true ) ;
87+ }
88+ }
89+ Ok ( false )
90+ }
91+
92+ /// Get the numeric partition index of the ESP (e.g. "1", "2").
93+ ///
94+ /// We read `/sys/class/block/<name>/partition` rather than parsing device
95+ /// names because naming conventions vary across disk types (sd, nvme, dm, etc.).
96+ /// On multipath devices the sysfs `partition` attribute doesn't exist, so we
97+ /// fall back to the `partn` field reported by lsblk.
98+ pub fn get_esp_partition_number ( & self ) -> Result < String > {
99+ let esp_device = self . find_partition_of_esp ( ) ?;
100+ let devname = & esp_device. name ;
101+
102+ let partition_path = Utf8PathBuf :: from ( format ! ( "/sys/class/block/{devname}/partition" ) ) ;
103+ if partition_path. exists ( ) {
104+ return std:: fs:: read_to_string ( & partition_path)
105+ . with_context ( || format ! ( "Failed to read {partition_path}" ) ) ;
106+ }
107+
108+ // On multipath the partition attribute is not existing
109+ if self . is_mpath ( ) ? {
110+ if let Some ( partn) = esp_device. partn {
111+ return Ok ( partn. to_string ( ) ) ;
112+ }
113+ }
114+ anyhow:: bail!( "Not supported for {devname}" )
115+ }
116+
117+ /// Find BIOS boot partition among children.
118+ pub fn find_partition_of_bios_boot ( & self ) -> Option < & Device > {
119+ self . find_partition_of_type ( BIOS_BOOT )
120+ }
121+
122+ /// Find all ESP partitions across all root devices backing this device.
123+ /// Calls find_all_roots() to discover physical disks, then searches each for an ESP.
124+ /// Returns None if no ESPs are found.
125+ 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 ( ) ;
132+ Ok ( ( !esps. is_empty ( ) ) . then_some ( esps) )
133+ }
134+
135+ /// Find all BIOS boot partitions across all root devices backing this device.
136+ /// Calls find_all_roots() to discover physical disks, then searches each for a BIOS boot partition.
137+ /// Returns None if no BIOS boot partitions are found.
138+ pub fn find_colocated_bios_boot ( & self ) -> Result < Option < Vec < Device > > > {
139+ let bios_boots: Vec < _ > = self
140+ . find_all_roots ( ) ?
141+ . iter ( )
142+ . filter_map ( |root| root. find_partition_of_bios_boot ( ) )
143+ . cloned ( )
144+ . collect ( ) ;
145+ Ok ( ( !bios_boots. is_empty ( ) ) . then_some ( bios_boots) )
146+ }
147+
72148 /// Find a child partition by partition type (case-insensitive).
73149 pub fn find_partition_of_type ( & self , parttype : & str ) -> Option < & Device > {
74150 self . children . as_ref ( ) ?. iter ( ) . find ( |child| {
@@ -506,6 +582,10 @@ mod test {
506582 // Verify find_partition_of_esp works
507583 let esp = dev. find_partition_of_esp ( ) . unwrap ( ) ;
508584 assert_eq ! ( esp. partn, Some ( 2 ) ) ;
585+ // Verify find_partition_of_bios_boot works (vda1 is BIOS-BOOT)
586+ let bios = dev. find_partition_of_bios_boot ( ) . unwrap ( ) ;
587+ assert_eq ! ( bios. partn, Some ( 1 ) ) ;
588+ assert_eq ! ( bios. parttype. as_deref( ) . unwrap( ) , BIOS_BOOT ) ;
509589 }
510590
511591 #[ test]
0 commit comments