@@ -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,50 @@ 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+ if devs. blockdevices . len ( ) > 1 {
78+ bail ! ( "found more than one device for {:?}" , dev) ;
79+ }
80+ let devinfo = devs
81+ . blockdevices
82+ . into_iter ( )
83+ . next ( )
84+ . ok_or_else ( || anyhow ! ( "failed to get device information" ) ) ?;
85+ Ok ( devinfo)
86+ }
87+ }
88+
4589#[ derive( Debug ) ]
4690pub struct Disk {
4791 pub path : String ,
@@ -100,29 +144,45 @@ impl Disk {
100144 }
101145 }
102146
147+ fn compute_partition ( & self , devinfo : & Device ) -> Result < Vec < Partition > > {
148+ let mut result: Vec < Partition > = Vec :: new ( ) ;
149+ // Only return partitions. Skip the whole-disk device, as well
150+ // as holders like LVM or RAID devices using one of the partitions.
151+ if !( devinfo. blktype != Some ( "part" . to_string ( ) ) ) {
152+ let ( mountpoint, swap) = match & devinfo. mountpoint {
153+ Some ( mp) if mp == "[SWAP]" => ( None , true ) ,
154+ Some ( mp) => ( Some ( mp. to_string ( ) ) , false ) ,
155+ None => ( None , false ) ,
156+ } ;
157+ result. push ( Partition {
158+ path : devinfo. name . to_owned ( ) ,
159+ label : devinfo. label . clone ( ) ,
160+ fstype : devinfo. fstype . clone ( ) ,
161+ parent : self . path . to_owned ( ) ,
162+ mountpoint,
163+ swap,
164+ } ) ;
165+ }
166+
167+ Ok ( result)
168+ }
169+
103170 fn get_partitions ( & self ) -> Result < Vec < Partition > > {
104171 // walk each device in the output
105172 let mut result: Vec < Partition > = Vec :: new ( ) ;
106- for devinfo in lsblk ( Path :: new ( & self . path ) , true ) ? {
107- if let Some ( name) = devinfo. get ( "NAME" ) {
108- // Only return partitions. Skip the whole-disk device, as well
109- // as holders like LVM or RAID devices using one of the partitions.
110- if devinfo. get ( "TYPE" ) . map ( |s| s. as_str ( ) ) != Some ( "part" ) {
111- continue ;
173+ let deviceinfo = Device :: lsblk ( Path :: new ( & self . path ) , true ) ?;
174+ let mut partition = self . compute_partition ( & deviceinfo) ?;
175+ if !partition. is_empty ( ) {
176+ result. append ( & mut partition) ;
177+ }
178+ if let Some ( children) = deviceinfo. children . as_ref ( ) {
179+ if !children. is_empty ( ) {
180+ for child in children {
181+ let mut childpartition = self . compute_partition ( child) ?;
182+ if !childpartition. is_empty ( ) {
183+ result. append ( & mut childpartition) ;
184+ }
112185 }
113- let ( mountpoint, swap) = match devinfo. get ( "MOUNTPOINT" ) {
114- Some ( mp) if mp == "[SWAP]" => ( None , true ) ,
115- Some ( mp) => ( Some ( mp. to_string ( ) ) , false ) ,
116- None => ( None , false ) ,
117- } ;
118- result. push ( Partition {
119- path : name. to_owned ( ) ,
120- label : devinfo. get ( "LABEL" ) . map ( <_ >:: to_string) ,
121- fstype : devinfo. get ( "FSTYPE" ) . map ( <_ >:: to_string) ,
122- parent : self . path . to_owned ( ) ,
123- mountpoint,
124- swap,
125- } ) ;
126186 }
127187 }
128188 Ok ( result)
@@ -493,11 +553,10 @@ impl Mount {
493553 }
494554
495555 pub fn get_filesystem_uuid ( & self ) -> Result < String > {
496- let devinfo = lsblk_single ( Path :: new ( & self . device ) ) ?;
497- devinfo
498- . get ( "UUID" )
499- . map ( String :: from)
500- . with_context ( || format ! ( "filesystem {} has no UUID" , self . device) )
556+ let uuid = Device :: lsblk ( Path :: new ( & self . device ) , false ) ?
557+ . uuid
558+ . ok_or_else ( || anyhow ! ( "failed to get uuid" ) ) ?;
559+ Ok ( uuid)
501560 }
502561}
503562
@@ -800,46 +859,6 @@ fn read_sysfs_dev_block_value(maj: u64, min: u64, field: &str) -> Result<String>
800859 Ok ( read_to_string ( & path) ?. trim_end ( ) . into ( ) )
801860}
802861
803- pub fn lsblk_single ( dev : & Path ) -> Result < HashMap < String , String > > {
804- let mut devinfos = lsblk ( Path :: new ( dev) , false ) ?;
805- if devinfos. is_empty ( ) {
806- // this should never happen because `lsblk` itself would've failed
807- bail ! ( "no lsblk results for {}" , dev. display( ) ) ;
808- }
809- Ok ( devinfos. remove ( 0 ) )
810- }
811-
812- pub fn lsblk ( dev : & Path , with_deps : bool ) -> Result < Vec < HashMap < String , String > > > {
813- let mut cmd = Command :: new ( "lsblk" ) ;
814- // Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
815- cmd. arg ( "--pairs" )
816- . arg ( "--paths" )
817- . arg ( "--output" )
818- . arg ( "NAME,LABEL,FSTYPE,TYPE,MOUNTPOINT,UUID" )
819- . arg ( dev) ;
820- if !with_deps {
821- cmd. arg ( "--nodeps" ) ;
822- }
823- let output = cmd_output ( & mut cmd) ?;
824- let mut result: Vec < HashMap < String , String > > = Vec :: new ( ) ;
825- for line in output. lines ( ) {
826- // parse key-value pairs
827- result. push ( split_lsblk_line ( line) ) ;
828- }
829- Ok ( result)
830- }
831-
832- /// Parse key-value pairs from lsblk --pairs.
833- /// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
834- fn split_lsblk_line ( line : & str ) -> HashMap < String , String > {
835- let re = Regex :: new ( r#"([A-Z-]+)="([^"]+)""# ) . unwrap ( ) ;
836- let mut fields: HashMap < String , String > = HashMap :: new ( ) ;
837- for cap in re. captures_iter ( line) {
838- fields. insert ( cap[ 1 ] . to_string ( ) , cap[ 2 ] . to_string ( ) ) ;
839- }
840- fields
841- }
842-
843862pub fn get_blkdev_deps ( device : & Path ) -> Result < Vec < PathBuf > > {
844863 let deps = {
845864 let mut p = PathBuf :: from ( "/sys/block" ) ;
@@ -1028,46 +1047,10 @@ mod ioctl {
10281047#[ cfg( test) ]
10291048mod tests {
10301049 use super :: * ;
1031- use maplit:: hashmap;
10321050 use std:: io:: copy;
10331051 use tempfile:: tempfile;
10341052 use xz2:: read:: XzDecoder ;
10351053
1036- #[ test]
1037- fn lsblk_split ( ) {
1038- assert_eq ! (
1039- split_lsblk_line( r#"NAME="sda" LABEL="" FSTYPE="""# ) ,
1040- hashmap! {
1041- String :: from( "NAME" ) => String :: from( "sda" ) ,
1042- }
1043- ) ;
1044- assert_eq ! (
1045- split_lsblk_line( r#"NAME="sda1" LABEL="" FSTYPE="vfat""# ) ,
1046- hashmap! {
1047- String :: from( "NAME" ) => String :: from( "sda1" ) ,
1048- String :: from( "FSTYPE" ) => String :: from( "vfat" )
1049- }
1050- ) ;
1051- assert_eq ! (
1052- split_lsblk_line( r#"NAME="sda2" LABEL="boot" FSTYPE="ext4""# ) ,
1053- hashmap! {
1054- String :: from( "NAME" ) => String :: from( "sda2" ) ,
1055- String :: from( "LABEL" ) => String :: from( "boot" ) ,
1056- String :: from( "FSTYPE" ) => String :: from( "ext4" ) ,
1057- }
1058- ) ;
1059- assert_eq ! (
1060- split_lsblk_line( r#"NAME="sda3" LABEL="foo=\x22bar\x22 baz" FSTYPE="ext4""# ) ,
1061- hashmap! {
1062- String :: from( "NAME" ) => String :: from( "sda3" ) ,
1063- // for now, we don't care about resolving lsblk's hex escapes,
1064- // so we just pass them through
1065- String :: from( "LABEL" ) => String :: from( r#"foo=\x22bar\x22 baz"# ) ,
1066- String :: from( "FSTYPE" ) => String :: from( "ext4" ) ,
1067- }
1068- ) ;
1069- }
1070-
10711054 #[ test]
10721055 fn disk_sector_size_reader ( ) {
10731056 struct Test {
0 commit comments