2 use serde_json
::{json, Value}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use crate::tools
::nom
::{
6 parse_complete
, parse_failure
, multispace0
, multispace1
, notspace1
, parse_u64
, IResult
,
10 bytes
::complete
::{tag, take_while, take_while1}
,
13 character
::complete
::{line_ending}
,
18 #[derive(Debug, Serialize, Deserialize)]
19 pub struct ZFSPoolVDevState
{
22 #[serde(skip_serializing_if="Option::is_none")]
23 pub state
: Option
<String
>,
24 #[serde(skip_serializing_if="Option::is_none")]
25 pub read
: Option
<u64>,
26 #[serde(skip_serializing_if="Option::is_none")]
27 pub write
: Option
<u64>,
28 #[serde(skip_serializing_if="Option::is_none")]
29 pub cksum
: Option
<u64>,
30 #[serde(skip_serializing_if="Option::is_none")]
31 pub msg
: Option
<String
>,
34 fn expand_tab_length(input
: &str) -> usize {
35 input
.chars().map(|c
| if c
== '
\t' { 8 }
else { 1 }
).sum()
38 fn parse_zpool_status_vdev(i
: &str) -> IResult
<&str, ZFSPoolVDevState
> {
40 let (n
, indent
) = multispace0(i
)?
;
42 let indent_len
= expand_tab_length(indent
);
44 if (indent_len
& 1) != 0 {
45 return Err(parse_failure(n
, "wrong indent length"));
49 let indent_level
= (indent_len
as u64)/2;
51 let (i
, vdev_name
) = notspace1(i
)?
;
53 if let Ok((n
, _
)) = preceded(multispace0
, line_ending
)(i
) { // sepecial device
54 let vdev
= ZFSPoolVDevState
{
55 name
: vdev_name
.to_string(),
66 let (i
, state
) = preceded(multispace1
, notspace1
)(i
)?
;
67 let (i
, read
) = preceded(multispace1
, parse_u64
)(i
)?
;
68 let (i
, write
) = preceded(multispace1
, parse_u64
)(i
)?
;
69 let (i
, cksum
) = preceded(multispace1
, parse_u64
)(i
)?
;
70 let (i
, msg
) = opt(preceded(multispace1
, take_while(|c
| c
!= '
\n'
)))(i
)?
;
71 let (i
, _
) = line_ending(i
)?
;
73 let vdev
= ZFSPoolVDevState
{
74 name
: vdev_name
.to_string(),
76 state
: Some(state
.to_string()),
80 msg
: msg
.map(String
::from
),
86 fn parse_zpool_status_tree(i
: &str) -> IResult
<&str, Vec
<ZFSPoolVDevState
>> {
89 let (i
, _
) = tag("NAME")(i
)?
;
90 let (i
, _
) = multispace1(i
)?
;
91 let (i
, _
) = tag("STATE")(i
)?
;
92 let (i
, _
) = multispace1(i
)?
;
93 let (i
, _
) = tag("READ")(i
)?
;
94 let (i
, _
) = multispace1(i
)?
;
95 let (i
, _
) = tag("WRITE")(i
)?
;
96 let (i
, _
) = multispace1(i
)?
;
97 let (i
, _
) = tag("CKSUM")(i
)?
;
98 let (i
, _
) = line_ending(i
)?
;
101 many1(parse_zpool_status_vdev
)(i
)
104 fn parse_zpool_status_field(i
: &str) -> IResult
<&str, (String
, String
)> {
105 let (i
, prefix
) = take_while1(|c
| c
!= '
:'
)(i
)?
;
106 let (i
, _
) = tag(":")(i
)?
;
107 let (i
, mut value
) = take_while(|c
| c
!= '
\n'
)(i
)?
;
108 if value
.starts_with(' '
) { value = &value[1..]; }
110 let (mut i
, _
) = line_ending(i
)?
;
112 let field
= prefix
.trim().to_string();
114 let indent
= (0..prefix
.len()+2).fold(String
::new(), |mut acc
, _
| { acc.push(' '); acc }
);
116 let parse_continuation
= opt(preceded(tag(indent
.as_str()), take_while1(|c
| c
!= '
\n'
)));
118 let mut value
= value
.to_string();
120 if field
== "config" {
121 let (n
, _
) = line_ending(i
)?
;
126 let (n
, cont
) = parse_continuation(i
)?
;
128 if let Some(cont
) = cont
{
129 let (n
, _
) = line_ending(n
)?
;
131 if !value
.is_empty() { value.push('\n'); }
132 value
.push_str(cont
);
134 if field
== "config" {
135 let (n
, _
) = line_ending(i
)?
;
143 Ok((i
, (field
, value
)))
146 pub fn parse_zpool_status_config_tree(i
: &str) -> Result
<Vec
<ZFSPoolVDevState
>, Error
> {
147 parse_complete("zfs status config tree", i
, parse_zpool_status_tree
)
150 fn parse_zpool_status(input
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
151 let input
= input
.replace('
\t'
, " "); // important!
152 parse_complete("zfs status output", &input
, many0(parse_zpool_status_field
))
155 pub fn vdev_list_to_tree(vdev_list
: &[ZFSPoolVDevState
]) -> Value
{
158 struct TreeNode
<'a
> {
159 vdev
: &'a ZFSPoolVDevState
,
163 fn node_to_json(node_idx
: usize, nodes
: &[TreeNode
]) -> Value
{
164 let node
= &nodes
[node_idx
];
165 let mut v
= serde_json
::to_value(node
.vdev
).unwrap();
166 if node
.children
.is_empty() {
167 v
["leaf"] = true.into();
169 v
["leaf"] = false.into();
170 v
["children"] = json
!([]);
171 for child
in node
.children
.iter(){
172 let c
= node_to_json(*child
, nodes
);
173 v
["children"].as_array_mut().unwrap().push(c
);
179 let mut nodes
: Vec
<TreeNode
> = vdev_list
.into_iter().map(|vdev
| {
182 children
: Vec
::new(),
186 let mut stack
: Vec
<usize> = Vec
::new();
188 let mut root_children
: Vec
<usize> = Vec
::new();
190 for idx
in 0..nodes
.len() {
192 if stack
.is_empty() {
193 root_children
.push(idx
);
198 let node_lvl
= nodes
[idx
].vdev
.lvl
;
200 let stacked_node
= &mut nodes
[*(stack
.last().unwrap())];
201 let last_lvl
= stacked_node
.vdev
.lvl
;
203 if node_lvl
> last_lvl
{
204 stacked_node
.children
.push(idx
);
205 } else if node_lvl
== last_lvl
{
208 Some(parent
) => nodes
[*parent
].children
.push(idx
),
209 None
=> root_children
.push(idx
),
213 if stack
.is_empty() {
214 root_children
.push(idx
);
218 let stacked_node
= &mut nodes
[*(stack
.last().unwrap())];
219 if node_lvl
<= stacked_node
.vdev
.lvl
{
222 stacked_node
.children
.push(idx
);
231 let mut result
= json
!({
233 "children": json
!([]),
236 for child
in root_children
{
237 let c
= node_to_json(child
, &nodes
);
238 result
["children"].as_array_mut().unwrap().push(c
);
244 pub fn zpool_status(pool
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
246 let mut command
= std
::process
::Command
::new("zpool");
247 command
.args(&["status", "-p", "-P", pool
]);
249 let output
= crate::tools
::run_command(command
, None
)?
;
251 parse_zpool_status(&output
)
255 fn test_zpool_status_parser() -> Result
<(), Error
> {
257 let output
= r
###" pool: tank
259 status: One or more devices could not be opened. Sufficient replicas exist for
260 the pool to continue functioning in a degraded state.
261 action: Attach the missing device and online it using 'zpool online'.
262 see: http://www.sun.com/msg/ZFS-8000-2Q
263 scrub: none requested
266 NAME STATE READ WRITE CKSUM
268 mirror-0 DEGRADED 0 0 0
271 c1t1d0 UNAVAIL 0 0 0 cannot open
272 mirror-1 DEGRADED 0 0 0
276 errors: No known data errors
279 let key_value_list
= parse_zpool_status(&output
)?
;
280 for (k
, v
) in key_value_list
{
281 println
!("{} => {}", k
,v
);
283 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
284 let _tree
= vdev_list_to_tree(&vdev_list
);
285 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);
293 fn test_zpool_status_parser2() -> Result
<(), Error
> {
295 // Note: this input create TABS
296 let output
= r
###" pool: btest
301 NAME STATE READ WRITE CKSUM
303 mirror-0 ONLINE 0 0 0
304 /dev/sda1 ONLINE 0 0 0
305 /dev/sda2 ONLINE 0 0 0
306 mirror-1 ONLINE 0 0 0
307 /dev/sda3 ONLINE 0 0 0
308 /dev/sda4 ONLINE 0 0 0
310 /dev/sda5 ONLINE 0 0 0
312 errors: No known data errors
315 let key_value_list
= parse_zpool_status(&output
)?
;
316 for (k
, v
) in key_value_list
{
317 println
!("{} => {}", k
,v
);
319 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
320 let _tree
= vdev_list_to_tree(&vdev_list
);
321 //println!("TREE1 {}", serde_json::to_string_pretty(&tree)?);