1 use anyhow
::{bail, format_err, Error}
;
2 use serde
::{Deserialize, Serialize}
;
5 use crate::tools
::nom
::{
6 parse_complete
, parse_error
, parse_failure
,
7 multispace0
, multispace1
, notspace1
, parse_u64
, IResult
,
11 bytes
::complete
::{tag, take_while, take_while1}
,
14 character
::complete
::{line_ending}
,
19 #[derive(Debug, Serialize, Deserialize)]
20 pub struct ZFSPoolVDevState
{
23 #[serde(skip_serializing_if="Option::is_none")]
24 pub state
: Option
<String
>,
25 #[serde(skip_serializing_if="Option::is_none")]
26 pub read
: Option
<u64>,
27 #[serde(skip_serializing_if="Option::is_none")]
28 pub write
: Option
<u64>,
29 #[serde(skip_serializing_if="Option::is_none")]
30 pub cksum
: Option
<u64>,
31 #[serde(skip_serializing_if="Option::is_none")]
32 pub msg
: Option
<String
>,
35 fn expand_tab_length(input
: &str) -> usize {
36 input
.chars().map(|c
| if c
== '
\t' { 8 }
else { 1 }
).sum()
39 fn parse_zpool_status_vdev(i
: &str) -> IResult
<&str, ZFSPoolVDevState
> {
41 let (n
, indent
) = multispace0(i
)?
;
43 let indent_len
= expand_tab_length(indent
);
45 if (indent_len
& 1) != 0 {
46 return Err(parse_failure(n
, "wrong indent length"));
50 let indent_level
= (indent_len
as u64)/2;
52 let (i
, vdev_name
) = notspace1(i
)?
;
54 if let Ok((n
, _
)) = preceded(multispace0
, line_ending
)(i
) { // sepecial device
55 let vdev
= ZFSPoolVDevState
{
56 name
: vdev_name
.to_string(),
67 let (i
, state
) = preceded(multispace1
, notspace1
)(i
)?
;
68 let (i
, read
) = preceded(multispace1
, parse_u64
)(i
)?
;
69 let (i
, write
) = preceded(multispace1
, parse_u64
)(i
)?
;
70 let (i
, cksum
) = preceded(multispace1
, parse_u64
)(i
)?
;
71 let (i
, msg
) = opt(preceded(multispace1
, take_while(|c
| c
!= '
\n'
)))(i
)?
;
72 let (i
, _
) = line_ending(i
)?
;
74 let vdev
= ZFSPoolVDevState
{
75 name
: vdev_name
.to_string(),
77 state
: Some(state
.to_string()),
81 msg
: msg
.map(String
::from
),
87 fn parse_zpool_status_tree(i
: &str) -> IResult
<&str, Vec
<ZFSPoolVDevState
>> {
90 let (i
, _
) = tag("NAME")(i
)?
;
91 let (i
, _
) = multispace1(i
)?
;
92 let (i
, _
) = tag("STATE")(i
)?
;
93 let (i
, _
) = multispace1(i
)?
;
94 let (i
, _
) = tag("READ")(i
)?
;
95 let (i
, _
) = multispace1(i
)?
;
96 let (i
, _
) = tag("WRITE")(i
)?
;
97 let (i
, _
) = multispace1(i
)?
;
98 let (i
, _
) = tag("CKSUM")(i
)?
;
99 let (i
, _
) = line_ending(i
)?
;
102 many1(parse_zpool_status_vdev
)(i
)
105 fn space_indented_line(indent
: usize) -> impl Fn(&str) -> IResult
<&str, &str> {
110 if n
.starts_with('
\t'
) {
113 } else if n
.starts_with(' '
) {
119 if len
>= indent { break; }
122 return Err(parse_error(i
, "not correctly indented"));
125 take_while1(|c
| c
!= '
\n'
)(n
)
129 fn parse_zpool_status_field(i
: &str) -> IResult
<&str, (String
, String
)> {
130 let (i
, prefix
) = take_while1(|c
| c
!= '
:'
)(i
)?
;
131 let (i
, _
) = tag(":")(i
)?
;
132 let (i
, mut value
) = take_while(|c
| c
!= '
\n'
)(i
)?
;
133 if value
.starts_with(' '
) { value = &value[1..]; }
135 let (mut i
, _
) = line_ending(i
)?
;
137 let field
= prefix
.trim().to_string();
139 let prefix_len
= expand_tab_length(prefix
);
141 let indent
: usize = prefix_len
+ 2;
143 let parse_continuation
= opt(space_indented_line(indent
));
145 let mut value
= value
.to_string();
147 if field
== "config" {
148 let (n
, _
) = line_ending(i
)?
;
153 let (n
, cont
) = parse_continuation(i
)?
;
155 if let Some(cont
) = cont
{
156 let (n
, _
) = line_ending(n
)?
;
158 if !value
.is_empty() { value.push('\n'); }
159 value
.push_str(cont
);
161 if field
== "config" {
162 let (n
, _
) = line_ending(i
)?
;
170 Ok((i
, (field
, value
)))
173 pub fn parse_zpool_status_config_tree(i
: &str) -> Result
<Vec
<ZFSPoolVDevState
>, Error
> {
174 parse_complete("zfs status config tree", i
, parse_zpool_status_tree
)
177 fn parse_zpool_status(input
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
178 parse_complete("zfs status output", &input
, many0(parse_zpool_status_field
))
181 pub fn vdev_list_to_tree(vdev_list
: &[ZFSPoolVDevState
]) -> Result
<Value
, Error
> {
182 indented_list_to_tree(vdev_list
, |vdev
| {
183 let node
= serde_json
::to_value(vdev
).unwrap();
188 fn indented_list_to_tree
<'a
, T
, F
, I
>(items
: I
, to_node
: F
) -> Result
<Value
, Error
>
191 I
: IntoIterator
<Item
= &'a T
>,
192 F
: Fn(&T
) -> (Value
, u64),
195 use std
::mem
::replace
;
197 let mut stack
= Vec
::<(Map
<String
, Value
>, u64, Vec
<Value
>)>::new(); // (node, level, children)
198 // hold current node and the children of the current parent (as that's where we insert)
199 let mut cur_node
= Map
::<String
, Value
>::new();
200 let mut cur_level
= 0;
201 let mut children_of_parent
= Vec
::new();
203 cur_node
.insert("name".to_string(), Value
::String("root".to_string()));
206 let (node
, node_level
) = to_node(&item
);
207 let vdev_level
= 1 + node_level
;
208 let mut node
= match node
{
209 Value
::Object(map
) => map
,
210 _
=> bail
!("to_node returned wrong type"),
213 node
.insert("leaf".to_string(), Value
::Bool(true));
215 // if required, go back up (possibly multiple levels):
216 while vdev_level
< cur_level
{
217 children_of_parent
.push(Value
::Object(cur_node
));
218 let mut prev
= // could be better with rust issue #372 resolved...
219 stack
.pop().ok_or_else(|| format_err
!("broken item list: stack underrun"))?
;
220 prev
.0.insert
("children".to_string(), Value
::Array(children_of_parent
));
221 prev
.0.insert
("leaf".to_string(), Value
::Bool(false));
224 children_of_parent
= prev
.2;
226 if vdev_level
> cur_level
{
227 // when we encounter misimatching levels like "0, 2, 1" instead of "0, 1, 2, 1"
228 bail
!("broken indentation between levels");
232 if vdev_level
> cur_level
{
233 // indented further, push our current state and start a new "map"
235 replace(&mut cur_node
, node
),
236 replace(&mut cur_level
, vdev_level
),
237 replace(&mut children_of_parent
, Vec
::new()),
240 // same indentation level, add to children of the previous level:
241 children_of_parent
.push(Value
::Object(
242 replace(&mut cur_node
, node
),
247 while !stack
.is_empty() {
248 children_of_parent
.push(Value
::Object(cur_node
));
249 let mut prev
= // could be better with rust issue #372 resolved...
250 stack
.pop().ok_or_else(|| format_err
!("broken item list: stack underrun"))?
;
251 prev
.0.insert
("children".to_string(), Value
::Array(children_of_parent
));
252 if !stack
.is_empty() {
253 prev
.0.insert
("leaf".to_string(), Value
::Bool(false));
256 children_of_parent
= prev
.2;
259 Ok(Value
::Object(cur_node
))
263 fn test_vdev_list_to_tree() {
264 const DEFAULT
: ZFSPoolVDevState
= ZFSPoolVDevState
{
275 //ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT },
276 ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT }
,
277 ZFSPoolVDevState { name: "vdev1-disk1".to_string(), lvl: 2, ..DEFAULT }
,
278 ZFSPoolVDevState { name: "vdev1-disk2".to_string(), lvl: 2, ..DEFAULT }
,
279 ZFSPoolVDevState { name: "vdev2".to_string(), lvl: 1, ..DEFAULT }
,
280 ZFSPoolVDevState { name: "vdev2-g1".to_string(), lvl: 2, ..DEFAULT }
,
281 ZFSPoolVDevState { name: "vdev2-g1-d1".to_string(), lvl: 3, ..DEFAULT }
,
282 ZFSPoolVDevState { name: "vdev2-g1-d2".to_string(), lvl: 3, ..DEFAULT }
,
283 ZFSPoolVDevState { name: "vdev2-g2".to_string(), lvl: 2, ..DEFAULT }
,
284 ZFSPoolVDevState { name: "vdev3".to_string(), lvl: 1, ..DEFAULT }
,
285 ZFSPoolVDevState { name: "vdev4".to_string(), lvl: 1, ..DEFAULT }
,
286 ZFSPoolVDevState { name: "vdev4-g1".to_string(), lvl: 2, ..DEFAULT }
,
287 ZFSPoolVDevState { name: "vdev4-g1-d1".to_string(), lvl: 3, ..DEFAULT }
,
288 ZFSPoolVDevState { name: "vdev4-g1-d1-x1".to_string(), lvl: 4, ..DEFAULT }
,
289 ZFSPoolVDevState { name: "vdev4-g2".to_string(), lvl: 2, ..DEFAULT }
, // up by 2
292 const EXPECTED
: &str = "{\
296 \"lvl\":2,\"name\":\"vdev1-disk1\"\
299 \"lvl\":2,\"name\":\"vdev1-disk2\"\
302 \"lvl\":1,\"name\":\"vdev1\"\
307 \"lvl\":3,\"name\":\"vdev2-g1-d1\"\
310 \"lvl\":3,\"name\":\"vdev2-g1-d2\"\
313 \"lvl\":2,\"name\":\"vdev2-g1\"\
316 \"lvl\":2,\"name\":\"vdev2-g2\"\
319 \"lvl\":1,\"name\":\"vdev2\"\
322 \"lvl\":1,\"name\":\"vdev3\"\
328 \"lvl\":4,\"name\":\"vdev4-g1-d1-x1\"\
331 \"lvl\":3,\"name\":\"vdev4-g1-d1\"\
334 \"lvl\":2,\"name\":\"vdev4-g1\"\
337 \"lvl\":2,\"name\":\"vdev4-g2\"\
340 \"lvl\":1,\"name\":\"vdev4\"\
344 let expected
: Value
= serde_json
::from_str(EXPECTED
)
345 .expect("failed to parse expected json value");
347 let tree
= vdev_list_to_tree(&input
)
348 .expect("failed to turn valid vdev list into a tree");
349 assert_eq
!(tree
, expected
);
352 pub fn zpool_status(pool
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
354 let mut command
= std
::process
::Command
::new("zpool");
355 command
.args(&["status", "-p", "-P", pool
]);
357 let output
= crate::tools
::run_command(command
, None
)?
;
359 parse_zpool_status(&output
)
363 fn test_zpool_status_parser() -> Result
<(), Error
> {
365 let output
= r
###" pool: tank
367 status: One or more devices could not be opened. Sufficient replicas exist for
368 the pool to continue functioning in a degraded state.
369 action: Attach the missing device and online it using 'zpool online'.
370 see: http://www.sun.com/msg/ZFS-8000-2Q
371 scrub: none requested
374 NAME STATE READ WRITE CKSUM
376 mirror-0 DEGRADED 0 0 0
379 c1t1d0 UNAVAIL 0 0 0 cannot open
380 mirror-1 DEGRADED 0 0 0
384 errors: No known data errors
387 let key_value_list
= parse_zpool_status(&output
)?
;
388 for (k
, v
) in key_value_list
{
389 println
!("{} => {}", k
,v
);
391 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
392 let _tree
= vdev_list_to_tree(&vdev_list
);
393 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);
401 fn test_zpool_status_parser2() -> Result
<(), Error
> {
403 // Note: this input create TABS
404 let output
= r
###" pool: btest
409 NAME STATE READ WRITE CKSUM
411 mirror-0 ONLINE 0 0 0
412 /dev/sda1 ONLINE 0 0 0
413 /dev/sda2 ONLINE 0 0 0
414 mirror-1 ONLINE 0 0 0
415 /dev/sda3 ONLINE 0 0 0
416 /dev/sda4 ONLINE 0 0 0
418 /dev/sda5 ONLINE 0 0 0
420 errors: No known data errors
423 let key_value_list
= parse_zpool_status(&output
)?
;
424 for (k
, v
) in key_value_list
{
425 println
!("{} => {}", k
,v
);
427 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
428 let _tree
= vdev_list_to_tree(&vdev_list
);
429 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);