2 use serde_json
::{json, Value}
;
3 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
]) -> Value
{
184 struct TreeNode
<'a
> {
185 vdev
: &'a ZFSPoolVDevState
,
189 fn node_to_json(node_idx
: usize, nodes
: &[TreeNode
]) -> Value
{
190 let node
= &nodes
[node_idx
];
191 let mut v
= serde_json
::to_value(node
.vdev
).unwrap();
192 if node
.children
.is_empty() {
193 v
["leaf"] = true.into();
195 v
["leaf"] = false.into();
196 v
["children"] = json
!([]);
197 for child
in node
.children
.iter(){
198 let c
= node_to_json(*child
, nodes
);
199 v
["children"].as_array_mut().unwrap().push(c
);
205 let mut nodes
: Vec
<TreeNode
> = vdev_list
.into_iter().map(|vdev
| {
208 children
: Vec
::new(),
212 let mut stack
: Vec
<usize> = Vec
::new();
214 let mut root_children
: Vec
<usize> = Vec
::new();
216 for idx
in 0..nodes
.len() {
218 if stack
.is_empty() {
219 root_children
.push(idx
);
224 let node_lvl
= nodes
[idx
].vdev
.lvl
;
226 let stacked_node
= &mut nodes
[*(stack
.last().unwrap())];
227 let last_lvl
= stacked_node
.vdev
.lvl
;
229 if node_lvl
> last_lvl
{
230 stacked_node
.children
.push(idx
);
231 } else if node_lvl
== last_lvl
{
234 Some(parent
) => nodes
[*parent
].children
.push(idx
),
235 None
=> root_children
.push(idx
),
239 if stack
.is_empty() {
240 root_children
.push(idx
);
244 let stacked_node
= &mut nodes
[*(stack
.last().unwrap())];
245 if node_lvl
<= stacked_node
.vdev
.lvl
{
248 stacked_node
.children
.push(idx
);
257 let mut result
= json
!({
259 "children": json
!([]),
262 for child
in root_children
{
263 let c
= node_to_json(child
, &nodes
);
264 result
["children"].as_array_mut().unwrap().push(c
);
270 pub fn zpool_status(pool
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
272 let mut command
= std
::process
::Command
::new("zpool");
273 command
.args(&["status", "-p", "-P", pool
]);
275 let output
= crate::tools
::run_command(command
, None
)?
;
277 parse_zpool_status(&output
)
281 fn test_zpool_status_parser() -> Result
<(), Error
> {
283 let output
= r
###" pool: tank
285 status: One or more devices could not be opened. Sufficient replicas exist for
286 the pool to continue functioning in a degraded state.
287 action: Attach the missing device and online it using 'zpool online'.
288 see: http://www.sun.com/msg/ZFS-8000-2Q
289 scrub: none requested
292 NAME STATE READ WRITE CKSUM
294 mirror-0 DEGRADED 0 0 0
297 c1t1d0 UNAVAIL 0 0 0 cannot open
298 mirror-1 DEGRADED 0 0 0
302 errors: No known data errors
305 let key_value_list
= parse_zpool_status(&output
)?
;
306 for (k
, v
) in key_value_list
{
307 println
!("{} => {}", k
,v
);
309 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
310 let _tree
= vdev_list_to_tree(&vdev_list
);
311 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);
319 fn test_zpool_status_parser2() -> Result
<(), Error
> {
321 // Note: this input create TABS
322 let output
= r
###" pool: btest
327 NAME STATE READ WRITE CKSUM
329 mirror-0 ONLINE 0 0 0
330 /dev/sda1 ONLINE 0 0 0
331 /dev/sda2 ONLINE 0 0 0
332 mirror-1 ONLINE 0 0 0
333 /dev/sda3 ONLINE 0 0 0
334 /dev/sda4 ONLINE 0 0 0
336 /dev/sda5 ONLINE 0 0 0
338 errors: No known data errors
341 let key_value_list
= parse_zpool_status(&output
)?
;
342 for (k
, v
) in key_value_list
{
343 println
!("{} => {}", k
,v
);
345 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
346 let _tree
= vdev_list_to_tree(&vdev_list
);
347 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);