@@ -4,7 +4,10 @@ use anyhow::{anyhow, Context, Result};
4
4
use camino:: Utf8Path ;
5
5
use fn_error_context:: context;
6
6
use nix:: errno:: Errno ;
7
+ use once_cell:: sync:: Lazy ;
8
+ use regex:: Regex ;
7
9
use serde:: Deserialize ;
10
+ use std:: collections:: HashMap ;
8
11
use std:: fs:: File ;
9
12
use std:: os:: unix:: io:: AsRawFd ;
10
13
use std:: process:: Command ;
@@ -39,7 +42,7 @@ impl Device {
39
42
40
43
pub ( crate ) fn wipefs ( dev : & Utf8Path ) -> Result < ( ) > {
41
44
Task :: new_and_run (
42
- & format ! ( "Wiping device {dev}" ) ,
45
+ format ! ( "Wiping device {dev}" ) ,
43
46
"wipefs" ,
44
47
[ "-a" , dev. as_str ( ) ] ,
45
48
)
@@ -109,6 +112,67 @@ pub(crate) fn reread_partition_table(file: &mut File, retry: bool) -> Result<()>
109
112
Ok ( ( ) )
110
113
}
111
114
115
+ /// Runs the provided Command object, captures its stdout, and swallows its stderr except on
116
+ /// failure. Returns a Result<String> describing whether the command failed, and if not, its
117
+ /// standard output. Output is assumed to be UTF-8. Errors are adequately prefixed with the full
118
+ /// command.
119
+ pub ( crate ) fn cmd_output ( cmd : & mut Command ) -> Result < String > {
120
+ let result = cmd
121
+ . output ( )
122
+ . with_context ( || format ! ( "running {:#?}" , cmd) ) ?;
123
+ if !result. status . success ( ) {
124
+ eprint ! ( "{}" , String :: from_utf8_lossy( & result. stderr) ) ;
125
+ anyhow:: bail!( "{:#?} failed with {}" , cmd, result. status) ;
126
+ }
127
+ String :: from_utf8 ( result. stdout )
128
+ . with_context ( || format ! ( "decoding as UTF-8 output of `{:#?}`" , cmd) )
129
+ }
130
+
131
+ /// Parse key-value pairs from lsblk --pairs.
132
+ /// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
133
+ fn split_lsblk_line ( line : & str ) -> HashMap < String , String > {
134
+ static REGEX : Lazy < Regex > = Lazy :: new ( || Regex :: new ( r#"([A-Z-_]+)="([^"]+)""# ) . unwrap ( ) ) ;
135
+ let mut fields: HashMap < String , String > = HashMap :: new ( ) ;
136
+ for cap in REGEX . captures_iter ( line) {
137
+ fields. insert ( cap[ 1 ] . to_string ( ) , cap[ 2 ] . to_string ( ) ) ;
138
+ }
139
+ fields
140
+ }
141
+
142
+ /// This is a bit fuzzy, but... this function will return every block device in the parent
143
+ /// hierarchy of `device` capable of containing other partitions. So e.g. parent devices of type
144
+ /// "part" doesn't match, but "disk" and "mpath" does.
145
+ pub ( crate ) fn find_parent_devices ( device : & str ) -> Result < Vec < String > > {
146
+ let mut cmd = Command :: new ( "lsblk" ) ;
147
+ // Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
148
+ cmd. arg ( "--pairs" )
149
+ . arg ( "--paths" )
150
+ . arg ( "--inverse" )
151
+ . arg ( "--output" )
152
+ . arg ( "NAME,TYPE" )
153
+ . arg ( device) ;
154
+ let output = cmd_output ( & mut cmd) ?;
155
+ let mut parents = Vec :: new ( ) ;
156
+ // skip first line, which is the device itself
157
+ for line in output. lines ( ) . skip ( 1 ) {
158
+ let dev = split_lsblk_line ( line) ;
159
+ let name = dev
160
+ . get ( "NAME" )
161
+ . with_context ( || format ! ( "device in hierarchy of {device} missing NAME" ) ) ?;
162
+ let kind = dev
163
+ . get ( "TYPE" )
164
+ . with_context ( || format ! ( "device in hierarchy of {device} missing TYPE" ) ) ?;
165
+ if kind == "disk" {
166
+ parents. push ( name. clone ( ) ) ;
167
+ } else if kind == "mpath" {
168
+ parents. push ( name. clone ( ) ) ;
169
+ // we don't need to know what disks back the multipath
170
+ break ;
171
+ }
172
+ }
173
+ Ok ( parents)
174
+ }
175
+
112
176
// create unsafe ioctl wrappers
113
177
#[ allow( clippy:: missing_safety_doc) ]
114
178
mod ioctl {
0 commit comments