@@ -17,7 +17,7 @@ use gptman::{GPTPartitionEntry, GPT};
1717use nix:: sys:: stat:: { major, minor} ;
1818use nix:: { errno:: Errno , mount, sched} ;
1919use regex:: Regex ;
20- use std :: collections :: HashMap ;
20+ use serde :: Deserialize ;
2121use std:: convert:: TryInto ;
2222use std:: env;
2323use std:: fs:: {
@@ -42,6 +42,42 @@ use crate::util::*;
4242
4343use crate :: { runcmd, runcmd_output} ;
4444
45+ #[ derive( Debug , Deserialize ) ]
46+ struct DevicesOutput {
47+ blockdevices : Vec < Device > ,
48+ }
49+
50+ #[ derive( Debug , Deserialize ) ]
51+ pub struct Device {
52+ pub name : String ,
53+ pub label : Option < String > ,
54+ pub fstype : Option < String > ,
55+ #[ serde( rename = "type" ) ]
56+ pub blktype : Option < String > ,
57+ pub mountpoint : Option < String > ,
58+ pub uuid : Option < String > ,
59+ pub children : Option < Vec < Device > > ,
60+ }
61+
62+ impl Device {
63+ pub fn lsblk ( dev : & Path , with_deps : bool ) -> Result < Vec < Device > > {
64+ let mut cmd = Command :: new ( "lsblk" ) ;
65+ cmd. args ( & [
66+ "-J" ,
67+ "--paths" ,
68+ "-o" ,
69+ "NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID" ,
70+ ] )
71+ . arg ( dev) ;
72+ if !with_deps {
73+ cmd. arg ( "--nodeps" ) ;
74+ }
75+ let output = cmd_output ( & mut cmd) ?;
76+ let devs: DevicesOutput = serde_json:: from_str ( & output) ?;
77+ Ok ( devs. blockdevices )
78+ }
79+ }
80+
4581#[ derive( Debug ) ]
4682pub struct Disk {
4783 pub path : String ,
@@ -105,37 +141,58 @@ impl Disk {
105141 }
106142 }
107143
144+ fn validate_blktype ( & self , blktype : & Option < String > , with_holders : bool ) -> bool {
145+ if let Some ( blktype) = & blktype {
146+ match blktype. as_str ( ) {
147+ // If whole-disk device, skip.
148+ "disk" => false ,
149+ // If partition, allow.
150+ "part" => true ,
151+ // If with_holders is true, allow anything else.
152+ _ if with_holders => true ,
153+ // Default case
154+ _ => false ,
155+ }
156+ } else {
157+ false
158+ }
159+ }
160+
161+ fn compute_partition ( & self , devinfo : & Device , with_holders : bool ) -> Result < Vec < Partition > > {
162+ let mut result: Vec < Partition > = Vec :: new ( ) ;
163+ let isblktypeavailable: bool = self . validate_blktype ( & devinfo. blktype , with_holders) ;
164+ if isblktypeavailable {
165+ let ( mountpoint, swap) = match & devinfo. mountpoint {
166+ Some ( mp) if mp == "[SWAP]" => ( None , true ) ,
167+ Some ( mp) => ( Some ( mp. to_string ( ) ) , false ) ,
168+ None => ( None , false ) ,
169+ } ;
170+ result. push ( Partition {
171+ path : devinfo. name . to_owned ( ) ,
172+ label : devinfo. label . clone ( ) ,
173+ fstype : devinfo. fstype . clone ( ) ,
174+ parent : self . path . to_owned ( ) ,
175+ mountpoint,
176+ swap,
177+ } ) ;
178+ }
179+ Ok ( result)
180+ }
181+
108182 fn get_partitions ( & self , with_holders : bool ) -> Result < Vec < Partition > > {
109183 // walk each device in the output
110184 let mut result: Vec < Partition > = Vec :: new ( ) ;
111- for devinfo in lsblk ( Path :: new ( & self . path ) , true ) ? {
112- if let Some ( name) = devinfo. get ( "NAME" ) {
113- match devinfo. get ( "TYPE" ) {
114- // If unknown type, skip.
115- None => continue ,
116- // If whole-disk device, skip.
117- Some ( t) if t == & "disk" . to_string ( ) => continue ,
118- // If partition, allow.
119- Some ( t) if t == & "part" . to_string ( ) => ( ) ,
120- // If with_holders is true, allow anything else.
121- Some ( _) if with_holders => ( ) ,
122- // Ignore LVM or RAID devices which are using one of the
123- // partitions but aren't a partition themselves.
124- _ => continue ,
125- } ;
126- let ( mountpoint, swap) = match devinfo. get ( "MOUNTPOINT" ) {
127- Some ( mp) if mp == "[SWAP]" => ( None , true ) ,
128- Some ( mp) => ( Some ( mp. to_string ( ) ) , false ) ,
129- None => ( None , false ) ,
130- } ;
131- result. push ( Partition {
132- path : name. to_owned ( ) ,
133- label : devinfo. get ( "LABEL" ) . map ( <_ >:: to_string) ,
134- fstype : devinfo. get ( "FSTYPE" ) . map ( <_ >:: to_string) ,
135- parent : self . path . to_owned ( ) ,
136- mountpoint,
137- swap,
138- } ) ;
185+ let deviceinfos: Vec < Device > = Device :: lsblk ( Path :: new ( & self . path ) , true ) ?;
186+ for deviceinfo in deviceinfos {
187+ let mut partition = self . compute_partition ( & deviceinfo, with_holders) ?;
188+ result. append ( & mut partition) ;
189+ if let Some ( children) = deviceinfo. children . as_ref ( ) . clone ( ) {
190+ if children. len ( ) > 0 {
191+ for child in children {
192+ let mut childpartitions = self . compute_partition ( child, with_holders) ?;
193+ result. append ( & mut childpartitions)
194+ }
195+ }
139196 }
140197 }
141198 Ok ( result)
@@ -506,11 +563,12 @@ impl Mount {
506563 }
507564
508565 pub fn get_filesystem_uuid ( & self ) -> Result < String > {
509- let devinfo = lsblk_single ( Path :: new ( & self . device ) ) ?;
510- devinfo
511- . get ( "UUID" )
512- . map ( String :: from)
513- . with_context ( || format ! ( "filesystem {} has no UUID" , self . device) )
566+ let devinfo = Device :: lsblk ( Path :: new ( & self . device ) , false ) ?;
567+ let uuid = devinfo[ 0 ]
568+ . uuid
569+ . as_ref ( )
570+ . ok_or_else ( || anyhow:: anyhow!( "missing uuid in {}" , devinfo[ 0 ] . name) ) ?;
571+ Ok ( uuid. to_string ( ) )
514572 }
515573}
516574
@@ -813,46 +871,6 @@ fn read_sysfs_dev_block_value(maj: u64, min: u64, field: &str) -> Result<String>
813871 Ok ( read_to_string ( & path) ?. trim_end ( ) . into ( ) )
814872}
815873
816- pub fn lsblk_single ( dev : & Path ) -> Result < HashMap < String , String > > {
817- let mut devinfos = lsblk ( Path :: new ( dev) , false ) ?;
818- if devinfos. is_empty ( ) {
819- // this should never happen because `lsblk` itself would've failed
820- bail ! ( "no lsblk results for {}" , dev. display( ) ) ;
821- }
822- Ok ( devinfos. remove ( 0 ) )
823- }
824-
825- pub fn lsblk ( dev : & Path , with_deps : bool ) -> Result < Vec < HashMap < String , String > > > {
826- let mut cmd = Command :: new ( "lsblk" ) ;
827- // Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
828- cmd. arg ( "--pairs" )
829- . arg ( "--paths" )
830- . arg ( "--output" )
831- . arg ( "NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID" )
832- . arg ( dev) ;
833- if !with_deps {
834- cmd. arg ( "--nodeps" ) ;
835- }
836- let output = cmd_output ( & mut cmd) ?;
837- let mut result: Vec < HashMap < String , String > > = Vec :: new ( ) ;
838- for line in output. lines ( ) {
839- // parse key-value pairs
840- result. push ( split_lsblk_line ( line) ) ;
841- }
842- Ok ( result)
843- }
844-
845- /// Parse key-value pairs from lsblk --pairs.
846- /// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
847- fn split_lsblk_line ( line : & str ) -> HashMap < String , String > {
848- let re = Regex :: new ( r#"([A-Z-]+)="([^"]+)""# ) . unwrap ( ) ;
849- let mut fields: HashMap < String , String > = HashMap :: new ( ) ;
850- for cap in re. captures_iter ( line) {
851- fields. insert ( cap[ 1 ] . to_string ( ) , cap[ 2 ] . to_string ( ) ) ;
852- }
853- fields
854- }
855-
856874pub fn get_blkdev_deps ( device : & Path ) -> Result < Vec < PathBuf > > {
857875 let deps = {
858876 let mut p = PathBuf :: from ( "/sys/block" ) ;
@@ -1041,46 +1059,10 @@ mod ioctl {
10411059#[ cfg( test) ]
10421060mod tests {
10431061 use super :: * ;
1044- use maplit:: hashmap;
10451062 use std:: io:: copy;
10461063 use tempfile:: tempfile;
10471064 use xz2:: read:: XzDecoder ;
10481065
1049- #[ test]
1050- fn lsblk_split ( ) {
1051- assert_eq ! (
1052- split_lsblk_line( r#"NAME="sda" LABEL="" FSTYPE="""# ) ,
1053- hashmap! {
1054- String :: from( "NAME" ) => String :: from( "sda" ) ,
1055- }
1056- ) ;
1057- assert_eq ! (
1058- split_lsblk_line( r#"NAME="sda1" LABEL="" FSTYPE="vfat""# ) ,
1059- hashmap! {
1060- String :: from( "NAME" ) => String :: from( "sda1" ) ,
1061- String :: from( "FSTYPE" ) => String :: from( "vfat" )
1062- }
1063- ) ;
1064- assert_eq ! (
1065- split_lsblk_line( r#"NAME="sda2" LABEL="boot" FSTYPE="ext4""# ) ,
1066- hashmap! {
1067- String :: from( "NAME" ) => String :: from( "sda2" ) ,
1068- String :: from( "LABEL" ) => String :: from( "boot" ) ,
1069- String :: from( "FSTYPE" ) => String :: from( "ext4" ) ,
1070- }
1071- ) ;
1072- assert_eq ! (
1073- split_lsblk_line( r#"NAME="sda3" LABEL="foo=\x22bar\x22 baz" FSTYPE="ext4""# ) ,
1074- hashmap! {
1075- String :: from( "NAME" ) => String :: from( "sda3" ) ,
1076- // for now, we don't care about resolving lsblk's hex escapes,
1077- // so we just pass them through
1078- String :: from( "LABEL" ) => String :: from( r#"foo=\x22bar\x22 baz"# ) ,
1079- String :: from( "FSTYPE" ) => String :: from( "ext4" ) ,
1080- }
1081- ) ;
1082- }
1083-
10841066 #[ test]
10851067 fn disk_sector_size_reader ( ) {
10861068 struct Test {
0 commit comments