tools: disk: rustfmt

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-02-10 17:46:13 +01:00
parent a1c906cb02
commit af6fdb9d0d
6 changed files with 248 additions and 229 deletions

View File

@ -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<HashSet<u64>, Error> {
pub fn get_lvm_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, 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)?;

View File

@ -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<Option<(String, Device, Option<OsString>)>, 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<u64> {
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<bool> {
Ok(*self
.info
.has_holders
.get_or_try_init(|| -> io::Result<bool> {
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<bool> {
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<Option<BlockDevStat>> {
if let Some(stat) = self.read_sys(Path::new("stat"))? {
let stat = unsafe { std::str::from_utf8_unchecked(&stat) };
let stat: Vec<u64> = stat.split_ascii_whitespace().map(|s| {
u64::from_str_radix(s, 10).unwrap_or(0)
}).collect();
let stat: Vec<u64> = 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<HashMap<u64, Disk>, 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<StorageStatus, Error> {
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<StorageStatus, Error> {
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<Vec<LsblkInfo>, 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<Vec<LsblkInfo>, 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<HashSet<u64>, Error> {
fn get_file_system_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, Error> {
let mut device_set: HashSet<u64> = 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/<name>)
@ -668,7 +670,6 @@ fn scan_partitions(
zfs_devices: &HashSet<u64>,
device: &str,
) -> Result<DiskUsageType, Error> {
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<DiskUsageInfo, Error> {
pub fn get_disk_usage_info(disk: &str, no_smart: bool) -> Result<DiskUsageInfo, Error> {
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<HashMap<String, DiskUsageInfo>, Error> {
let disk_manager = DiskManage::new();
let lsblk_info = get_lsblk_info()?;
let zfs_devices = zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, Error> {
eprintln!("error getting zfs devices: {}", err);
Ok(HashSet::new())
})?;
let zfs_devices =
zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, 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<Disk, Error> {
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<Disk, Error> {
#[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<String, String>) -> Vec<String> {
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<String, Error> {
let disk_path = match disk.device_path() {
Some(path) => path,
None => bail!("disk {:?} has no node in /dev", disk.syspath()),

View File

@ -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<u64>,
/// ATA Flags
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
flags: Option<String>,
/// ATA normalized value (0..100)
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
normalized: Option<f64>,
/// ATA worst
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
worst: Option<f64>,
/// ATA threshold
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
threshold: Option<f64>,
}
#[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<SmartData, Error> {
pub fn get_smart_data(disk: &super::Disk, health_only: bool) -> Result<SmartData, Error> {
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();
}

View File

@ -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<Option<BlockDevStat>, 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<Option<BlockDevStat>, Error> {
// Note: w -> wait (wtime -> wait time)
// Note: r -> run (rtime -> run time)
// All times are nanoseconds
let stat: Vec<u64> = lines[2].split_ascii_whitespace().map(|s| {
u64::from_str_radix(s, 10).unwrap_or(0)
}).collect();
let stat: Vec<u64> = 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<Option<BlockDevStat>, 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<String>,
) -> Result<HashSet<u64>, Error> {
pub fn zfs_devices(lsblk_info: &[LsblkInfo], pool: Option<String>) -> Result<HashSet<u64>, 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()

View File

@ -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<String>,
}
fn parse_optional_u64(i: &str) -> IResult<&str, Option<u64>> {
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<Vec<ZFSPoolInfo>, 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<Vec<ZFSPoolInfo>, Error> {
/// Devices are only included when run with verbose flags
/// set. Without, device lists are empty.
pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo>, 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<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo
// Note: We do not use -o to define output properties, because zpool command ignores
// that completely for special vdevs and devices
if verbose { command.arg("-v"); }
if verbose {
command.arg("-v");
}
if let Some(pool) = pool { command.arg(pool); }
if let Some(pool) = pool {
command.arg(pool);
}
let output = proxmox_sys::command::run_command(command, None)?;
@ -154,7 +161,6 @@ pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo
#[test]
fn test_zfs_parse_list() -> 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);

View File

@ -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<String>,
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
pub read: Option<u64>,
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
pub write: Option<u64>,
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cksum: Option<u64>,
#[serde(skip_serializing_if="Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none")]
pub msg: Option<String>,
}
@ -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<ZFSPoolVDevState>> {
// 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<Vec<(String, String)>, 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