@@ -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,47 @@ 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_children : bool ) -> Result < 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_children {
73+ cmd. arg ( "--nodeps" ) ;
74+ }
75+ let output = cmd_output ( & mut cmd) ?;
76+ let devs: DevicesOutput = serde_json:: from_str ( & output) ?;
77+ let devinfo = devs
78+ . blockdevices
79+ . into_iter ( )
80+ . next ( )
81+ . ok_or_else ( || anyhow ! ( "failed to get device information" ) ) ?;
82+ Ok ( devinfo)
83+ }
84+ }
85+
4586#[ derive( Debug ) ]
4687pub struct Disk {
4788 pub path : String ,
@@ -105,37 +146,72 @@ impl Disk {
105146 }
106147 }
107148
149+ fn validate_blktype ( & self , blktype : & Option < String > , with_holders : bool ) -> bool {
150+ if let Some ( blktype) = & blktype {
151+ match blktype. as_str ( ) {
152+ // If whole-disk device, skip.
153+ "disk" => false ,
154+ // If partition, allow.
155+ "part" => true ,
156+ // If with_holders is true, allow anything else.
157+ _ if with_holders => true ,
158+ // Default case
159+ _ => false ,
160+ }
161+ } else {
162+ false
163+ }
164+ }
165+
166+ fn compute_partition ( & self , devinfo : & Device , with_holders : bool ) -> Result < Vec < Partition > > {
167+ let mut result: Vec < Partition > = Vec :: new ( ) ;
168+ let isblktypeavailable: bool = self . validate_blktype ( & devinfo. blktype , with_holders) ;
169+ if isblktypeavailable {
170+ let ( mountpoint, swap) = match & devinfo. mountpoint {
171+ Some ( mp) if mp == "[SWAP]" => ( None , true ) ,
172+ Some ( mp) => ( Some ( mp. to_string ( ) ) , false ) ,
173+ None => ( None , false ) ,
174+ } ;
175+ result. push ( Partition {
176+ path : devinfo. name . to_owned ( ) ,
177+ label : devinfo. label . clone ( ) ,
178+ fstype : devinfo. fstype . clone ( ) ,
179+ parent : self . path . to_owned ( ) ,
180+ mountpoint,
181+ swap,
182+ } ) ;
183+ }
184+ Ok ( result)
185+ }
186+
108187 fn get_partitions ( & self , with_holders : bool ) -> Result < Vec < Partition > > {
109188 // walk each device in the output
110189 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- } ) ;
190+ let deviceinfo = Device :: lsblk ( Path :: new ( & self . path ) , true ) ?;
191+ let mut partition = self . compute_partition ( & deviceinfo, with_holders) ?;
192+ if !partition. is_empty ( ) {
193+ result. append ( & mut partition) ;
194+ }
195+ let mut childpartitioninfo = self . get_partitions_recurse ( & deviceinfo, with_holders) ?;
196+ if !childpartitioninfo. is_empty ( ) {
197+ result. append ( & mut childpartitioninfo) ;
198+ }
199+ Ok ( result)
200+ }
201+
202+ fn get_partitions_recurse (
203+ & self ,
204+ deviceinfo : & Device ,
205+ with_holders : bool ,
206+ ) -> Result < Vec < Partition > > {
207+ let mut result: Vec < Partition > = Vec :: new ( ) ;
208+ if let Some ( children) = deviceinfo. children . as_ref ( ) {
209+ if !children. is_empty ( ) {
210+ for child in children {
211+ let mut childpartitions = self . compute_partition ( child, with_holders) ?;
212+ result. append ( & mut childpartitions) ;
213+ self . get_partitions_recurse ( child, with_holders) ?;
214+ }
139215 }
140216 }
141217 Ok ( result)
@@ -506,11 +582,12 @@ impl Mount {
506582 }
507583
508584 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) )
585+ let devinfo = Device :: lsblk ( Path :: new ( & self . device ) , false ) ?;
586+ let uuid = devinfo
587+ . uuid
588+ . as_ref ( )
589+ . ok_or_else ( || anyhow ! ( "missing uuid in {}" , devinfo. name) ) ?;
590+ Ok ( uuid. to_string ( ) )
514591 }
515592}
516593
@@ -813,46 +890,6 @@ fn read_sysfs_dev_block_value(maj: u64, min: u64, field: &str) -> Result<String>
813890 Ok ( read_to_string ( & path) ?. trim_end ( ) . into ( ) )
814891}
815892
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-
856893pub fn get_blkdev_deps ( device : & Path ) -> Result < Vec < PathBuf > > {
857894 let deps = {
858895 let mut p = PathBuf :: from ( "/sys/block" ) ;
@@ -1041,46 +1078,10 @@ mod ioctl {
10411078#[ cfg( test) ]
10421079mod tests {
10431080 use super :: * ;
1044- use maplit:: hashmap;
10451081 use std:: io:: copy;
10461082 use tempfile:: tempfile;
10471083 use xz2:: read:: XzDecoder ;
10481084
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-
10841085 #[ test]
10851086 fn disk_sector_size_reader ( ) {
10861087 struct Test {
0 commit comments