]> git.proxmox.com Git - proxmox-backup.git/blob - src/tools/disks/zpool_list.rs
update to proxmox-sys 0.2 crate
[proxmox-backup.git] / src / tools / disks / zpool_list.rs
1 use anyhow::{bail, Error};
2
3 use pbs_tools::nom::{
4 multispace0, multispace1, notspace1, IResult,
5 };
6
7 use nom::{
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},
12 multi::{many0},
13 };
14
15 #[derive(Debug, PartialEq)]
16 pub struct ZFSPoolUsage {
17 pub size: u64,
18 pub alloc: u64,
19 pub free: u64,
20 pub dedup: f64,
21 pub frag: u64,
22 }
23
24 #[derive(Debug, PartialEq)]
25 pub struct ZFSPoolInfo {
26 pub name: String,
27 pub health: String,
28 pub usage: Option<ZFSPoolUsage>,
29 pub devices: Vec<String>,
30 }
31
32
33 fn parse_optional_u64(i: &str) -> IResult<&str, Option<u64>> {
34 if let Some(rest) = i.strip_prefix('-') {
35 Ok((rest, None))
36 } else {
37 let (i, value) = map_res(recognize(digit1), str::parse)(i)?;
38 Ok((i, Some(value)))
39 }
40 }
41
42 fn parse_optional_f64(i: &str) -> IResult<&str, Option<f64>> {
43 if let Some(rest) = i.strip_prefix('-') {
44 Ok((rest, None))
45 } else {
46 let (i, value) = nom::number::complete::double(i)?;
47 Ok((i, Some(value)))
48 }
49 }
50
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')),
54 multispace1,
55 preceded(take_till(|c| c == '\n'), char('\n')),
56 ))(i)?;
57
58 Ok((i, device.to_string()))
59 }
60
61 fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
62 // name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot.
63
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
78 line_ending,
79 ))(i)?;
80
81 let status = if let (Some(size), Some(alloc), Some(free), Some(frag), Some(dedup)) = (size, alloc, free, frag, dedup) {
82 ZFSPoolInfo {
83 name: text.into(),
84 health: health.into(),
85 usage: Some(ZFSPoolUsage { size, alloc, free, frag, dedup }),
86 devices: Vec::new(),
87 }
88 } else {
89 ZFSPoolInfo {
90 name: text.into(),
91 health: health.into(),
92 usage: None,
93 devices: Vec::new(),
94 }
95 };
96
97 Ok((i, status))
98 }
99
100 fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
101
102 let (i, mut stat) = parse_zpool_list_header(i)?;
103 let (i, devices) = many0(parse_pool_device)(i)?;
104
105 for device_path in devices.into_iter().filter(|n| n.starts_with("/dev/")) {
106 stat.devices.push(device_path);
107 }
108
109 let (i, _) = many0(tuple((multispace0, char('\n'))))(i)?; // skip empty lines
110
111 Ok((i, stat))
112 }
113
114 /// Parse zpool list output
115 ///
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));
123 }
124 Err(err) => {
125 bail!("unable to parse zfs list output - {}", err);
126 }
127 Ok((_, ce)) => Ok(ce),
128 }
129 }
130
131 /// Run zpool list and return parsed output
132 ///
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> {
136
137 // Note: zpools list verbose output can include entries for 'special', 'cache' and 'logs'
138 // and maybe other things.
139
140 let mut command = std::process::Command::new("zpool");
141 command.args(&["list", "-H", "-p", "-P"]);
142
143 // Note: We do not use -o to define output properties, because zpool command ignores
144 // that completely for special vdevs and devices
145
146 if verbose { command.arg("-v"); }
147
148 if let Some(pool) = pool { command.arg(pool); }
149
150 let output = proxmox_sys::command::run_command(command, None)?;
151
152 parse_zpool_list(&output)
153 }
154
155 #[test]
156 fn test_zfs_parse_list() -> Result<(), Error> {
157
158 let output = "";
159
160 let data = parse_zpool_list(&output)?;
161 let expect = Vec::new();
162
163 assert_eq!(data, expect);
164
165 let output = "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n";
166 let data = parse_zpool_list(&output)?;
167 let expect = vec![
168 ZFSPoolInfo {
169 name: "btest".to_string(),
170 health: "ONLINE".to_string(),
171 devices: Vec::new(),
172 usage: Some(ZFSPoolUsage {
173 size: 427349245952,
174 alloc: 405504,
175 free: 427348840448,
176 dedup: 1.0,
177 frag: 0,
178 }),
179 }];
180
181 assert_eq!(data, expect);
182
183 let output = "\
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
190
191 ";
192
193 let data = parse_zpool_list(&output)?;
194 let expect = vec![
195 ZFSPoolInfo {
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 {
200 size: 535260299264,
201 alloc:402852388864 ,
202 free: 132407910400,
203 dedup: 1.0,
204 frag: 22,
205 }),
206 },
207 ZFSPoolInfo {
208 name: String::from("special"),
209 health: String::from("-"),
210 devices: vec![String::from("/dev/sda2")],
211 usage: None,
212 },
213 ZFSPoolInfo {
214 name: String::from("logs"),
215 health: String::from("-"),
216 devices: vec![String::from("/dev/sda3")],
217 usage: None,
218 },
219 ];
220
221 assert_eq!(data, expect);
222
223 let output = "\
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
233 ";
234
235 let data = parse_zpool_list(&output)?;
236 let expect = vec![
237 ZFSPoolInfo {
238 name: String::from("b-test"),
239 health: String::from("ONLINE"),
240 usage: Some(ZFSPoolUsage {
241 size: 427349245952,
242 alloc: 761856,
243 free: 427348484096,
244 dedup: 1.0,
245 frag: 0,
246 }),
247 devices: vec![
248 String::from("/dev/sda1"),
249 String::from("/dev/sda2"),
250 String::from("/dev/sda3"),
251 String::from("/dev/sda4"),
252 ]
253 },
254 ZFSPoolInfo {
255 name: String::from("logs"),
256 health: String::from("-"),
257 usage: None,
258 devices: vec![String::from("/dev/sda5")],
259 },
260 ];
261
262 assert_eq!(data, expect);
263
264 let output = "\
265 b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
266 mirror 213674622976 438272 213674184704 - - 0 0 - ONLINE
267 /dev/sda1 - - - - - - - - ONLINE
268 ";
269
270 let data = parse_zpool_list(&output)?;
271 let expect = vec![
272 ZFSPoolInfo {
273 name: String::from("b.test"),
274 health: String::from("ONLINE"),
275 usage: Some(ZFSPoolUsage {
276 size: 427349245952,
277 alloc: 761856,
278 free: 427348484096,
279 dedup: 1.0,
280 frag: 0,
281 }),
282 devices: vec![
283 String::from("/dev/sda1"),
284 ]
285 },
286 ];
287
288 assert_eq!(data, expect);
289
290 Ok(())
291 }