tools: disk: rustfmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
a1c906cb02
commit
af6fdb9d0d
|
@ -1,9 +1,9 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::Error;
|
||||||
use serde_json::Value;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::LsblkInfo;
|
use super::LsblkInfo;
|
||||||
|
|
||||||
|
@ -18,14 +18,18 @@ lazy_static!{
|
||||||
/// Get set of devices used by LVM (pvs).
|
/// Get set of devices used by LVM (pvs).
|
||||||
///
|
///
|
||||||
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||||
pub fn get_lvm_devices(
|
pub fn get_lvm_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, Error> {
|
||||||
lsblk_info: &[LsblkInfo],
|
|
||||||
) -> Result<HashSet<u64>, Error> {
|
|
||||||
|
|
||||||
const PVS_BIN_PATH: &str = "pvs";
|
const PVS_BIN_PATH: &str = "pvs";
|
||||||
|
|
||||||
let mut command = std::process::Command::new(PVS_BIN_PATH);
|
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)?;
|
let output = proxmox_sys::command::run_command(command, None)?;
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use ::serde::{Deserialize, Serialize};
|
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_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;
|
mod zfs;
|
||||||
pub use zfs::*;
|
pub use zfs::*;
|
||||||
|
@ -153,7 +153,6 @@ impl DiskManage {
|
||||||
&self,
|
&self,
|
||||||
path: &std::path::Path,
|
path: &std::path::Path,
|
||||||
) -> Result<Option<(String, Device, Option<OsString>)>, Error> {
|
) -> Result<Option<(String, Device, Option<OsString>)>, Error> {
|
||||||
|
|
||||||
let stat = nix::sys::stat::stat(path)?;
|
let stat = nix::sys::stat::stat(path)?;
|
||||||
let device = Device::from_dev_t(stat.st_dev);
|
let device = Device::from_dev_t(stat.st_dev);
|
||||||
|
|
||||||
|
@ -161,7 +160,11 @@ impl DiskManage {
|
||||||
|
|
||||||
for (_id, entry) in self.mount_info()? {
|
for (_id, entry) in self.mount_info()? {
|
||||||
if entry.root == root_path && entry.device == device {
|
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(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,11 +476,14 @@ impl Disk {
|
||||||
pub fn read_stat(&self) -> std::io::Result<Option<BlockDevStat>> {
|
pub fn read_stat(&self) -> std::io::Result<Option<BlockDevStat>> {
|
||||||
if let Some(stat) = self.read_sys(Path::new("stat"))? {
|
if let Some(stat) = self.read_sys(Path::new("stat"))? {
|
||||||
let stat = unsafe { std::str::from_utf8_unchecked(&stat) };
|
let stat = unsafe { std::str::from_utf8_unchecked(&stat) };
|
||||||
let stat: Vec<u64> = stat.split_ascii_whitespace().map(|s| {
|
let stat: Vec<u64> = stat
|
||||||
u64::from_str_radix(s, 10).unwrap_or(0)
|
.split_ascii_whitespace()
|
||||||
}).collect();
|
.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 {
|
return Ok(Some(BlockDevStat {
|
||||||
read_ios: stat[0],
|
read_ios: stat[0],
|
||||||
|
@ -492,7 +498,6 @@ impl Disk {
|
||||||
|
|
||||||
/// List device partitions
|
/// List device partitions
|
||||||
pub fn partitions(&self) -> Result<HashMap<u64, Disk>, Error> {
|
pub fn partitions(&self) -> Result<HashMap<u64, Disk>, Error> {
|
||||||
|
|
||||||
let sys_path = self.syspath();
|
let sys_path = self.syspath();
|
||||||
let device = self.sysname().to_string_lossy().to_string();
|
let device = self.sysname().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
@ -505,7 +510,9 @@ impl Disk {
|
||||||
Err(_) => continue, // skip non utf8 entries
|
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();
|
let mut part_path = sys_path.to_owned();
|
||||||
part_path.push(name);
|
part_path.push(name);
|
||||||
|
@ -523,7 +530,6 @@ impl Disk {
|
||||||
|
|
||||||
/// Returns disk usage information (total, used, avail)
|
/// Returns disk usage information (total, used, avail)
|
||||||
pub fn disk_usage(path: &std::path::Path) -> Result<StorageStatus, Error> {
|
pub fn disk_usage(path: &std::path::Path) -> Result<StorageStatus, Error> {
|
||||||
|
|
||||||
let mut stat: libc::statfs64 = unsafe { std::mem::zeroed() };
|
let mut stat: libc::statfs64 = unsafe { std::mem::zeroed() };
|
||||||
|
|
||||||
use nix::NixPath;
|
use nix::NixPath;
|
||||||
|
@ -570,7 +576,6 @@ pub struct BlockDevStat {
|
||||||
|
|
||||||
/// Use lsblk to read partition type uuids and file system types.
|
/// Use lsblk to read partition type uuids and file system types.
|
||||||
pub fn get_lsblk_info() -> Result<Vec<LsblkInfo>, Error> {
|
pub fn get_lsblk_info() -> Result<Vec<LsblkInfo>, Error> {
|
||||||
|
|
||||||
let mut command = std::process::Command::new("lsblk");
|
let mut command = std::process::Command::new("lsblk");
|
||||||
command.args(&["--json", "-o", "path,parttype,fstype"]);
|
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.
|
/// Get set of devices with a file system label.
|
||||||
///
|
///
|
||||||
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||||
fn get_file_system_devices(
|
fn get_file_system_devices(lsblk_info: &[LsblkInfo]) -> Result<HashSet<u64>, Error> {
|
||||||
lsblk_info: &[LsblkInfo],
|
|
||||||
) -> Result<HashSet<u64>, Error> {
|
|
||||||
|
|
||||||
let mut device_set: HashSet<u64> = HashSet::new();
|
let mut device_set: HashSet<u64> = HashSet::new();
|
||||||
|
|
||||||
for info in lsblk_info.iter() {
|
for info in lsblk_info.iter() {
|
||||||
|
@ -668,7 +670,6 @@ fn scan_partitions(
|
||||||
zfs_devices: &HashSet<u64>,
|
zfs_devices: &HashSet<u64>,
|
||||||
device: &str,
|
device: &str,
|
||||||
) -> Result<DiskUsageType, Error> {
|
) -> Result<DiskUsageType, Error> {
|
||||||
|
|
||||||
let mut sys_path = std::path::PathBuf::from("/sys/block");
|
let mut sys_path = std::path::PathBuf::from("/sys/block");
|
||||||
sys_path.push(device);
|
sys_path.push(device);
|
||||||
|
|
||||||
|
@ -686,7 +687,9 @@ fn scan_partitions(
|
||||||
Ok(name) => name,
|
Ok(name) => name,
|
||||||
Err(_) => continue, // skip non utf8 entries
|
Err(_) => continue, // skip non utf8 entries
|
||||||
};
|
};
|
||||||
if !name.starts_with(device) { continue; }
|
if !name.starts_with(device) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
found_partitions = true;
|
found_partitions = true;
|
||||||
|
|
||||||
|
@ -729,12 +732,8 @@ fn scan_partitions(
|
||||||
Ok(used)
|
Ok(used)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get disk usage information for a single disk
|
/// Get disk usage information for a single disk
|
||||||
pub fn get_disk_usage_info(
|
pub fn get_disk_usage_info(disk: &str, no_smart: bool) -> Result<DiskUsageInfo, Error> {
|
||||||
disk: &str,
|
|
||||||
no_smart: bool,
|
|
||||||
) -> Result<DiskUsageInfo, Error> {
|
|
||||||
let mut filter = Vec::new();
|
let mut filter = Vec::new();
|
||||||
filter.push(disk.to_string());
|
filter.push(disk.to_string());
|
||||||
let mut map = get_disks(Some(filter), no_smart)?;
|
let mut map = get_disks(Some(filter), no_smart)?;
|
||||||
|
@ -752,12 +751,12 @@ pub fn get_disks(
|
||||||
// do no include data from smartctl
|
// do no include data from smartctl
|
||||||
no_smart: bool,
|
no_smart: bool,
|
||||||
) -> Result<HashMap<String, DiskUsageInfo>, Error> {
|
) -> Result<HashMap<String, DiskUsageInfo>, Error> {
|
||||||
|
|
||||||
let disk_manager = DiskManage::new();
|
let disk_manager = DiskManage::new();
|
||||||
|
|
||||||
let lsblk_info = get_lsblk_info()?;
|
let lsblk_info = get_lsblk_info()?;
|
||||||
|
|
||||||
let zfs_devices = zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, Error> {
|
let zfs_devices =
|
||||||
|
zfs_devices(&lsblk_info, None).or_else(|err| -> Result<HashSet<u64>, Error> {
|
||||||
eprintln!("error getting zfs devices: {}", err);
|
eprintln!("error getting zfs devices: {}", err);
|
||||||
Ok(HashSet::new())
|
Ok(HashSet::new())
|
||||||
})?;
|
})?;
|
||||||
|
@ -770,20 +769,25 @@ pub fn get_disks(
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
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 item = item?;
|
||||||
|
|
||||||
let name = item.file_name().to_str().unwrap().to_string();
|
let name = item.file_name().to_str().unwrap().to_string();
|
||||||
|
|
||||||
if let Some(ref disks) = disks {
|
if let Some(ref disks) = disks {
|
||||||
if !disks.contains(&name) { continue; }
|
if !disks.contains(&name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sys_path = format!("/sys/block/{}", name);
|
let sys_path = format!("/sys/block/{}", name);
|
||||||
|
|
||||||
if let Ok(target) = std::fs::read_link(&sys_path) {
|
if let Ok(target) = std::fs::read_link(&sys_path) {
|
||||||
if let Some(target) = target.to_str() {
|
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() {
|
match disk.is_mounted() {
|
||||||
Ok(true) => usage = DiskUsageType::Mounted,
|
Ok(true) => usage = DiskUsageType::Mounted,
|
||||||
Ok(false) => {},
|
Ok(false) => {}
|
||||||
Err(_) => continue, // skip devices with undetectable mount status
|
Err(_) => continue, // skip devices with undetectable mount status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,17 +821,20 @@ pub fn get_disks(
|
||||||
usage = DiskUsageType::ZFS;
|
usage = DiskUsageType::ZFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
let vendor = disk.vendor().unwrap_or(None).
|
let vendor = disk
|
||||||
map(|s| s.to_string_lossy().trim().to_string());
|
.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 model = disk.model().map(|s| s.to_string_lossy().into_owned());
|
||||||
|
|
||||||
let serial = disk.serial().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());
|
.map(|p| p.to_string_lossy().to_string());
|
||||||
|
|
||||||
|
|
||||||
let wwn = disk.wwn().map(|s| s.to_string_lossy().into_owned());
|
let wwn = disk.wwn().map(|s| s.to_string_lossy().into_owned());
|
||||||
|
|
||||||
if usage != DiskUsageType::Mounted {
|
if usage != DiskUsageType::Mounted {
|
||||||
|
@ -836,7 +843,7 @@ pub fn get_disks(
|
||||||
if part_usage != DiskUsageType::Unused {
|
if part_usage != DiskUsageType::Unused {
|
||||||
usage = part_usage;
|
usage = part_usage;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => continue, // skip devices if scan_partitions fail
|
Err(_) => continue, // skip devices if scan_partitions fail
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -861,8 +868,15 @@ pub fn get_disks(
|
||||||
|
|
||||||
let info = DiskUsageInfo {
|
let info = DiskUsageInfo {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
vendor, model, serial, devpath, size, wwn, disk_type,
|
vendor,
|
||||||
status, wearout,
|
model,
|
||||||
|
serial,
|
||||||
|
devpath,
|
||||||
|
size,
|
||||||
|
wwn,
|
||||||
|
disk_type,
|
||||||
|
status,
|
||||||
|
wearout,
|
||||||
used: usage,
|
used: usage,
|
||||||
gpt: disk.has_gpt(),
|
gpt: disk.has_gpt(),
|
||||||
rpm: disk.ata_rotation_rate_rpm(),
|
rpm: disk.ata_rotation_rate_rpm(),
|
||||||
|
@ -876,7 +890,6 @@ pub fn get_disks(
|
||||||
|
|
||||||
/// Try to reload the partition table
|
/// Try to reload the partition table
|
||||||
pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> {
|
pub fn reread_partition_table(disk: &Disk) -> Result<(), Error> {
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
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
|
/// Initialize disk by writing a GPT partition table
|
||||||
pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Error> {
|
pub fn inititialize_gpt_disk(disk: &Disk, uuid: Option<&str>) -> Result<(), Error> {
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
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
|
/// Create a single linux partition using the whole available space
|
||||||
pub fn create_single_linux_partition(disk: &Disk) -> Result<Disk, Error> {
|
pub fn create_single_linux_partition(disk: &Disk) -> Result<Disk, Error> {
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||||
|
@ -963,7 +974,6 @@ impl std::str::FromStr for FileSystemType {
|
||||||
|
|
||||||
/// Create a file system on a disk or disk partition
|
/// Create a file system on a disk or disk partition
|
||||||
pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Error> {
|
pub fn create_file_system(disk: &Disk, fs_type: FileSystemType) -> Result<(), Error> {
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
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
|
/// Block device name completion helper
|
||||||
pub fn complete_disk_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
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) {
|
let dir =
|
||||||
|
match proxmox_sys::fs::scan_subdir(libc::AT_FDCWD, "/sys/block", &BLOCKDEVICE_NAME_REGEX) {
|
||||||
Ok(dir) => dir,
|
Ok(dir) => dir,
|
||||||
Err(_) => return vec![],
|
Err(_) => return vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
dir.flatten().map(|item| {
|
dir.flatten()
|
||||||
item.file_name().to_str().unwrap().to_string()
|
.map(|item| item.file_name().to_str().unwrap().to_string())
|
||||||
}).collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the FS UUID (parse blkid output)
|
/// Read the FS UUID (parse blkid output)
|
||||||
///
|
///
|
||||||
/// Note: Calling blkid is more reliable than using the udev ID_FS_UUID property.
|
/// Note: Calling blkid is more reliable than using the udev ID_FS_UUID property.
|
||||||
pub fn get_fs_uuid(disk: &Disk) -> Result<String, Error> {
|
pub fn get_fs_uuid(disk: &Disk) -> Result<String, Error> {
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
None => bail!("disk {:?} has no node in /dev", disk.syspath()),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use anyhow::{bail, Error};
|
|
||||||
use ::serde::{Deserialize, Serialize};
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
use anyhow::{bail, Error};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use proxmox_schema::api;
|
use proxmox_schema::api;
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ pub struct SmartAttribute {
|
||||||
threshold: Option<f64>,
|
threshold: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
properties: {
|
properties: {
|
||||||
status: {
|
status: {
|
||||||
|
@ -74,16 +73,14 @@ pub struct SmartData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read smartctl data for a disk (/dev/XXX).
|
/// Read smartctl data for a disk (/dev/XXX).
|
||||||
pub fn get_smart_data(
|
pub fn get_smart_data(disk: &super::Disk, health_only: bool) -> Result<SmartData, Error> {
|
||||||
disk: &super::Disk,
|
|
||||||
health_only: bool,
|
|
||||||
) -> Result<SmartData, Error> {
|
|
||||||
|
|
||||||
const SMARTCTL_BIN_PATH: &str = "smartctl";
|
const SMARTCTL_BIN_PATH: &str = "smartctl";
|
||||||
|
|
||||||
let mut command = std::process::Command::new(SMARTCTL_BIN_PATH);
|
let mut command = std::process::Command::new(SMARTCTL_BIN_PATH);
|
||||||
command.arg("-H");
|
command.arg("-H");
|
||||||
if !health_only { command.args(&["-A", "-j"]); }
|
if !health_only {
|
||||||
|
command.args(&["-A", "-j"]);
|
||||||
|
}
|
||||||
|
|
||||||
let disk_path = match disk.device_path() {
|
let disk_path = match disk.device_path() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
|
@ -91,9 +88,12 @@ pub fn get_smart_data(
|
||||||
};
|
};
|
||||||
command.arg(disk_path);
|
command.arg(disk_path);
|
||||||
|
|
||||||
let output = proxmox_sys::command::run_command(command, Some(|exitcode|
|
let output = proxmox_sys::command::run_command(
|
||||||
(exitcode & 0b0111) == 0 // only bits 0-2 are fatal errors
|
command,
|
||||||
))?;
|
Some(
|
||||||
|
|exitcode| (exitcode & 0b0111) == 0, // only bits 0-2 are fatal errors
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
let output: serde_json::Value = output.parse()?;
|
let output: serde_json::Value = output.parse()?;
|
||||||
|
|
||||||
|
@ -196,8 +196,11 @@ pub fn get_smart_data(
|
||||||
Some(false) => SmartStatus::Failed,
|
Some(false) => SmartStatus::Failed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ok(SmartData {
|
||||||
Ok(SmartData { status, wearout, attributes })
|
status,
|
||||||
|
wearout,
|
||||||
|
attributes,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static WEAROUT_FIELD_ORDER: &[&'static str] = &[
|
static WEAROUT_FIELD_ORDER: &[&'static str] = &[
|
||||||
|
@ -213,11 +216,10 @@ static WEAROUT_FIELD_ORDER: &[&'static str] = &[
|
||||||
"Lifetime_Remaining",
|
"Lifetime_Remaining",
|
||||||
"Percent_Life_Remaining",
|
"Percent_Life_Remaining",
|
||||||
"Percent_Lifetime_Used",
|
"Percent_Lifetime_Used",
|
||||||
"Perc_Rated_Life_Used"
|
"Perc_Rated_Life_Used",
|
||||||
];
|
];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> = {
|
static ref WEAROUT_FIELD_NAMES: HashSet<&'static str> =
|
||||||
WEAROUT_FIELD_ORDER.iter().cloned().collect()
|
WEAROUT_FIELD_ORDER.iter().cloned().collect();
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
|
@ -29,14 +29,15 @@ fn get_pool_from_dataset(dataset: &str) -> &str {
|
||||||
|
|
||||||
/// Returns kernel IO-stats for zfs pools
|
/// Returns kernel IO-stats for zfs pools
|
||||||
pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
||||||
|
|
||||||
let mut path = PathBuf::from("/proc/spl/kstat/zfs");
|
let mut path = PathBuf::from("/proc/spl/kstat/zfs");
|
||||||
path.push(pool);
|
path.push(pool);
|
||||||
path.push("io");
|
path.push("io");
|
||||||
|
|
||||||
let text = match proxmox_sys::fs::file_read_optional_string(&path)? {
|
let text = match proxmox_sys::fs::file_read_optional_string(&path)? {
|
||||||
Some(text) => text,
|
Some(text) => text,
|
||||||
None => { return Ok(None); }
|
None => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lines: Vec<&str> = text.lines().collect();
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
@ -50,9 +51,10 @@ pub fn zfs_pool_stats(pool: &OsStr) -> Result<Option<BlockDevStat>, Error> {
|
||||||
// Note: w -> wait (wtime -> wait time)
|
// Note: w -> wait (wtime -> wait time)
|
||||||
// Note: r -> run (rtime -> run time)
|
// Note: r -> run (rtime -> run time)
|
||||||
// All times are nanoseconds
|
// All times are nanoseconds
|
||||||
let stat: Vec<u64> = lines[2].split_ascii_whitespace().map(|s| {
|
let stat: Vec<u64> = lines[2]
|
||||||
u64::from_str_radix(s, 10).unwrap_or(0)
|
.split_ascii_whitespace()
|
||||||
}).collect();
|
.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
|
||||||
|
|
||||||
|
@ -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)
|
/// 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)
|
/// The set is indexed by using the unix raw device number (dev_t is u64)
|
||||||
pub fn zfs_devices(
|
pub fn zfs_devices(lsblk_info: &[LsblkInfo], pool: Option<String>) -> Result<HashSet<u64>, Error> {
|
||||||
lsblk_info: &[LsblkInfo],
|
|
||||||
pool: Option<String>,
|
|
||||||
) -> Result<HashSet<u64>, Error> {
|
|
||||||
|
|
||||||
let list = zpool_list(pool, true)?;
|
let list = zpool_list(pool, true)?;
|
||||||
|
|
||||||
let mut device_set = HashSet::new();
|
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))
|
Ok((dataset_name, stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_mapping(dataset: &str) -> Option<(String, String)> {
|
fn get_mapping(dataset: &str) -> Option<(String, String)> {
|
||||||
ZFS_DATASET_OBJSET_MAP
|
ZFS_DATASET_OBJSET_MAP
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
|
|
||||||
use pbs_tools::nom::{
|
use pbs_tools::nom::{multispace0, multispace1, notspace1, IResult};
|
||||||
multispace0, multispace1, notspace1, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{take_while1, take_till, take_till1},
|
bytes::complete::{take_till, take_till1, take_while1},
|
||||||
combinator::{map_res, all_consuming, recognize, opt},
|
character::complete::{char, digit1, line_ending},
|
||||||
|
combinator::{all_consuming, map_res, opt, recognize},
|
||||||
|
multi::many0,
|
||||||
sequence::{preceded, tuple},
|
sequence::{preceded, tuple},
|
||||||
character::complete::{digit1, char, line_ending},
|
|
||||||
multi::{many0},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -29,7 +27,6 @@ pub struct ZFSPoolInfo {
|
||||||
pub devices: Vec<String>,
|
pub devices: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn parse_optional_u64(i: &str) -> IResult<&str, Option<u64>> {
|
fn parse_optional_u64(i: &str) -> IResult<&str, Option<u64>> {
|
||||||
if let Some(rest) = i.strip_prefix('-') {
|
if let Some(rest) = i.strip_prefix('-') {
|
||||||
Ok((rest, None))
|
Ok((rest, None))
|
||||||
|
@ -61,9 +58,7 @@ fn parse_pool_device(i: &str) -> IResult<&str, String> {
|
||||||
fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||||
// name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot.
|
// name, size, allocated, free, checkpoint, expandsize, fragmentation, capacity, dedupratio, health, altroot.
|
||||||
|
|
||||||
let (i, (text, size, alloc, free, _, _,
|
let (i, (text, size, alloc, free, _, _, frag, _, dedup, health, _altroot, _eol)) = tuple((
|
||||||
frag, _, dedup, health,
|
|
||||||
_altroot, _eol)) = tuple((
|
|
||||||
take_while1(|c| char::is_alphanumeric(c) || c == '-' || c == ':' || c == '_' || c == '.'), // name
|
take_while1(|c| char::is_alphanumeric(c) || c == '-' || c == ':' || c == '_' || c == '.'), // name
|
||||||
preceded(multispace1, parse_optional_u64), // size
|
preceded(multispace1, parse_optional_u64), // size
|
||||||
preceded(multispace1, parse_optional_u64), // allocated
|
preceded(multispace1, parse_optional_u64), // allocated
|
||||||
|
@ -78,11 +73,19 @@ fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||||
line_ending,
|
line_ending,
|
||||||
))(i)?;
|
))(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 {
|
ZFSPoolInfo {
|
||||||
name: text.into(),
|
name: text.into(),
|
||||||
health: health.into(),
|
health: health.into(),
|
||||||
usage: Some(ZFSPoolUsage { size, alloc, free, frag, dedup }),
|
usage: Some(ZFSPoolUsage {
|
||||||
|
size,
|
||||||
|
alloc,
|
||||||
|
free,
|
||||||
|
frag,
|
||||||
|
dedup,
|
||||||
|
}),
|
||||||
devices: Vec::new(),
|
devices: Vec::new(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,7 +101,6 @@ fn parse_zpool_list_header(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||||
|
|
||||||
let (i, mut stat) = parse_zpool_list_header(i)?;
|
let (i, mut stat) = parse_zpool_list_header(i)?;
|
||||||
let (i, devices) = many0(parse_pool_device)(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...
|
/// the zpool list output format is not really defined...
|
||||||
fn parse_zpool_list(i: &str) -> Result<Vec<ZFSPoolInfo>, Error> {
|
fn parse_zpool_list(i: &str) -> Result<Vec<ZFSPoolInfo>, Error> {
|
||||||
match all_consuming(many0(parse_zpool_list_item))(i) {
|
match all_consuming(many0(parse_zpool_list_item))(i) {
|
||||||
Err(nom::Err::Error(err)) |
|
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||||
Err(nom::Err::Failure(err)) => {
|
bail!(
|
||||||
bail!("unable to parse zfs list output - {}", nom::error::convert_error(i, err));
|
"unable to parse zfs list output - {}",
|
||||||
|
nom::error::convert_error(i, err)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
bail!("unable to parse zfs list output - {}", 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
|
/// Devices are only included when run with verbose flags
|
||||||
/// set. Without, device lists are empty.
|
/// set. Without, device lists are empty.
|
||||||
pub fn zpool_list(pool: Option<String>, verbose: bool) -> Result<Vec<ZFSPoolInfo>, Error> {
|
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'
|
// Note: zpools list verbose output can include entries for 'special', 'cache' and 'logs'
|
||||||
// and maybe other things.
|
// 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
|
// Note: We do not use -o to define output properties, because zpool command ignores
|
||||||
// that completely for special vdevs and devices
|
// 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)?;
|
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]
|
#[test]
|
||||||
fn test_zfs_parse_list() -> Result<(), Error> {
|
fn test_zfs_parse_list() -> Result<(), Error> {
|
||||||
|
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
let data = parse_zpool_list(output)?;
|
let data = parse_zpool_list(output)?;
|
||||||
|
@ -164,8 +170,7 @@ fn test_zfs_parse_list() -> Result<(), Error> {
|
||||||
|
|
||||||
let output = "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n";
|
let output = "btest 427349245952 405504 427348840448 - - 0 0 1.00 ONLINE -\n";
|
||||||
let data = parse_zpool_list(output)?;
|
let data = parse_zpool_list(output)?;
|
||||||
let expect = vec![
|
let expect = vec![ZFSPoolInfo {
|
||||||
ZFSPoolInfo {
|
|
||||||
name: "btest".to_string(),
|
name: "btest".to_string(),
|
||||||
health: "ONLINE".to_string(),
|
health: "ONLINE".to_string(),
|
||||||
devices: Vec::new(),
|
devices: Vec::new(),
|
||||||
|
@ -195,7 +200,9 @@ logs
|
||||||
ZFSPoolInfo {
|
ZFSPoolInfo {
|
||||||
name: String::from("rpool"),
|
name: String::from("rpool"),
|
||||||
health: String::from("ONLINE"),
|
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 {
|
usage: Some(ZFSPoolUsage {
|
||||||
size: 535260299264,
|
size: 535260299264,
|
||||||
alloc: 402852388864,
|
alloc: 402852388864,
|
||||||
|
@ -249,7 +256,7 @@ logs - - - - - - - - -
|
||||||
String::from("/dev/sda2"),
|
String::from("/dev/sda2"),
|
||||||
String::from("/dev/sda3"),
|
String::from("/dev/sda3"),
|
||||||
String::from("/dev/sda4"),
|
String::from("/dev/sda4"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
ZFSPoolInfo {
|
ZFSPoolInfo {
|
||||||
name: String::from("logs"),
|
name: String::from("logs"),
|
||||||
|
@ -268,8 +275,7 @@ b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
|
||||||
";
|
";
|
||||||
|
|
||||||
let data = parse_zpool_list(output)?;
|
let data = parse_zpool_list(output)?;
|
||||||
let expect = vec![
|
let expect = vec![ZFSPoolInfo {
|
||||||
ZFSPoolInfo {
|
|
||||||
name: String::from("b.test"),
|
name: String::from("b.test"),
|
||||||
health: String::from("ONLINE"),
|
health: String::from("ONLINE"),
|
||||||
usage: Some(ZFSPoolUsage {
|
usage: Some(ZFSPoolUsage {
|
||||||
|
@ -279,11 +285,8 @@ b.test 427349245952 761856 427348484096 - - 0 0 1.00 ONLINE -
|
||||||
dedup: 1.0,
|
dedup: 1.0,
|
||||||
frag: 0,
|
frag: 0,
|
||||||
}),
|
}),
|
||||||
devices: vec![
|
devices: vec![String::from("/dev/sda1")],
|
||||||
String::from("/dev/sda1"),
|
}];
|
||||||
]
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(data, expect);
|
assert_eq!(data, expect);
|
||||||
|
|
||||||
|
|
|
@ -5,19 +5,18 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use pbs_tools::nom::{
|
use pbs_tools::nom::{
|
||||||
parse_complete, parse_error, parse_failure,
|
multispace0, multispace1, notspace1, parse_complete, parse_error, parse_failure, parse_u64,
|
||||||
multispace0, multispace1, notspace1, parse_u64, IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take_while, take_while1},
|
bytes::complete::{tag, take_while, take_while1},
|
||||||
combinator::{opt},
|
character::complete::line_ending,
|
||||||
sequence::{preceded},
|
combinator::opt,
|
||||||
character::complete::{line_ending},
|
|
||||||
multi::{many0, many1},
|
multi::{many0, many1},
|
||||||
|
sequence::preceded,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ZFSPoolVDevState {
|
pub struct ZFSPoolVDevState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -39,7 +38,6 @@ fn expand_tab_length(input: &str) -> usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
||||||
|
|
||||||
let (n, indent) = multispace0(i)?;
|
let (n, indent) = multispace0(i)?;
|
||||||
|
|
||||||
let indent_len = expand_tab_length(indent);
|
let indent_len = expand_tab_length(indent);
|
||||||
|
@ -53,7 +51,8 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
||||||
|
|
||||||
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 {
|
let vdev = ZFSPoolVDevState {
|
||||||
name: vdev_name.to_string(),
|
name: vdev_name.to_string(),
|
||||||
lvl: indent_level,
|
lvl: indent_level,
|
||||||
|
@ -67,7 +66,8 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (i, state) = preceded(multispace1, notspace1)(i)?;
|
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 {
|
let vdev = ZFSPoolVDevState {
|
||||||
name: vdev_name.to_string(),
|
name: vdev_name.to_string(),
|
||||||
lvl: indent_level,
|
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>> {
|
fn parse_zpool_status_tree(i: &str) -> IResult<&str, Vec<ZFSPoolVDevState>> {
|
||||||
|
|
||||||
// skip header
|
// skip header
|
||||||
let (i, _) = tag("NAME")(i)?;
|
let (i, _) = tag("NAME")(i)?;
|
||||||
let (i, _) = multispace1(i)?;
|
let (i, _) = multispace1(i)?;
|
||||||
|
@ -130,8 +129,10 @@ fn space_indented_line(indent: usize) -> impl Fn(&str) -> IResult<&str, &str> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
n = &n[1..];
|
n = &n[1..];
|
||||||
if len >= indent { break; }
|
if len >= indent {
|
||||||
};
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if len != indent {
|
if len != indent {
|
||||||
return Err(parse_error(i, "not correctly indented"));
|
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, prefix) = take_while1(|c| c != ':')(i)?;
|
||||||
let (i, _) = tag(":")(i)?;
|
let (i, _) = tag(":")(i)?;
|
||||||
let (i, mut value) = take_while(|c| c != '\n')(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)?;
|
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 {
|
if let Some(cont) = cont {
|
||||||
let (n, _) = line_ending(n)?;
|
let (n, _) = line_ending(n)?;
|
||||||
i = n;
|
i = n;
|
||||||
if !value.is_empty() { value.push('\n'); }
|
if !value.is_empty() {
|
||||||
|
value.push('\n');
|
||||||
|
}
|
||||||
value.push_str(cont);
|
value.push_str(cont);
|
||||||
} else {
|
} else {
|
||||||
if field == "config" {
|
if field == "config" {
|
||||||
|
@ -233,7 +238,9 @@ where
|
||||||
while vdev_level < cur.level {
|
while vdev_level < cur.level {
|
||||||
cur.children_of_parent.push(Value::Object(cur.node));
|
cur.children_of_parent.push(Value::Object(cur.node));
|
||||||
let mut parent = stack.pop().unwrap();
|
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));
|
parent.node.insert("leaf".to_string(), Value::Bool(false));
|
||||||
cur = parent;
|
cur = parent;
|
||||||
|
|
||||||
|
@ -252,16 +259,17 @@ where
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// same indentation level, add to children of the previous level:
|
// same indentation level, add to children of the previous level:
|
||||||
cur.children_of_parent.push(Value::Object(
|
cur.children_of_parent
|
||||||
replace(&mut cur.node, node),
|
.push(Value::Object(replace(&mut cur.node, node)));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while !stack.is_empty() {
|
while !stack.is_empty() {
|
||||||
cur.children_of_parent.push(Value::Object(cur.node));
|
cur.children_of_parent.push(Value::Object(cur.node));
|
||||||
let mut parent = stack.pop().unwrap();
|
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));
|
parent.node.insert("leaf".to_string(), Value::Bool(false));
|
||||||
cur = parent;
|
cur = parent;
|
||||||
}
|
}
|
||||||
|
@ -281,6 +289,7 @@ fn test_vdev_list_to_tree() {
|
||||||
msg: None,
|
msg: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
let input = vec![
|
let input = vec![
|
||||||
//ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT },
|
//ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT },
|
||||||
ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT },
|
ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT },
|
||||||
|
@ -351,16 +360,14 @@ fn test_vdev_list_to_tree() {
|
||||||
}],\
|
}],\
|
||||||
\"leaf\":false\
|
\"leaf\":false\
|
||||||
}";
|
}";
|
||||||
let expected: Value = serde_json::from_str(EXPECTED)
|
let expected: Value =
|
||||||
.expect("failed to parse expected json value");
|
serde_json::from_str(EXPECTED).expect("failed to parse expected json value");
|
||||||
|
|
||||||
let tree = vdev_list_to_tree(&input)
|
let tree = vdev_list_to_tree(&input).expect("failed to turn valid vdev list into a tree");
|
||||||
.expect("failed to turn valid vdev list into a tree");
|
|
||||||
assert_eq!(tree, expected);
|
assert_eq!(tree, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zpool_status(pool: &str) -> Result<Vec<(String, String)>, Error> {
|
pub fn zpool_status(pool: &str) -> Result<Vec<(String, String)>, Error> {
|
||||||
|
|
||||||
let mut command = std::process::Command::new("zpool");
|
let mut command = std::process::Command::new("zpool");
|
||||||
command.args(&["status", "-p", "-P", pool]);
|
command.args(&["status", "-p", "-P", pool]);
|
||||||
|
|
||||||
|
@ -390,7 +397,6 @@ fn test_parse(output: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zpool_status_parser() -> Result<(), Error> {
|
fn test_zpool_status_parser() -> Result<(), Error> {
|
||||||
|
|
||||||
let output = r###" pool: tank
|
let output = r###" pool: tank
|
||||||
state: DEGRADED
|
state: DEGRADED
|
||||||
status: One or more devices could not be opened. Sufficient replicas exist for
|
status: One or more devices could not be opened. Sufficient replicas exist for
|
||||||
|
@ -418,7 +424,6 @@ errors: No known data errors
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zpool_status_parser2() -> Result<(), Error> {
|
fn test_zpool_status_parser2() -> Result<(), Error> {
|
||||||
|
|
||||||
// Note: this input create TABS
|
// Note: this input create TABS
|
||||||
let output = r###" pool: btest
|
let output = r###" pool: btest
|
||||||
state: ONLINE
|
state: ONLINE
|
||||||
|
@ -443,7 +448,6 @@ errors: No known data errors
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zpool_status_parser3() -> Result<(), Error> {
|
fn test_zpool_status_parser3() -> Result<(), Error> {
|
||||||
|
|
||||||
let output = r###" pool: bt-est
|
let output = r###" pool: bt-est
|
||||||
state: ONLINE
|
state: ONLINE
|
||||||
scan: none requested
|
scan: none requested
|
||||||
|
@ -468,7 +472,6 @@ errors: No known data errors
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zpool_status_parser_spares() -> Result<(), Error> {
|
fn test_zpool_status_parser_spares() -> Result<(), Error> {
|
||||||
|
|
||||||
let output = r###" pool: tank
|
let output = r###" pool: tank
|
||||||
state: ONLINE
|
state: ONLINE
|
||||||
scan: none requested
|
scan: none requested
|
||||||
|
|
Loading…
Reference in New Issue