diff --git a/src/tools/disks/lvm.rs b/src/tools/disks/lvm.rs index 2aeabb6d..09a60693 100644 --- a/src/tools/disks/lvm.rs +++ b/src/tools/disks/lvm.rs @@ -1,13 +1,13 @@ use std::collections::HashSet; use std::os::unix::fs::MetadataExt; -use anyhow::{Error}; -use serde_json::Value; +use anyhow::Error; use lazy_static::lazy_static; +use serde_json::Value; use super::LsblkInfo; -lazy_static!{ +lazy_static! { static ref LVM_UUIDS: HashSet<&'static str> = { let mut set = HashSet::new(); set.insert("e6d6d379-f507-44c2-a23c-238f2a3df928"); @@ -18,14 +18,18 @@ lazy_static!{ /// Get set of devices used by LVM (pvs). /// /// The set is indexed by using the unix raw device number (dev_t is u64) -pub fn get_lvm_devices( - lsblk_info: &[LsblkInfo], -) -> Result, Error> { - +pub fn get_lvm_devices(lsblk_info: &[LsblkInfo]) -> Result, Error> { const PVS_BIN_PATH: &str = "pvs"; let mut command = std::process::Command::new(PVS_BIN_PATH); - command.args(&["--reportformat", "json", "--noheadings", "--readonly", "-o", "pv_name"]); + command.args(&[ + "--reportformat", + "json", + "--noheadings", + "--readonly", + "-o", + "pv_name", + ]); let output = proxmox_sys::command::run_command(command, None)?; diff --git a/src/tools/disks/mod.rs b/src/tools/disks/mod.rs index 080e4ba6..267de8d4 100644 --- a/src/tools/disks/mod.rs +++ b/src/tools/disks/mod.rs @@ -14,12 +14,12 @@ use once_cell::sync::OnceCell; use ::serde::{Deserialize, Serialize}; -use proxmox_sys::error::io_err_other; -use proxmox_sys::linux::procfs::{MountInfo, mountinfo::Device}; -use proxmox_sys::{io_bail, io_format_err}; use proxmox_schema::api; +use proxmox_sys::error::io_err_other; +use proxmox_sys::linux::procfs::{mountinfo::Device, MountInfo}; +use proxmox_sys::{io_bail, io_format_err}; -use pbs_api_types::{BLOCKDEVICE_NAME_REGEX, StorageStatus}; +use pbs_api_types::{StorageStatus, BLOCKDEVICE_NAME_REGEX}; mod zfs; pub use zfs::*; @@ -32,7 +32,7 @@ pub use lvm::*; mod smart; pub use smart::*; -lazy_static::lazy_static!{ +lazy_static::lazy_static! { static ref ISCSI_PATH_REGEX: regex::Regex = regex::Regex::new(r"host[^/]*/session[^/]*").unwrap(); } @@ -153,7 +153,6 @@ impl DiskManage { &self, path: &std::path::Path, ) -> Result)>, Error> { - let stat = nix::sys::stat::stat(path)?; let device = Device::from_dev_t(stat.st_dev); @@ -161,7 +160,11 @@ impl DiskManage { for (_id, entry) in self.mount_info()? { if entry.root == root_path && entry.device == device { - return Ok(Some((entry.fs_type.clone(), entry.device, entry.mount_source.clone()))); + return Ok(Some(( + entry.fs_type.clone(), + entry.device, + entry.mount_source.clone(), + ))); } } @@ -300,7 +303,7 @@ impl Disk { /// Get the disk's size in bytes. pub fn size(&self) -> io::Result { Ok(*self.info.size.get_or_try_init(|| { - self.read_sys_u64("size")?.map(|s| s*512).ok_or_else(|| { + self.read_sys_u64("size")?.map(|s| s * 512).ok_or_else(|| { io_format_err!( "failed to get disk size from {:?}", self.syspath().join("size"), @@ -444,19 +447,19 @@ impl Disk { /// another kernel driver like the device mapper. pub fn has_holders(&self) -> io::Result { Ok(*self - .info - .has_holders - .get_or_try_init(|| -> io::Result { - let mut subdir = self.syspath().to_owned(); - subdir.push("holders"); - for entry in std::fs::read_dir(subdir)? { - match entry?.file_name().as_bytes() { - b"." | b".." => (), - _ => return Ok(true), - } - } - Ok(false) - })?) + .info + .has_holders + .get_or_try_init(|| -> io::Result { + let mut subdir = self.syspath().to_owned(); + subdir.push("holders"); + for entry in std::fs::read_dir(subdir)? { + match entry?.file_name().as_bytes() { + b"." | b".." => (), + _ => return Ok(true), + } + } + Ok(false) + })?) } /// Check if this disk is mounted. @@ -473,26 +476,28 @@ impl Disk { pub fn read_stat(&self) -> std::io::Result> { if let Some(stat) = self.read_sys(Path::new("stat"))? { let stat = unsafe { std::str::from_utf8_unchecked(&stat) }; - let stat: Vec = stat.split_ascii_whitespace().map(|s| { - u64::from_str_radix(s, 10).unwrap_or(0) - }).collect(); + let stat: Vec = stat + .split_ascii_whitespace() + .map(|s| u64::from_str_radix(s, 10).unwrap_or(0)) + .collect(); - if stat.len() < 15 { return Ok(None); } + if stat.len() < 15 { + return Ok(None); + } return Ok(Some(BlockDevStat { read_ios: stat[0], read_sectors: stat[2], - write_ios: stat[4] + stat[11], // write + discard + write_ios: stat[4] + stat[11], // write + discard write_sectors: stat[6] + stat[13], // write + discard io_ticks: stat[10], - })); + })); } Ok(None) } /// List device partitions pub fn partitions(&self) -> Result, Error> { - let sys_path = self.syspath(); let device = self.sysname().to_string_lossy().to_string(); @@ -505,7 +510,9 @@ impl Disk { Err(_) => continue, // skip non utf8 entries }; - if !name.starts_with(&device) { continue; } + if !name.starts_with(&device) { + continue; + } let mut part_path = sys_path.to_owned(); part_path.push(name); @@ -523,7 +530,6 @@ impl Disk { /// Returns disk usage information (total, used, avail) pub fn disk_usage(path: &std::path::Path) -> Result { - let mut stat: libc::statfs64 = unsafe { std::mem::zeroed() }; use nix::NixPath; @@ -533,16 +539,16 @@ pub fn disk_usage(path: &std::path::Path) -> Result { let bsize = stat.f_bsize as u64; - Ok(StorageStatus{ - total: stat.f_blocks*bsize, - used: (stat.f_blocks-stat.f_bfree)*bsize, - avail: stat.f_bavail*bsize, + Ok(StorageStatus { + total: stat.f_blocks * bsize, + used: (stat.f_blocks - stat.f_bfree) * bsize, + avail: stat.f_bavail * bsize, }) } #[api()] #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] /// This is just a rough estimate for a "type" of disk. pub enum DiskType { /// We know nothing. @@ -570,7 +576,6 @@ pub struct BlockDevStat { /// Use lsblk to read partition type uuids and file system types. pub fn get_lsblk_info() -> Result, Error> { - let mut command = std::process::Command::new("lsblk"); command.args(&["--json", "-o", "path,parttype,fstype"]); @@ -584,10 +589,7 @@ pub fn get_lsblk_info() -> Result, Error> { /// Get set of devices with a file system label. /// /// The set is indexed by using the unix raw device number (dev_t is u64) -fn get_file_system_devices( - lsblk_info: &[LsblkInfo], -) -> Result, Error> { - +fn get_file_system_devices(lsblk_info: &[LsblkInfo]) -> Result, Error> { let mut device_set: HashSet = HashSet::new(); for info in lsblk_info.iter() { @@ -602,7 +604,7 @@ fn get_file_system_devices( #[api()] #[derive(Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] pub enum DiskUsageType { /// Disk is not used (as far we can tell) Unused, @@ -634,7 +636,7 @@ pub enum DiskUsageType { } )] #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all="kebab-case")] +#[serde(rename_all = "kebab-case")] /// Information about how a Disk is used pub struct DiskUsageInfo { /// Disk name (/sys/block/) @@ -668,7 +670,6 @@ fn scan_partitions( zfs_devices: &HashSet, device: &str, ) -> Result { - let mut sys_path = std::path::PathBuf::from("/sys/block"); sys_path.push(device); @@ -686,7 +687,9 @@ fn scan_partitions( Ok(name) => name, Err(_) => continue, // skip non utf8 entries }; - if !name.starts_with(device) { continue; } + if !name.starts_with(device) { + continue; + } found_partitions = true; @@ -709,9 +712,9 @@ fn scan_partitions( found_dm = true; } - if zfs_devices.contains(&devnum) { + if zfs_devices.contains(&devnum) { found_zfs = true; - } + } } if found_mountpoints { @@ -729,12 +732,8 @@ fn scan_partitions( Ok(used) } - /// Get disk usage information for a single disk -pub fn get_disk_usage_info( - disk: &str, - no_smart: bool, -) -> Result { +pub fn get_disk_usage_info(disk: &str, no_smart: bool) -> Result { let mut filter = Vec::new(); filter.push(disk.to_string()); let mut map = get_disks(Some(filter), no_smart)?; @@ -752,15 +751,15 @@ pub fn get_disks( // do no include data from smartctl no_smart: bool, ) -> Result, Error> { - let disk_manager = DiskManage::new(); let lsblk_info = get_lsblk_info()?; - let zfs_devices = zfs_devices(&lsblk_info, None).or_else(|err| -> Result, Error> { - eprintln!("error getting zfs devices: {}", err); - Ok(HashSet::new()) - })?; + let zfs_devices = + zfs_devices(&lsblk_info, None).or_else(|err| -> Result, Error> { + eprintln!("error getting zfs devices: {}", err); + Ok(HashSet::new()) + })?; let lvm_devices = get_lvm_devices(&lsblk_info)?; @@ -770,20 +769,25 @@ pub fn get_disks( let mut result = HashMap::new(); - for item in proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX)? { + for item in proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX)? + { let item = item?; let name = item.file_name().to_str().unwrap().to_string(); if let Some(ref disks) = disks { - if !disks.contains(&name) { continue; } + if !disks.contains(&name) { + continue; + } } let sys_path = format!("/sys/block/{}", name); if let Ok(target) = std::fs::read_link(&sys_path) { if let Some(target) = target.to_str() { - if ISCSI_PATH_REGEX.is_match(target) { continue; } // skip iSCSI devices + if ISCSI_PATH_REGEX.is_match(target) { + continue; + } // skip iSCSI devices } } @@ -809,7 +813,7 @@ pub fn get_disks( match disk.is_mounted() { Ok(true) => usage = DiskUsageType::Mounted, - Ok(false) => {}, + Ok(false) => {} Err(_) => continue, // skip devices with undetectable mount status } @@ -817,17 +821,20 @@ pub fn get_disks( usage = DiskUsageType::ZFS; } - let vendor = disk.vendor().unwrap_or(None). - map(|s| s.to_string_lossy().trim().to_string()); + let vendor = disk + .vendor() + .unwrap_or(None) + .map(|s| s.to_string_lossy().trim().to_string()); let model = disk.model().map(|s| s.to_string_lossy().into_owned()); let serial = disk.serial().map(|s| s.to_string_lossy().into_owned()); - let devpath = disk.device_path().map(|p| p.to_owned()) + let devpath = disk + .device_path() + .map(|p| p.to_owned()) .map(|p| p.to_string_lossy().to_string()); - let wwn = disk.wwn().map(|s| s.to_string_lossy().into_owned()); if usage != DiskUsageType::Mounted { @@ -836,7 +843,7 @@ pub fn get_disks( if part_usage != DiskUsageType::Unused { usage = part_usage; } - }, + } Err(_) => continue, // skip devices if scan_partitions fail }; } @@ -849,7 +856,7 @@ pub fn get_disks( usage = DiskUsageType::DeviceMapper; } - let mut status = SmartStatus::Unknown; + let mut status = SmartStatus::Unknown; let mut wearout = None; if !no_smart { @@ -861,8 +868,15 @@ pub fn get_disks( let info = DiskUsageInfo { name: name.clone(), - vendor, model, serial, devpath, size, wwn, disk_type, - status, wearout, + vendor, + model, + serial, + devpath, + size, + wwn, + disk_type, + status, + wearout, used: usage, gpt: disk.has_gpt(), rpm: disk.ata_rotation_rate_rpm(), @@ -876,7 +890,6 @@ pub fn get_disks( /// Try to reload the partition table pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> { - let disk_path = match disk.device_path() { Some(path) => path, None => bail!("disk {:?} has no node in /dev", disk.syspath()), @@ -893,7 +906,6 @@ pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> { /// Initialize disk by writing a GPT partition table pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Error> { - let disk_path = match disk.device_path() { Some(path) => path, None => bail!("disk {:?} has no node in /dev", disk.syspath()), @@ -912,7 +924,6 @@ pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Erro /// Create a single linux partition using the whole available space pub fn create_single_linux_partition(disk: &Disk) -> Result { - let disk_path = match disk.device_path() { Some(path) => path, None => bail!("disk {:?} has no node in /dev", disk.syspath()), @@ -934,7 +945,7 @@ pub fn create_single_linux_partition(disk: &Disk) -> Result { #[api()] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] pub enum FileSystemType { /// Linux Ext4 Ext4, @@ -963,7 +974,6 @@ impl std::str::FromStr for FileSystemType { /// Create a file system on a disk or disk partition pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Error> { - let disk_path = match disk.device_path() { Some(path) => path, None => bail!("disk {:?} has no node in /dev", disk.syspath()), @@ -982,21 +992,21 @@ pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Er /// Block device name completion helper pub fn complete_disk_name(_arg: &str, _param: &HashMap) -> Vec { - let dir = match proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX) { - Ok(dir) => dir, - Err(_) => return vec![], - }; + let dir = + match proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX) { + Ok(dir) => dir, + Err(_) => return vec![], + }; - dir.flatten().map(|item| { - item.file_name().to_str().unwrap().to_string() - }).collect() + dir.flatten() + .map(|item| item.file_name().to_str().unwrap().to_string()) + .collect() } /// Read the FS UUID (parse blkid output) /// /// Note: Calling blkid is more reliable than using the udev ID_FS_UUID property. pub fn get_fs_uuid(disk: &Disk) -> Result { - let disk_path = match disk.device_path() { Some(path) => path, None => bail!("disk {:?} has no node in /dev", disk.syspath()), diff --git a/src/tools/disks/smart.rs b/src/tools/disks/smart.rs index ee79d3e8..3738cdfd 100644 --- a/src/tools/disks/smart.rs +++ b/src/tools/disks/smart.rs @@ -1,14 +1,14 @@ use std::collections::{HashMap, HashSet}; -use lazy_static::lazy_static; -use anyhow::{bail, Error}; use ::serde::{Deserialize, Serialize}; +use anyhow::{bail, Error}; +use lazy_static::lazy_static; use proxmox_schema::api; #[api()] #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all="lowercase")] +#[serde(rename_all = "lowercase")] /// SMART status pub enum SmartStatus { /// Smart tests passed - everything is OK @@ -29,23 +29,22 @@ pub struct SmartAttribute { value: String, // the rest of the values is available for ATA type /// ATA Attribute ID - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] id: Option, /// ATA Flags - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] flags: Option, /// ATA normalized value (0..100) - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] normalized: Option, /// ATA worst - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] worst: Option, /// ATA threshold - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] threshold: Option, } - #[api( properties: { status: { @@ -74,16 +73,14 @@ pub struct SmartData { } /// Read smartctl data for a disk (/dev/XXX). -pub fn get_smart_data( - disk: &super::Disk, - health_only: bool, -) -> Result { - +pub fn get_smart_data(disk: &super::Disk, health_only: bool) -> Result { const SMARTCTL_BIN_PATH: &str = "smartctl"; let mut command = std::process::Command::new(SMARTCTL_BIN_PATH); command.arg("-H"); - if !health_only { command.args(&["-A", "-j"]); } + if !health_only { + command.args(&["-A", "-j"]); + } let disk_path = match disk.device_path() { Some(path) => path, @@ -91,9 +88,12 @@ pub fn get_smart_data( }; command.arg(disk_path); - let output = proxmox_sys::command::run_command(command, Some(|exitcode| - (exitcode & 0b0111) == 0 // only bits 0-2 are fatal errors - ))?; + let output = proxmox_sys::command::run_command( + command, + Some( + |exitcode| (exitcode & 0b0111) == 0, // only bits 0-2 are fatal errors + ), + )?; let output: serde_json::Value = output.parse()?; @@ -196,8 +196,11 @@ pub fn get_smart_data( Some(false) => SmartStatus::Failed, }; - - Ok(SmartData { status, wearout, attributes }) + Ok(SmartData { + status, + wearout, + attributes, + }) } static WEAROUT_FIELD_ORDER: &[&'static str] = &[ @@ -213,11 +216,10 @@ static WEAROUT_FIELD_ORDER: &[&'static str] = &[ "Lifetime_Remaining", "Percent_Life_Remaining", "Percent_Lifetime_Used", - "Perc_Rated_Life_Used" + "Perc_Rated_Life_Used", ]; lazy_static! { - static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> = { - WEAROUT_FIELD_ORDER.iter().cloned().collect() - }; + static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> = + WEAROUT_FIELD_ORDER.iter().cloned().collect(); } diff --git a/src/tools/disks/zfs.rs b/src/tools/disks/zfs.rs index 25a3a709..9bca0297 100644 --- a/src/tools/disks/zfs.rs +++ b/src/tools/disks/zfs.rs @@ -1,6 +1,6 @@ -use std::path::PathBuf; use std::collections::HashSet; use std::os::unix::fs::MetadataExt; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use anyhow::{bail, Error}; @@ -10,7 +10,7 @@ use proxmox_schema::const_regex; use super::*; -lazy_static!{ +lazy_static! { static ref ZFS_UUIDS: HashSet<&'static str> = { let mut set = HashSet::new(); set.insert("6a898cc3-1dd2-11b2-99a6-080020736631"); // apple @@ -29,14 +29,15 @@ fn get_pool_from_dataset(dataset: &str) -> &str { /// Returns kernel IO-stats for zfs pools pub fn zfs_pool_stats(pool: &OsStr) -> Result, Error> { - let mut path = PathBuf::from("/proc/spl/kstat/zfs"); path.push(pool); path.push("io"); let text = match proxmox_sys::fs::file_read_optional_string(&path)? { Some(text) => text, - None => { return Ok(None); } + None => { + return Ok(None); + } }; let lines: Vec<&str> = text.lines().collect(); @@ -50,15 +51,16 @@ pub fn zfs_pool_stats(pool: &OsStr) -> Result, Error> { // Note: w -> wait (wtime -> wait time) // Note: r -> run (rtime -> run time) // All times are nanoseconds - let stat: Vec = lines[2].split_ascii_whitespace().map(|s| { - u64::from_str_radix(s, 10).unwrap_or(0) - }).collect(); + let stat: Vec = lines[2] + .split_ascii_whitespace() + .map(|s| u64::from_str_radix(s, 10).unwrap_or(0)) + .collect(); - let ticks = (stat[4] + stat[7])/1_000_000; // convert to milisec + let ticks = (stat[4] + stat[7]) / 1_000_000; // convert to milisec let stat = BlockDevStat { - read_sectors: stat[0]>>9, - write_sectors: stat[1]>>9, + read_sectors: stat[0] >> 9, + write_sectors: stat[1] >> 9, read_ios: stat[2], write_ios: stat[3], io_ticks: ticks, @@ -70,11 +72,7 @@ pub fn zfs_pool_stats(pool: &OsStr) -> Result, Error> { /// Get set of devices used by zfs (or a specific zfs pool) /// /// The set is indexed by using the unix raw device number (dev_t is u64) -pub fn zfs_devices( - lsblk_info: &[LsblkInfo], - pool: Option, -) -> Result, Error> { - +pub fn zfs_devices(lsblk_info: &[LsblkInfo], pool: Option) -> Result, Error> { let list = zpool_list(pool, true)?; let mut device_set = HashSet::new(); @@ -162,7 +160,6 @@ fn parse_objset_stat(pool: &str, objset_id: &str) -> Result<(String, BlockDevSta Ok((dataset_name, stat)) } - fn get_mapping(dataset: &str) -> Option<(String, String)> { ZFS_DATASET_OBJSET_MAP .lock() diff --git a/src/tools/disks/zpool_list.rs b/src/tools/disks/zpool_list.rs index 09667dfc..1d0b8712 100644 --- a/src/tools/disks/zpool_list.rs +++ b/src/tools/disks/zpool_list.rs @@ -1,15 +1,13 @@ use anyhow::{bail, Error}; -use pbs_tools::nom::{ - multispace0, multispace1, notspace1, IResult, -}; +use pbs_tools::nom::{multispace0, multispace1, notspace1, IResult}; use nom::{ - bytes::complete::{take_while1, take_till, take_till1}, - combinator::{map_res, all_consuming, recognize, opt}, + bytes::complete::{take_till, take_till1, take_while1}, + character::complete::{char, digit1, line_ending}, + combinator::{all_consuming, map_res, opt, recognize}, + multi::many0, sequence::{preceded, tuple}, - character::complete::{digit1, char, line_ending}, - multi::{many0}, }; #[derive(Debug, PartialEq)] @@ -29,7 +27,6 @@ pub struct ZFSPoolInfo { pub devices: Vec, } - fn parse_optional_u64(i: &str) -> IResult<&str, Option> { if let Some(rest) = i.strip_prefix('-') { Ok((rest, None)) @@ -61,44 +58,49 @@ fn parse_pool_device(i: &str) -> IResult<&str, String> { fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> { // name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot. - let (i, (text, size, alloc, free, _, _, - frag, _, dedup, health, - _altroot, _eol)) = tuple(( + let (i, (text, size, alloc, free, _, _, frag, _, dedup, health, _altroot, _eol)) = tuple(( take_while1(|c| char::is_alphanumeric(c) || c == '-' || c == ':' || c == '_' || c == '.'), // name preceded(multispace1, parse_optional_u64), // size preceded(multispace1, parse_optional_u64), // allocated preceded(multispace1, parse_optional_u64), // free - preceded(multispace1, notspace1), // checkpoint - preceded(multispace1, notspace1), // expandsize + preceded(multispace1, notspace1), // checkpoint + preceded(multispace1, notspace1), // expandsize preceded(multispace1, parse_optional_u64), // fragmentation - preceded(multispace1, notspace1), // capacity + preceded(multispace1, notspace1), // capacity preceded(multispace1, parse_optional_f64), // dedup - preceded(multispace1, notspace1), // health - opt(preceded(multispace1, notspace1)), // optional altroot + preceded(multispace1, notspace1), // health + opt(preceded(multispace1, notspace1)), // optional altroot line_ending, ))(i)?; - let status = if let (Some(size), Some(alloc), Some(free), Some(frag), Some(dedup)) = (size, alloc, free, frag, dedup) { + let status = if let (Some(size), Some(alloc), Some(free), Some(frag), Some(dedup)) = + (size, alloc, free, frag, dedup) + { ZFSPoolInfo { name: text.into(), health: health.into(), - usage: Some(ZFSPoolUsage { size, alloc, free, frag, dedup }), + usage: Some(ZFSPoolUsage { + size, + alloc, + free, + frag, + dedup, + }), devices: Vec::new(), } } else { - ZFSPoolInfo { - name: text.into(), - health: health.into(), - usage: None, - devices: Vec::new(), - } + ZFSPoolInfo { + name: text.into(), + health: health.into(), + usage: None, + devices: Vec::new(), + } }; Ok((i, status)) } fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> { - let (i, mut stat) = parse_zpool_list_header(i)?; let (i, devices) = many0(parse_pool_device)(i)?; @@ -117,9 +119,11 @@ fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> { /// the zpool list output format is not really defined... fn parse_zpool_list(i: &str) -> Result, Error> { match all_consuming(many0(parse_zpool_list_item))(i) { - Err(nom::Err::Error(err)) | - Err(nom::Err::Failure(err)) => { - bail!("unable to parse zfs list output - {}", nom::error::convert_error(i, err)); + Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => { + bail!( + "unable to parse zfs list output - {}", + nom::error::convert_error(i, err) + ); } Err(err) => { bail!("unable to parse zfs list output - {}", err); @@ -133,7 +137,6 @@ fn parse_zpool_list(i: &str) -> Result, Error> { /// Devices are only included when run with verbose flags /// set. Without, device lists are empty. pub fn zpool_list(pool: Option, verbose: bool) -> Result, Error> { - // Note: zpools list verbose output can include entries for 'special', 'cache' and 'logs' // and maybe other things. @@ -143,9 +146,13 @@ pub fn zpool_list(pool: Option, verbose: bool) -> Result, verbose: bool) -> Result Result<(), Error> { - let output = ""; let data = parse_zpool_list(output)?; @@ -164,19 +170,18 @@ fn test_zfs_parse_list() -> Result<(), Error> { let output = "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n"; let data = parse_zpool_list(output)?; - let expect = vec![ - ZFSPoolInfo { - name: "btest".to_string(), - health: "ONLINE".to_string(), - devices: Vec::new(), - usage: Some(ZFSPoolUsage { - size: 427349245952, - alloc: 405504, - free: 427348840448, - dedup: 1.0, - frag: 0, - }), - }]; + let expect = vec![ZFSPoolInfo { + name: "btest".to_string(), + health: "ONLINE".to_string(), + devices: Vec::new(), + usage: Some(ZFSPoolUsage { + size: 427349245952, + alloc: 405504, + free: 427348840448, + dedup: 1.0, + frag: 0, + }), + }]; assert_eq!(data, expect); @@ -195,10 +200,12 @@ logs ZFSPoolInfo { name: String::from("rpool"), health: String::from("ONLINE"), - devices: vec![String::from("/dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3")], + devices: vec![String::from( + "/dev/disk/by-id/ata-Crucial_CT500MX200SSD1_154210EB4078-part3", + )], usage: Some(ZFSPoolUsage { size: 535260299264, - alloc:402852388864 , + alloc: 402852388864, free: 132407910400, dedup: 1.0, frag: 22, @@ -249,7 +256,7 @@ logs - - - - - - - - - String::from("/dev/sda2"), String::from("/dev/sda3"), String::from("/dev/sda4"), - ] + ], }, ZFSPoolInfo { name: String::from("logs"), @@ -268,22 +275,18 @@ b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE - "; let data = parse_zpool_list(output)?; - let expect = vec![ - ZFSPoolInfo { - name: String::from("b.test"), - health: String::from("ONLINE"), - usage: Some(ZFSPoolUsage { - size: 427349245952, - alloc: 761856, - free: 427348484096, - dedup: 1.0, - frag: 0, - }), - devices: vec![ - String::from("/dev/sda1"), - ] - }, - ]; + let expect = vec![ZFSPoolInfo { + name: String::from("b.test"), + health: String::from("ONLINE"), + usage: Some(ZFSPoolUsage { + size: 427349245952, + alloc: 761856, + free: 427348484096, + dedup: 1.0, + frag: 0, + }), + devices: vec![String::from("/dev/sda1")], + }]; assert_eq!(data, expect); diff --git a/src/tools/disks/zpool_status.rs b/src/tools/disks/zpool_status.rs index 4601b14f..cb87c81f 100644 --- a/src/tools/disks/zpool_status.rs +++ b/src/tools/disks/zpool_status.rs @@ -5,32 +5,31 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use pbs_tools::nom::{ - parse_complete, parse_error, parse_failure, - multispace0, multispace1, notspace1, parse_u64, IResult, + multispace0, multispace1, notspace1, parse_complete, parse_error, parse_failure, parse_u64, + IResult, }; use nom::{ bytes::complete::{tag, take_while, take_while1}, - combinator::{opt}, - sequence::{preceded}, - character::complete::{line_ending}, - multi::{many0,many1}, + character::complete::line_ending, + combinator::opt, + multi::{many0, many1}, + sequence::preceded, }; - #[derive(Debug, Serialize, Deserialize)] pub struct ZFSPoolVDevState { pub name: String, pub lvl: u64, - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub state: Option, - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub read: Option, - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub write: Option, - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub cksum: Option, - #[serde(skip_serializing_if="Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub msg: Option, } @@ -39,7 +38,6 @@ fn expand_tab_length(input: &str) -> usize { } fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> { - let (n, indent) = multispace0(i)?; let indent_len = expand_tab_length(indent); @@ -49,11 +47,12 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> { } let i = n; - let indent_level = (indent_len as u64)/2; + let indent_level = (indent_len as u64) / 2; - let (i, vdev_name) = notspace1(i)?; + let (i, vdev_name) = notspace1(i)?; - if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // special device + if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { + // special device let vdev = ZFSPoolVDevState { name: vdev_name.to_string(), lvl: indent_level, @@ -67,7 +66,8 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> { } let (i, state) = preceded(multispace1, notspace1)(i)?; - if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // spares + if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { + // spares let vdev = ZFSPoolVDevState { name: vdev_name.to_string(), lvl: indent_level, @@ -100,7 +100,6 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> { } fn parse_zpool_status_tree(i: &str) -> IResult<&str, Vec> { - // skip header let (i, _) = tag("NAME")(i)?; let (i, _) = multispace1(i)?; @@ -130,8 +129,10 @@ fn space_indented_line(indent: usize) -> impl Fn(&str) -> IResult<&str, &str> { break; } n = &n[1..]; - if len >= indent { break; } - }; + if len >= indent { + break; + } + } if len != indent { return Err(parse_error(i, "not correctly indented")); } @@ -144,7 +145,9 @@ fn parse_zpool_status_field(i: &str) -> IResult<&str, (String, String)> { let (i, prefix) = take_while1(|c| c != ':')(i)?; let (i, _) = tag(":")(i)?; let (i, mut value) = take_while(|c| c != '\n')(i)?; - if value.starts_with(' ') { value = &value[1..]; } + if value.starts_with(' ') { + value = &value[1..]; + } let (mut i, _) = line_ending(i)?; @@ -169,7 +172,9 @@ fn parse_zpool_status_field(i: &str) -> IResult<&str, (String, String)> { if let Some(cont) = cont { let (n, _) = line_ending(n)?; i = n; - if !value.is_empty() { value.push('\n'); } + if !value.is_empty() { + value.push('\n'); + } value.push_str(cont); } else { if field == "config" { @@ -233,7 +238,9 @@ where while vdev_level < cur.level { cur.children_of_parent.push(Value::Object(cur.node)); let mut parent = stack.pop().unwrap(); - parent.node.insert("children".to_string(), Value::Array(cur.children_of_parent)); + parent + .node + .insert("children".to_string(), Value::Array(cur.children_of_parent)); parent.node.insert("leaf".to_string(), Value::Bool(false)); cur = parent; @@ -252,16 +259,17 @@ where }); } else { // same indentation level, add to children of the previous level: - cur.children_of_parent.push(Value::Object( - replace(&mut cur.node, node), - )); + cur.children_of_parent + .push(Value::Object(replace(&mut cur.node, node))); } } while !stack.is_empty() { cur.children_of_parent.push(Value::Object(cur.node)); let mut parent = stack.pop().unwrap(); - parent.node.insert("children".to_string(), Value::Array(cur.children_of_parent)); + parent + .node + .insert("children".to_string(), Value::Array(cur.children_of_parent)); parent.node.insert("leaf".to_string(), Value::Bool(false)); cur = parent; } @@ -281,6 +289,7 @@ fn test_vdev_list_to_tree() { msg: None, }; + #[rustfmt::skip] let input = vec![ //ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT }, ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT }, @@ -351,16 +360,14 @@ fn test_vdev_list_to_tree() { }],\ \"leaf\":false\ }"; - let expected: Value = serde_json::from_str(EXPECTED) - .expect("failed to parse expected json value"); + let expected: Value = + serde_json::from_str(EXPECTED).expect("failed to parse expected json value"); - let tree = vdev_list_to_tree(&input) - .expect("failed to turn valid vdev list into a tree"); + let tree = vdev_list_to_tree(&input).expect("failed to turn valid vdev list into a tree"); assert_eq!(tree, expected); } pub fn zpool_status(pool: &str) -> Result, Error> { - let mut command = std::process::Command::new("zpool"); command.args(&["status", "-p", "-P", pool]); @@ -390,7 +397,6 @@ fn test_parse(output: &str) -> Result<(), Error> { #[test] fn test_zpool_status_parser() -> Result<(), Error> { - let output = r###" pool: tank state: DEGRADED status: One or more devices could not be opened. Sufficient replicas exist for @@ -418,7 +424,6 @@ errors: No known data errors #[test] fn test_zpool_status_parser2() -> Result<(), Error> { - // Note: this input create TABS let output = r###" pool: btest state: ONLINE @@ -443,7 +448,6 @@ errors: No known data errors #[test] fn test_zpool_status_parser3() -> Result<(), Error> { - let output = r###" pool: bt-est state: ONLINE scan: none requested @@ -468,7 +472,6 @@ errors: No known data errors #[test] fn test_zpool_status_parser_spares() -> Result<(), Error> { - let output = r###" pool: tank state: ONLINE scan: none requested