1 use anyhow
::{bail, Error}
;
4 multispace0
, multispace1
, notspace1
, IResult
,
8 bytes
::complete
::{take_while1, take_till, take_till1}
,
9 combinator
::{map_res, all_consuming, recognize, opt}
,
10 sequence
::{preceded, tuple}
,
11 character
::complete
::{digit1, char, line_ending}
,
15 #[derive(Debug, PartialEq)]
16 pub struct ZFSPoolUsage
{
24 #[derive(Debug, PartialEq)]
25 pub struct ZFSPoolInfo
{
28 pub usage
: Option
<ZFSPoolUsage
>,
29 pub devices
: Vec
<String
>,
33 fn parse_optional_u64(i
: &str) -> IResult
<&str, Option
<u64>> {
34 if let Some(rest
) = i
.strip_prefix('
-'
) {
37 let (i
, value
) = map_res(recognize(digit1
), str::parse
)(i
)?
;
42 fn parse_optional_f64(i
: &str) -> IResult
<&str, Option
<f64>> {
43 if let Some(rest
) = i
.strip_prefix('
-'
) {
46 let (i
, value
) = nom
::number
::complete
::double(i
)?
;
51 fn parse_pool_device(i
: &str) -> IResult
<&str, String
> {
52 let (i
, (device
, _
, _rest
)) = tuple((
53 preceded(multispace1
, take_till1(|c
| c
== ' '
|| c
== '
\t'
)),
55 preceded(take_till(|c
| c
== '
\n'
), char('
\n'
)),
58 Ok((i
, device
.to_string()))
61 fn parse_zpool_list_header(i
: &str) -> IResult
<&str, ZFSPoolInfo
> {
62 // name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot.
64 let (i
, (text
, size
, alloc
, free
, _
, _
,
65 frag
, _
, dedup
, health
,
66 _altroot
, _eol
)) = tuple((
67 take_while1(|c
| char::is_alphanumeric(c
) || c
== '
-'
|| c
== '
:'
|| c
== '_'
|| c
== '
.'
), // name
68 preceded(multispace1
, parse_optional_u64
), // size
69 preceded(multispace1
, parse_optional_u64
), // allocated
70 preceded(multispace1
, parse_optional_u64
), // free
71 preceded(multispace1
, notspace1
), // checkpoint
72 preceded(multispace1
, notspace1
), // expandsize
73 preceded(multispace1
, parse_optional_u64
), // fragmentation
74 preceded(multispace1
, notspace1
), // capacity
75 preceded(multispace1
, parse_optional_f64
), // dedup
76 preceded(multispace1
, notspace1
), // health
77 opt(preceded(multispace1
, notspace1
)), // optional altroot
81 let status
= if let (Some(size
), Some(alloc
), Some(free
), Some(frag
), Some(dedup
)) = (size
, alloc
, free
, frag
, dedup
) {
84 health
: health
.into(),
85 usage
: Some(ZFSPoolUsage { size, alloc, free, frag, dedup }
),
91 health
: health
.into(),
100 fn parse_zpool_list_item(i
: &str) -> IResult
<&str, ZFSPoolInfo
> {
102 let (i
, mut stat
) = parse_zpool_list_header(i
)?
;
103 let (i
, devices
) = many0(parse_pool_device
)(i
)?
;
105 for device_path
in devices
.into_iter().filter(|n
| n
.starts_with("/dev/")) {
106 stat
.devices
.push(device_path
);
109 let (i
, _
) = many0(tuple((multispace0
, char('
\n'
))))(i
)?
; // skip empty lines
114 /// Parse zpool list output
116 /// Note: This does not reveal any details on how the pool uses the devices, because
117 /// the zpool list output format is not really defined...
118 fn parse_zpool_list(i
: &str) -> Result
<Vec
<ZFSPoolInfo
>, Error
> {
119 match all_consuming(many0(parse_zpool_list_item
))(i
) {
120 Err(nom
::Err
::Error(err
)) |
121 Err(nom
::Err
::Failure(err
)) => {
122 bail
!("unable to parse zfs list output - {}", nom
::error
::convert_error(i
, err
));
125 bail
!("unable to parse zfs list output - {}", err
);
127 Ok((_
, ce
)) => Ok(ce
),
131 /// Run zpool list and return parsed output
133 /// Devices are only included when run with verbose flags
134 /// set. Without, device lists are empty.
135 pub fn zpool_list(pool
: Option
<String
>, verbose
: bool
) -> Result
<Vec
<ZFSPoolInfo
>, Error
> {
137 // Note: zpools list verbose output can include entries for 'special', 'cache' and 'logs'
138 // and maybe other things.
140 let mut command
= std
::process
::Command
::new("zpool");
141 command
.args(&["list", "-H", "-p", "-P"]);
143 // Note: We do not use -o to define output properties, because zpool command ignores
144 // that completely for special vdevs and devices
146 if verbose { command.arg("-v"); }
148 if let Some(pool
) = pool { command.arg(pool); }
150 let output
= proxmox_sys
::command
::run_command(command
, None
)?
;
152 parse_zpool_list(&output
)
156 fn test_zfs_parse_list() -> Result
<(), Error
> {
160 let data
= parse_zpool_list(output
)?
;
161 let expect
= Vec
::new();
163 assert_eq
!(data
, expect
);
165 let output
= "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n";
166 let data
= parse_zpool_list(output
)?
;
169 name
: "btest".to_string(),
170 health
: "ONLINE".to_string(),
172 usage
: Some(ZFSPoolUsage
{
181 assert_eq
!(data
, expect
);
184 rpool 535260299264 402852388864 132407910400 - - 22 75 1.00 ONLINE -
185 /dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3 498216206336 392175546368 106040659968 - - 22 78 - ONLINE
186 special - - - - - - - - -
187 /dev/sda2 37044092928 10676842496 26367250432 - - 63 28 - ONLINE
188 logs - - - - - - - - -
189 /dev/sda3 4831838208 1445888 4830392320 - - 0 0 - ONLINE
193 let data
= parse_zpool_list(output
)?
;
196 name
: String
::from("rpool"),
197 health
: String
::from("ONLINE"),
198 devices
: vec
![String
::from("/dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3")],
199 usage
: Some(ZFSPoolUsage
{
208 name
: String
::from("special"),
209 health
: String
::from("-"),
210 devices
: vec
![String
::from("/dev/sda2")],
214 name
: String
::from("logs"),
215 health
: String
::from("-"),
216 devices
: vec
![String
::from("/dev/sda3")],
221 assert_eq
!(data
, expect
);
224 b-test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
225 mirror 213674622976 438272 213674184704 - - 0 0 - ONLINE
226 /dev/sda1 - - - - - - - - ONLINE
227 /dev/sda2 - - - - - - - - ONLINE
228 mirror 213674622976 323584 213674299392 - - 0 0 - ONLINE
229 /dev/sda3 - - - - - - - - ONLINE
230 /dev/sda4 - - - - - - - - ONLINE
231 logs - - - - - - - - -
232 /dev/sda5 213674622976 0 213674622976 - - 0 0 - ONLINE
235 let data
= parse_zpool_list(output
)?
;
238 name
: String
::from("b-test"),
239 health
: String
::from("ONLINE"),
240 usage
: Some(ZFSPoolUsage
{
248 String
::from("/dev/sda1"),
249 String
::from("/dev/sda2"),
250 String
::from("/dev/sda3"),
251 String
::from("/dev/sda4"),
255 name
: String
::from("logs"),
256 health
: String
::from("-"),
258 devices
: vec
![String
::from("/dev/sda5")],
262 assert_eq
!(data
, expect
);
265 b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
266 mirror 213674622976 438272 213674184704 - - 0 0 - ONLINE
267 /dev/sda1 - - - - - - - - ONLINE
270 let data
= parse_zpool_list(output
)?
;
273 name
: String
::from("b.test"),
274 health
: String
::from("ONLINE"),
275 usage
: Some(ZFSPoolUsage
{
283 String
::from("/dev/sda1"),
288 assert_eq
!(data
, expect
);