all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH proxmox-backup 14/26] tools: replace disks module with proxmox-disks
Date: Thu, 12 Mar 2026 14:52:15 +0100	[thread overview]
Message-ID: <20260312135229.420729-15-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260312135229.420729-1-l.wagner@proxmox.com>

This commit replaces the disks module with the proxmox-disks crate. It
is extracted to enable disk metric collection in PDM.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 Cargo.toml                             |    3 +
 src/api2/admin/datastore.rs            |   10 +-
 src/api2/config/datastore.rs           |    2 +-
 src/api2/node/disks/directory.rs       |   10 +-
 src/api2/node/disks/mod.rs             |   20 +-
 src/api2/node/disks/zfs.rs             |   14 +-
 src/bin/proxmox_backup_manager/disk.rs |    9 +-
 src/server/metric_collection/mod.rs    |    3 +-
 src/tools/disks/lvm.rs                 |   60 -
 src/tools/disks/mod.rs                 | 1394 ------------------------
 src/tools/disks/smart.rs               |  227 ----
 src/tools/disks/zfs.rs                 |  205 ----
 src/tools/mod.rs                       |    1 -
 13 files changed, 33 insertions(+), 1925 deletions(-)
 delete mode 100644 src/tools/disks/lvm.rs
 delete mode 100644 src/tools/disks/mod.rs
 delete mode 100644 src/tools/disks/smart.rs
 delete mode 100644 src/tools/disks/zfs.rs

diff --git a/Cargo.toml b/Cargo.toml
index 57f6aa88e..03a98de64 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,6 +62,7 @@ proxmox-borrow = "1"
 proxmox-compression = "1.0.1"
 proxmox-config-digest = "1"
 proxmox-daemon = "1"
+proxmox-disks = "0.1"
 proxmox-fuse = "2"
 proxmox-docgen = "1"
 proxmox-http = { version = "1.0.2", features = [ "client", "http-helpers", "websocket" ] } # see below
@@ -224,6 +225,7 @@ proxmox-compression.workspace = true
 proxmox-config-digest.workspace = true
 proxmox-daemon.workspace = true
 proxmox-docgen.workspace = true
+proxmox-disks = { workspace =  true, features = ["api-types"] }
 proxmox-http = { workspace = true, features = [ "body", "client-trait", "proxmox-async", "rate-limited-stream" ] } # pbs-client doesn't use these
 proxmox-human-byte.workspace = true
 proxmox-io.workspace = true
@@ -290,6 +292,7 @@ proxmox-rrd-api-types.workspace = true
 #proxmox-config-digest = { path = "../proxmox/proxmox-config-digest" }
 #proxmox-daemon = { path = "../proxmox/proxmox-daemon" }
 #proxmox-docgen = { path = "../proxmox/proxmox-docgen" }
+#proxmox-disks = { path = "../proxmox/proxmox-disks" }
 #proxmox-http = { path = "../proxmox/proxmox-http" }
 #proxmox-http-error = { path = "../proxmox/proxmox-http-error" }
 #proxmox-human-byte = { path = "../proxmox/proxmox-human-byte" }
diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
index 88ad5d53b..d75e10e37 100644
--- a/src/api2/admin/datastore.rs
+++ b/src/api2/admin/datastore.rs
@@ -1874,7 +1874,7 @@ pub fn get_rrd_stats(
     _param: Value,
 ) -> Result<Value, Error> {
     let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
-    let disk_manager = crate::tools::disks::DiskManage::new();
+    let disk_manager = proxmox_disks::DiskManage::new();
 
     let mut rrd_fields = vec![
         "total",
@@ -2340,7 +2340,7 @@ fn setup_mounted_device(datastore: &DataStoreConfig, tmp_mount_path: &str) -> Re
         datastore.name, datastore.path, mount_point
     );
 
-    crate::tools::disks::bind_mount(Path::new(&full_store_path), Path::new(&mount_point))
+    proxmox_disks::bind_mount(Path::new(&full_store_path), Path::new(&mount_point))
 }
 
 /// Here we
@@ -2377,13 +2377,13 @@ pub fn do_mount_device(datastore: DataStoreConfig) -> Result<bool, Error> {
         )?;
 
         info!("temporarily mounting '{uuid}' to '{}'", tmp_mount_path);
-        crate::tools::disks::mount_by_uuid(uuid, Path::new(&tmp_mount_path))
+        proxmox_disks::mount_by_uuid(uuid, Path::new(&tmp_mount_path))
             .map_err(|e| format_err!("mounting to tmp path failed: {e}"))?;
 
         let setup_result = setup_mounted_device(&datastore, &tmp_mount_path);
 
         let mut unmounted = true;
-        if let Err(e) = crate::tools::disks::unmount_by_mountpoint(Path::new(&tmp_mount_path)) {
+        if let Err(e) = proxmox_disks::unmount_by_mountpoint(Path::new(&tmp_mount_path)) {
             unmounted = false;
             warn!("unmounting from tmp path '{tmp_mount_path} failed: {e}'");
         }
@@ -2614,7 +2614,7 @@ fn do_unmount_device(
 
     let mount_point = datastore.absolute_path();
     run_maintenance_locked(&datastore.name, MaintenanceType::Unmount, worker, || {
-        crate::tools::disks::unmount_by_mountpoint(Path::new(&mount_point))
+        proxmox_disks::unmount_by_mountpoint(Path::new(&mount_point))
     })
 }
 
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index f845fe2d0..034daf14b 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -8,6 +8,7 @@ use http_body_util::BodyExt;
 use serde_json::Value;
 use tracing::{info, warn};
 
+use proxmox_disks::unmount_by_mountpoint;
 use proxmox_router::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType};
 use proxmox_schema::{api, param_bail, ApiType};
 use proxmox_section_config::SectionConfigData;
@@ -37,7 +38,6 @@ use proxmox_rest_server::WorkerTask;
 use proxmox_s3_client::{S3ObjectKey, S3_HTTP_REQUEST_TIMEOUT};
 
 use crate::server::jobstate;
-use crate::tools::disks::unmount_by_mountpoint;
 
 #[derive(Default, serde::Deserialize, serde::Serialize)]
 #[serde(rename_all = "kebab-case")]
diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
index c37d65b8d..cd8c6b4e5 100644
--- a/src/api2/node/disks/directory.rs
+++ b/src/api2/node/disks/directory.rs
@@ -1,11 +1,15 @@
+use std::os::linux::fs::MetadataExt;
 use std::sync::LazyLock;
 
 use ::serde::{Deserialize, Serialize};
 use anyhow::{bail, Error};
 use serde_json::json;
-use std::os::linux::fs::MetadataExt;
 use tracing::info;
 
+use proxmox_disks::{
+    create_file_system, create_single_linux_partition, get_fs_uuid, DiskManage, DiskUsageQuery,
+    DiskUsageType, FileSystemType,
+};
 use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
 use proxmox_schema::api;
 use proxmox_section_config::SectionConfigData;
@@ -15,10 +19,6 @@ use pbs_api_types::{
     PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA,
 };
 
-use crate::tools::disks::{
-    create_file_system, create_single_linux_partition, get_fs_uuid, DiskManage, DiskUsageQuery,
-    DiskUsageType, FileSystemType,
-};
 use crate::tools::systemd::{self, types::*};
 
 use proxmox_rest_server::WorkerTask;
diff --git a/src/api2/node/disks/mod.rs b/src/api2/node/disks/mod.rs
index abcb8ee40..1c21bb91f 100644
--- a/src/api2/node/disks/mod.rs
+++ b/src/api2/node/disks/mod.rs
@@ -1,23 +1,21 @@
 use anyhow::{bail, Error};
 use serde_json::{json, Value};
-
-use proxmox_router::{
-    list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
-};
-use proxmox_schema::api;
-use proxmox_sortable_macro::sortable;
 use tracing::info;
 
 use pbs_api_types::{
     BLOCKDEVICE_DISK_AND_PARTITION_NAME_SCHEMA, BLOCKDEVICE_NAME_SCHEMA, NODE_SCHEMA,
     PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA,
 };
-
-use crate::tools::disks::{
-    get_smart_data, inititialize_gpt_disk, wipe_blockdev, DiskManage, DiskUsageInfo,
-    DiskUsageQuery, DiskUsageType, SmartData,
+use proxmox_disks::{
+    get_smart_data, initialize_gpt_disk, wipe_blockdev, DiskManage, DiskUsageInfo, DiskUsageQuery,
+    DiskUsageType, SmartData,
 };
 use proxmox_rest_server::WorkerTask;
+use proxmox_router::{
+    list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
+};
+use proxmox_schema::api;
+use proxmox_sortable_macro::sortable;
 
 pub mod directory;
 pub mod zfs;
@@ -174,7 +172,7 @@ pub fn initialize_disk(
             let disk_manager = DiskManage::new();
             let disk_info = disk_manager.disk_by_name(&disk)?;
 
-            inititialize_gpt_disk(&disk_info, uuid.as_deref())?;
+            initialize_gpt_disk(&disk_info, uuid.as_deref())?;
 
             Ok(())
         },
diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
index 3e5a7decf..21f4a3073 100644
--- a/src/api2/node/disks/zfs.rs
+++ b/src/api2/node/disks/zfs.rs
@@ -2,20 +2,18 @@ use anyhow::{bail, Error};
 use serde_json::{json, Value};
 use tracing::{error, info};
 
-use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
-use proxmox_schema::api;
-
 use pbs_api_types::{
     DataStoreConfig, ZfsCompressionType, ZfsRaidLevel, ZpoolListItem, DATASTORE_SCHEMA,
     DISK_ARRAY_SCHEMA, DISK_LIST_SCHEMA, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA,
     ZFS_ASHIFT_SCHEMA, ZPOOL_NAME_SCHEMA,
 };
-
-use crate::tools::disks::{
-    parse_zpool_status_config_tree, vdev_list_to_tree, zpool_list, zpool_status, DiskUsageType,
+use proxmox_disks::{
+    parse_zpool_status_config_tree, vdev_list_to_tree, zpool_list, zpool_status, DiskUsageQuery,
+    DiskUsageType,
 };
-
 use proxmox_rest_server::WorkerTask;
+use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
+use proxmox_schema::api;
 
 #[api(
     protected: true,
@@ -174,7 +172,7 @@ pub fn create_zpool(
         .map(|v| v.as_str().unwrap().to_string())
         .collect();
 
-    let disk_map = crate::tools::disks::DiskUsageQuery::new().query()?;
+    let disk_map = DiskUsageQuery::new().query()?;
     for disk in devices.iter() {
         match disk_map.get(disk) {
             Some(info) => {
diff --git a/src/bin/proxmox_backup_manager/disk.rs b/src/bin/proxmox_backup_manager/disk.rs
index cd7a0b7aa..f10c6e696 100644
--- a/src/bin/proxmox_backup_manager/disk.rs
+++ b/src/bin/proxmox_backup_manager/disk.rs
@@ -1,17 +1,14 @@
 use anyhow::{bail, Error};
 use serde_json::Value;
-
-use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
-use proxmox_schema::api;
 use std::io::IsTerminal;
 
 use pbs_api_types::{
     ZfsCompressionType, ZfsRaidLevel, BLOCKDEVICE_DISK_AND_PARTITION_NAME_SCHEMA,
     BLOCKDEVICE_NAME_SCHEMA, DATASTORE_SCHEMA, DISK_LIST_SCHEMA, ZFS_ASHIFT_SCHEMA,
 };
-use proxmox_backup::tools::disks::{
-    complete_disk_name, complete_partition_name, FileSystemType, SmartAttribute,
-};
+use proxmox_disks::{complete_disk_name, complete_partition_name, FileSystemType, SmartAttribute};
+use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
+use proxmox_schema::api;
 
 use proxmox_backup::api2;
 
diff --git a/src/server/metric_collection/mod.rs b/src/server/metric_collection/mod.rs
index 9b62cbb42..3fa6e9fbf 100644
--- a/src/server/metric_collection/mod.rs
+++ b/src/server/metric_collection/mod.rs
@@ -10,14 +10,13 @@ use anyhow::Error;
 use tokio::join;
 
 use pbs_api_types::{DataStoreConfig, Operation};
+use proxmox_disks::{zfs_dataset_stats, BlockDevStat, DiskManage};
 use proxmox_network_api::{get_network_interfaces, IpLink};
 use proxmox_sys::{
     fs::FileSystemInformation,
     linux::procfs::{Loadavg, ProcFsMemInfo, ProcFsNetDev, ProcFsStat},
 };
 
-use crate::tools::disks::{zfs_dataset_stats, BlockDevStat, DiskManage};
-
 mod metric_server;
 pub(crate) mod pull_metrics;
 pub(crate) mod rrd;
diff --git a/src/tools/disks/lvm.rs b/src/tools/disks/lvm.rs
deleted file mode 100644
index 1456a21c3..000000000
--- a/src/tools/disks/lvm.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use std::collections::HashSet;
-use std::os::unix::fs::MetadataExt;
-use std::sync::LazyLock;
-
-use anyhow::Error;
-use serde_json::Value;
-
-use super::LsblkInfo;
-
-static LVM_UUIDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
-    let mut set = HashSet::new();
-    set.insert("e6d6d379-f507-44c2-a23c-238f2a3df928");
-    set
-});
-
-/// 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> {
-    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",
-    ]);
-
-    let output = proxmox_sys::command::run_command(command, None)?;
-
-    let mut device_set: HashSet<u64> = HashSet::new();
-
-    for info in lsblk_info.iter() {
-        if let Some(partition_type) = &info.partition_type {
-            if LVM_UUIDS.contains(partition_type.as_str()) {
-                let meta = std::fs::metadata(&info.path)?;
-                device_set.insert(meta.rdev());
-            }
-        }
-    }
-
-    let output: Value = output.parse()?;
-
-    match output["report"][0]["pv"].as_array() {
-        Some(list) => {
-            for info in list {
-                if let Some(pv_name) = info["pv_name"].as_str() {
-                    let meta = std::fs::metadata(pv_name)?;
-                    device_set.insert(meta.rdev());
-                }
-            }
-        }
-        None => return Ok(device_set),
-    }
-
-    Ok(device_set)
-}
diff --git a/src/tools/disks/mod.rs b/src/tools/disks/mod.rs
deleted file mode 100644
index 4197d0b0f..000000000
--- a/src/tools/disks/mod.rs
+++ /dev/null
@@ -1,1394 +0,0 @@
-//! Disk query/management utilities for.
-
-use std::collections::{HashMap, HashSet};
-use std::ffi::{OsStr, OsString};
-use std::io;
-use std::os::unix::ffi::{OsStrExt, OsStringExt};
-use std::os::unix::fs::{FileExt, MetadataExt, OpenOptionsExt};
-use std::path::{Path, PathBuf};
-use std::sync::{Arc, LazyLock};
-
-use anyhow::{bail, format_err, Context as _, Error};
-use libc::dev_t;
-use once_cell::sync::OnceCell;
-
-use ::serde::{Deserialize, Serialize};
-
-use proxmox_lang::{io_bail, io_format_err};
-use proxmox_log::info;
-use proxmox_schema::api;
-use proxmox_sys::linux::procfs::{mountinfo::Device, MountInfo};
-
-use pbs_api_types::{BLOCKDEVICE_DISK_AND_PARTITION_NAME_REGEX, BLOCKDEVICE_NAME_REGEX};
-
-use proxmox_parallel_handler::ParallelHandler;
-
-mod zfs;
-pub use zfs::*;
-mod zpool_status;
-pub use zpool_status::*;
-mod zpool_list;
-pub use zpool_list::*;
-mod lvm;
-pub use lvm::*;
-mod smart;
-pub use smart::*;
-
-static ISCSI_PATH_REGEX: LazyLock<regex::Regex> =
-    LazyLock::new(|| regex::Regex::new(r"host[^/]*/session[^/]*").unwrap());
-
-/// Disk management context.
-///
-/// This provides access to disk information with some caching for faster querying of multiple
-/// devices.
-pub struct DiskManage {
-    mount_info: OnceCell<MountInfo>,
-    mounted_devices: OnceCell<HashSet<dev_t>>,
-}
-
-/// Information for a device as returned by lsblk.
-#[derive(Deserialize)]
-pub struct LsblkInfo {
-    /// Path to the device.
-    path: String,
-    /// Partition type GUID.
-    #[serde(rename = "parttype")]
-    partition_type: Option<String>,
-    /// File system label.
-    #[serde(rename = "fstype")]
-    file_system_type: Option<String>,
-    /// File system UUID.
-    uuid: Option<String>,
-}
-
-impl DiskManage {
-    /// Create a new disk management context.
-    pub fn new() -> Arc<Self> {
-        Arc::new(Self {
-            mount_info: OnceCell::new(),
-            mounted_devices: OnceCell::new(),
-        })
-    }
-
-    /// Get the current mount info. This simply caches the result of `MountInfo::read` from the
-    /// `proxmox::sys` module.
-    pub fn mount_info(&self) -> Result<&MountInfo, Error> {
-        self.mount_info.get_or_try_init(MountInfo::read)
-    }
-
-    /// Get a `Disk` from a device node (eg. `/dev/sda`).
-    pub fn disk_by_node<P: AsRef<Path>>(self: Arc<Self>, devnode: P) -> io::Result<Disk> {
-        let devnode = devnode.as_ref();
-
-        let meta = std::fs::metadata(devnode)?;
-        if (meta.mode() & libc::S_IFBLK) == libc::S_IFBLK {
-            self.disk_by_dev_num(meta.rdev())
-        } else {
-            io_bail!("not a block device: {:?}", devnode);
-        }
-    }
-
-    /// Get a `Disk` for a specific device number.
-    pub fn disk_by_dev_num(self: Arc<Self>, devnum: dev_t) -> io::Result<Disk> {
-        self.disk_by_sys_path(format!(
-            "/sys/dev/block/{}:{}",
-            unsafe { libc::major(devnum) },
-            unsafe { libc::minor(devnum) },
-        ))
-    }
-
-    /// Get a `Disk` for a path in `/sys`.
-    pub fn disk_by_sys_path<P: AsRef<Path>>(self: Arc<Self>, path: P) -> io::Result<Disk> {
-        let device = udev::Device::from_syspath(path.as_ref())?;
-        Ok(Disk {
-            manager: self,
-            device,
-            info: Default::default(),
-        })
-    }
-
-    /// Get a `Disk` for a name in `/sys/block/<name>`.
-    pub fn disk_by_name(self: Arc<Self>, name: &str) -> io::Result<Disk> {
-        let syspath = format!("/sys/block/{name}");
-        self.disk_by_sys_path(syspath)
-    }
-
-    /// Get a `Disk` for a name in `/sys/class/block/<name>`.
-    pub fn partition_by_name(self: Arc<Self>, name: &str) -> io::Result<Disk> {
-        let syspath = format!("/sys/class/block/{name}");
-        self.disk_by_sys_path(syspath)
-    }
-
-    /// Gather information about mounted disks:
-    fn mounted_devices(&self) -> Result<&HashSet<dev_t>, Error> {
-        self.mounted_devices
-            .get_or_try_init(|| -> Result<_, Error> {
-                let mut mounted = HashSet::new();
-
-                for (_id, mp) in self.mount_info()? {
-                    let source = match mp.mount_source.as_deref() {
-                        Some(s) => s,
-                        None => continue,
-                    };
-
-                    let path = Path::new(source);
-                    if !path.is_absolute() {
-                        continue;
-                    }
-
-                    let meta = match std::fs::metadata(path) {
-                        Ok(meta) => meta,
-                        Err(ref err) if err.kind() == io::ErrorKind::NotFound => continue,
-                        Err(other) => return Err(Error::from(other)),
-                    };
-
-                    if (meta.mode() & libc::S_IFBLK) != libc::S_IFBLK {
-                        // not a block device
-                        continue;
-                    }
-
-                    mounted.insert(meta.rdev());
-                }
-
-                Ok(mounted)
-            })
-    }
-
-    /// Information about file system type and used device for a path
-    ///
-    /// Returns tuple (fs_type, device, mount_source)
-    pub fn find_mounted_device(
-        &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);
-
-        let root_path = std::path::Path::new("/");
-
-        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(),
-                )));
-            }
-        }
-
-        Ok(None)
-    }
-
-    /// Check whether a specific device node is mounted.
-    ///
-    /// Note that this tries to `stat` the sources of all mount points without caching the result
-    /// of doing so, so this is always somewhat expensive.
-    pub fn is_devnum_mounted(&self, dev: dev_t) -> Result<bool, Error> {
-        self.mounted_devices().map(|mounted| mounted.contains(&dev))
-    }
-}
-
-/// Queries (and caches) various information about a specific disk.
-///
-/// This belongs to a `Disks` and provides information for a single disk.
-pub struct Disk {
-    manager: Arc<DiskManage>,
-    device: udev::Device,
-    info: DiskInfo,
-}
-
-/// Helper struct (so we can initialize this with Default)
-///
-/// We probably want this to be serializable to the same hash type we use in perl currently.
-#[derive(Default)]
-struct DiskInfo {
-    size: OnceCell<u64>,
-    vendor: OnceCell<Option<OsString>>,
-    model: OnceCell<Option<OsString>>,
-    rotational: OnceCell<Option<bool>>,
-    // for perl: #[serde(rename = "devpath")]
-    ata_rotation_rate_rpm: OnceCell<Option<u64>>,
-    // for perl: #[serde(rename = "devpath")]
-    device_path: OnceCell<Option<PathBuf>>,
-    wwn: OnceCell<Option<OsString>>,
-    serial: OnceCell<Option<OsString>>,
-    // for perl: #[serde(skip_serializing)]
-    partition_table_type: OnceCell<Option<OsString>>,
-    // for perl: #[serde(skip_serializing)]
-    partition_entry_scheme: OnceCell<Option<OsString>>,
-    // for perl: #[serde(skip_serializing)]
-    partition_entry_uuid: OnceCell<Option<OsString>>,
-    // for perl: #[serde(skip_serializing)]
-    partition_entry_type: OnceCell<Option<OsString>>,
-    gpt: OnceCell<bool>,
-    // ???
-    bus: OnceCell<Option<OsString>>,
-    // ???
-    fs_type: OnceCell<Option<OsString>>,
-    // ???
-    has_holders: OnceCell<bool>,
-    // ???
-    is_mounted: OnceCell<bool>,
-}
-
-impl Disk {
-    /// Try to get the device number for this disk.
-    ///
-    /// (In udev this can fail...)
-    pub fn devnum(&self) -> Result<dev_t, Error> {
-        // not sure when this can fail...
-        self.device
-            .devnum()
-            .ok_or_else(|| format_err!("failed to get device number"))
-    }
-
-    /// Get the sys-name of this device. (The final component in the `/sys` path).
-    pub fn sysname(&self) -> &OsStr {
-        self.device.sysname()
-    }
-
-    /// Get the this disk's `/sys` path.
-    pub fn syspath(&self) -> &Path {
-        self.device.syspath()
-    }
-
-    /// Get the device node in `/dev`, if any.
-    pub fn device_path(&self) -> Option<&Path> {
-        //self.device.devnode()
-        self.info
-            .device_path
-            .get_or_init(|| self.device.devnode().map(Path::to_owned))
-            .as_ref()
-            .map(PathBuf::as_path)
-    }
-
-    /// Get the parent device.
-    pub fn parent(&self) -> Option<Self> {
-        self.device.parent().map(|parent| Self {
-            manager: self.manager.clone(),
-            device: parent,
-            info: Default::default(),
-        })
-    }
-
-    /// Read from a file in this device's sys path.
-    ///
-    /// Note: path must be a relative path!
-    pub fn read_sys(&self, path: &Path) -> io::Result<Option<Vec<u8>>> {
-        assert!(path.is_relative());
-
-        std::fs::read(self.syspath().join(path))
-            .map(Some)
-            .or_else(|err| {
-                if err.kind() == io::ErrorKind::NotFound {
-                    Ok(None)
-                } else {
-                    Err(err)
-                }
-            })
-    }
-
-    /// Convenience wrapper for reading a `/sys` file which contains just a simple `OsString`.
-    pub fn read_sys_os_str<P: AsRef<Path>>(&self, path: P) -> io::Result<Option<OsString>> {
-        Ok(self.read_sys(path.as_ref())?.map(|mut v| {
-            if Some(&b'\n') == v.last() {
-                v.pop();
-            }
-            OsString::from_vec(v)
-        }))
-    }
-
-    /// Convenience wrapper for reading a `/sys` file which contains just a simple utf-8 string.
-    pub fn read_sys_str<P: AsRef<Path>>(&self, path: P) -> io::Result<Option<String>> {
-        Ok(match self.read_sys(path.as_ref())? {
-            Some(data) => Some(String::from_utf8(data).map_err(io::Error::other)?),
-            None => None,
-        })
-    }
-
-    /// Convenience wrapper for unsigned integer `/sys` values up to 64 bit.
-    pub fn read_sys_u64<P: AsRef<Path>>(&self, path: P) -> io::Result<Option<u64>> {
-        Ok(match self.read_sys_str(path)? {
-            Some(data) => Some(data.trim().parse().map_err(io::Error::other)?),
-            None => None,
-        })
-    }
-
-    /// 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(|| {
-                io_format_err!(
-                    "failed to get disk size from {:?}",
-                    self.syspath().join("size"),
-                )
-            })
-        })?)
-    }
-
-    /// Get the device vendor (`/sys/.../device/vendor`) entry if available.
-    pub fn vendor(&self) -> io::Result<Option<&OsStr>> {
-        Ok(self
-            .info
-            .vendor
-            .get_or_try_init(|| self.read_sys_os_str("device/vendor"))?
-            .as_ref()
-            .map(OsString::as_os_str))
-    }
-
-    /// Get the device model (`/sys/.../device/model`) entry if available.
-    pub fn model(&self) -> Option<&OsStr> {
-        self.info
-            .model
-            .get_or_init(|| self.device.property_value("ID_MODEL").map(OsStr::to_owned))
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Check whether this is a rotational disk.
-    ///
-    /// Returns `None` if there's no `queue/rotational` file, in which case no information is
-    /// known. `Some(false)` if `queue/rotational` is zero, `Some(true)` if it has a non-zero
-    /// value.
-    pub fn rotational(&self) -> io::Result<Option<bool>> {
-        Ok(*self
-            .info
-            .rotational
-            .get_or_try_init(|| -> io::Result<Option<bool>> {
-                Ok(self.read_sys_u64("queue/rotational")?.map(|n| n != 0))
-            })?)
-    }
-
-    /// Get the WWN if available.
-    pub fn wwn(&self) -> Option<&OsStr> {
-        self.info
-            .wwn
-            .get_or_init(|| self.device.property_value("ID_WWN").map(|v| v.to_owned()))
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Get the device serial if available.
-    pub fn serial(&self) -> Option<&OsStr> {
-        self.info
-            .serial
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_SERIAL_SHORT")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Get the ATA rotation rate value from udev. This is not necessarily the same as sysfs'
-    /// `rotational` value.
-    pub fn ata_rotation_rate_rpm(&self) -> Option<u64> {
-        *self.info.ata_rotation_rate_rpm.get_or_init(|| {
-            std::str::from_utf8(
-                self.device
-                    .property_value("ID_ATA_ROTATION_RATE_RPM")?
-                    .as_bytes(),
-            )
-            .ok()?
-            .parse()
-            .ok()
-        })
-    }
-
-    /// Get the partition table type, if any.
-    pub fn partition_table_type(&self) -> Option<&OsStr> {
-        self.info
-            .partition_table_type
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_PART_TABLE_TYPE")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Check if this contains a GPT partition table.
-    pub fn has_gpt(&self) -> bool {
-        *self.info.gpt.get_or_init(|| {
-            self.partition_table_type()
-                .map(|s| s == "gpt")
-                .unwrap_or(false)
-        })
-    }
-
-    /// Get the partitioning scheme of which this device is a partition.
-    pub fn partition_entry_scheme(&self) -> Option<&OsStr> {
-        self.info
-            .partition_entry_scheme
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_PART_ENTRY_SCHEME")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Check if this is a partition.
-    pub fn is_partition(&self) -> bool {
-        self.partition_entry_scheme().is_some()
-    }
-
-    /// Get the type of partition entry (ie. type UUID from the entry in the GPT partition table).
-    pub fn partition_entry_type(&self) -> Option<&OsStr> {
-        self.info
-            .partition_entry_type
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_PART_ENTRY_TYPE")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Get the partition entry UUID (ie. the UUID from the entry in the GPT partition table).
-    pub fn partition_entry_uuid(&self) -> Option<&OsStr> {
-        self.info
-            .partition_entry_uuid
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_PART_ENTRY_UUID")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Get the bus type used for this disk.
-    pub fn bus(&self) -> Option<&OsStr> {
-        self.info
-            .bus
-            .get_or_init(|| self.device.property_value("ID_BUS").map(|v| v.to_owned()))
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Attempt to guess the disk type.
-    pub fn guess_disk_type(&self) -> io::Result<DiskType> {
-        Ok(match self.rotational()? {
-            Some(false) => DiskType::Ssd,
-            Some(true) => DiskType::Hdd,
-            None => match self.ata_rotation_rate_rpm() {
-                Some(_) => DiskType::Hdd,
-                None => match self.bus() {
-                    Some(bus) if bus == "usb" => DiskType::Usb,
-                    _ => DiskType::Unknown,
-                },
-            },
-        })
-    }
-
-    /// Get the file system type found on the disk, if any.
-    ///
-    /// Note that `None` may also just mean "unknown".
-    pub fn fs_type(&self) -> Option<&OsStr> {
-        self.info
-            .fs_type
-            .get_or_init(|| {
-                self.device
-                    .property_value("ID_FS_TYPE")
-                    .map(|v| v.to_owned())
-            })
-            .as_ref()
-            .map(OsString::as_os_str)
-    }
-
-    /// Check if there are any "holders" in `/sys`. This usually means the device is in use by
-    /// 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)
-            })?)
-    }
-
-    /// Check if this disk is mounted.
-    pub fn is_mounted(&self) -> Result<bool, Error> {
-        Ok(*self
-            .info
-            .is_mounted
-            .get_or_try_init(|| self.manager.is_devnum_mounted(self.devnum()?))?)
-    }
-
-    /// Read block device stats
-    ///
-    /// see <https://www.kernel.org/doc/Documentation/block/stat.txt>
-    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| s.parse().unwrap_or_default())
-                .collect();
-
-            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_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();
-
-        let mut map = HashMap::new();
-
-        for item in proxmox_sys::fs::read_subdir(libc::AT_FDCWD, sys_path)? {
-            let item = item?;
-            let name = match item.file_name().to_str() {
-                Ok(name) => name,
-                Err(_) => continue, // skip non utf8 entries
-            };
-
-            if !name.starts_with(&device) {
-                continue;
-            }
-
-            let mut part_path = sys_path.to_owned();
-            part_path.push(name);
-
-            let disk_part = self.manager.clone().disk_by_sys_path(&part_path)?;
-
-            if let Some(partition) = disk_part.read_sys_u64("partition")? {
-                map.insert(partition, disk_part);
-            }
-        }
-
-        Ok(map)
-    }
-}
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
-/// This is just a rough estimate for a "type" of disk.
-pub enum DiskType {
-    /// We know nothing.
-    Unknown,
-
-    /// May also be a USB-HDD.
-    Hdd,
-
-    /// May also be a USB-SSD.
-    Ssd,
-
-    /// Some kind of USB disk, but we don't know more than that.
-    Usb,
-}
-
-#[derive(Debug)]
-/// Represents the contents of the `/sys/block/<dev>/stat` file.
-pub struct BlockDevStat {
-    pub read_ios: u64,
-    pub read_sectors: u64,
-    pub write_ios: u64,
-    pub write_sectors: u64,
-    pub io_ticks: u64, // milliseconds
-}
-
-/// 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,uuid"]);
-
-    let output = proxmox_sys::command::run_command(command, None)?;
-
-    let mut output: serde_json::Value = output.parse()?;
-
-    Ok(serde_json::from_value(output["blockdevices"].take())?)
-}
-
-/// 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> {
-    let mut device_set: HashSet<u64> = HashSet::new();
-
-    for info in lsblk_info.iter() {
-        if info.file_system_type.is_some() {
-            let meta = std::fs::metadata(&info.path)?;
-            device_set.insert(meta.rdev());
-        }
-    }
-
-    Ok(device_set)
-}
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-/// What a block device partition is used for.
-pub enum PartitionUsageType {
-    /// Partition is not used (as far we can tell)
-    Unused,
-    /// Partition is used by LVM
-    LVM,
-    /// Partition is used by ZFS
-    ZFS,
-    /// Partition is ZFS reserved
-    ZfsReserved,
-    /// Partition is an EFI partition
-    EFI,
-    /// Partition is a BIOS partition
-    BIOS,
-    /// Partition contains a file system label
-    FileSystem,
-}
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-/// What a block device (disk) is used for.
-pub enum DiskUsageType {
-    /// Disk is not used (as far we can tell)
-    Unused,
-    /// Disk is mounted
-    Mounted,
-    /// Disk is used by LVM
-    LVM,
-    /// Disk is used by ZFS
-    ZFS,
-    /// Disk is used by device-mapper
-    DeviceMapper,
-    /// Disk has partitions
-    Partitions,
-    /// Disk contains a file system label
-    FileSystem,
-}
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Basic information about a partition
-pub struct PartitionInfo {
-    /// The partition name
-    pub name: String,
-    /// What the partition is used for
-    pub used: PartitionUsageType,
-    /// Is the partition mounted
-    pub mounted: bool,
-    /// The filesystem of the partition
-    pub filesystem: Option<String>,
-    /// The partition devpath
-    pub devpath: Option<String>,
-    /// Size in bytes
-    pub size: Option<u64>,
-    /// GPT partition
-    pub gpt: bool,
-    /// UUID
-    pub uuid: Option<String>,
-}
-
-#[api(
-    properties: {
-        used: {
-            type: DiskUsageType,
-        },
-        "disk-type": {
-            type: DiskType,
-        },
-        status: {
-            type: SmartStatus,
-        },
-        partitions: {
-            optional: true,
-            items: {
-                type: PartitionInfo
-            }
-        }
-    }
-)]
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-/// Information about how a Disk is used
-pub struct DiskUsageInfo {
-    /// Disk name (`/sys/block/<name>`)
-    pub name: String,
-    pub used: DiskUsageType,
-    pub disk_type: DiskType,
-    pub status: SmartStatus,
-    /// Disk wearout
-    pub wearout: Option<f64>,
-    /// Vendor
-    pub vendor: Option<String>,
-    /// Model
-    pub model: Option<String>,
-    /// WWN
-    pub wwn: Option<String>,
-    /// Disk size
-    pub size: u64,
-    /// Serisal number
-    pub serial: Option<String>,
-    /// Partitions on the device
-    pub partitions: Option<Vec<PartitionInfo>>,
-    /// Linux device path (/dev/xxx)
-    pub devpath: Option<String>,
-    /// Set if disk contains a GPT partition table
-    pub gpt: bool,
-    /// RPM
-    pub rpm: Option<u64>,
-}
-
-fn scan_partitions(
-    disk_manager: Arc<DiskManage>,
-    lvm_devices: &HashSet<u64>,
-    zfs_devices: &HashSet<u64>,
-    device: &str,
-) -> Result<DiskUsageType, Error> {
-    let mut sys_path = std::path::PathBuf::from("/sys/block");
-    sys_path.push(device);
-
-    let mut used = DiskUsageType::Unused;
-
-    let mut found_lvm = false;
-    let mut found_zfs = false;
-    let mut found_mountpoints = false;
-    let mut found_dm = false;
-    let mut found_partitions = false;
-
-    for item in proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &sys_path)? {
-        let item = item?;
-        let name = match item.file_name().to_str() {
-            Ok(name) => name,
-            Err(_) => continue, // skip non utf8 entries
-        };
-        if !name.starts_with(device) {
-            continue;
-        }
-
-        found_partitions = true;
-
-        let mut part_path = sys_path.clone();
-        part_path.push(name);
-
-        let data = disk_manager.clone().disk_by_sys_path(&part_path)?;
-
-        let devnum = data.devnum()?;
-
-        if lvm_devices.contains(&devnum) {
-            found_lvm = true;
-        }
-
-        if data.is_mounted()? {
-            found_mountpoints = true;
-        }
-
-        if data.has_holders()? {
-            found_dm = true;
-        }
-
-        if zfs_devices.contains(&devnum) {
-            found_zfs = true;
-        }
-    }
-
-    if found_mountpoints {
-        used = DiskUsageType::Mounted;
-    } else if found_lvm {
-        used = DiskUsageType::LVM;
-    } else if found_zfs {
-        used = DiskUsageType::ZFS;
-    } else if found_dm {
-        used = DiskUsageType::DeviceMapper;
-    } else if found_partitions {
-        used = DiskUsageType::Partitions;
-    }
-
-    Ok(used)
-}
-
-pub struct DiskUsageQuery {
-    smart: bool,
-    partitions: bool,
-}
-
-impl Default for DiskUsageQuery {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl DiskUsageQuery {
-    pub const fn new() -> Self {
-        Self {
-            smart: true,
-            partitions: false,
-        }
-    }
-
-    pub fn smart(&mut self, smart: bool) -> &mut Self {
-        self.smart = smart;
-        self
-    }
-
-    pub fn partitions(&mut self, partitions: bool) -> &mut Self {
-        self.partitions = partitions;
-        self
-    }
-
-    pub fn query(&self) -> Result<HashMap<String, DiskUsageInfo>, Error> {
-        get_disks(None, !self.smart, self.partitions)
-    }
-
-    pub fn find(&self, disk: &str) -> Result<DiskUsageInfo, Error> {
-        let mut map = get_disks(Some(vec![disk.to_string()]), !self.smart, self.partitions)?;
-        if let Some(info) = map.remove(disk) {
-            Ok(info)
-        } else {
-            bail!("failed to get disk usage info - internal error"); // should not happen
-        }
-    }
-
-    pub fn find_all(&self, disks: Vec<String>) -> Result<HashMap<String, DiskUsageInfo>, Error> {
-        get_disks(Some(disks), !self.smart, self.partitions)
-    }
-}
-
-fn get_partitions_info(
-    partitions: HashMap<u64, Disk>,
-    lvm_devices: &HashSet<u64>,
-    zfs_devices: &HashSet<u64>,
-    file_system_devices: &HashSet<u64>,
-    lsblk_infos: &[LsblkInfo],
-) -> Vec<PartitionInfo> {
-    partitions
-        .values()
-        .map(|disk| {
-            let devpath = disk
-                .device_path()
-                .map(|p| p.to_owned())
-                .map(|p| p.to_string_lossy().to_string());
-
-            let mut used = PartitionUsageType::Unused;
-
-            if let Ok(devnum) = disk.devnum() {
-                if lvm_devices.contains(&devnum) {
-                    used = PartitionUsageType::LVM;
-                } else if zfs_devices.contains(&devnum) {
-                    used = PartitionUsageType::ZFS;
-                } else if file_system_devices.contains(&devnum) {
-                    used = PartitionUsageType::FileSystem;
-                }
-            }
-
-            let mounted = disk.is_mounted().unwrap_or(false);
-            let mut filesystem = None;
-            let mut uuid = None;
-            if let Some(devpath) = devpath.as_ref() {
-                for info in lsblk_infos.iter().filter(|i| i.path.eq(devpath)) {
-                    uuid = info
-                        .uuid
-                        .clone()
-                        .filter(|uuid| pbs_api_types::UUID_REGEX.is_match(uuid));
-                    used = match info.partition_type.as_deref() {
-                        Some("21686148-6449-6e6f-744e-656564454649") => PartitionUsageType::BIOS,
-                        Some("c12a7328-f81f-11d2-ba4b-00a0c93ec93b") => PartitionUsageType::EFI,
-                        Some("6a945a3b-1dd2-11b2-99a6-080020736631") => {
-                            PartitionUsageType::ZfsReserved
-                        }
-                        _ => used,
-                    };
-                    if used == PartitionUsageType::FileSystem {
-                        filesystem.clone_from(&info.file_system_type);
-                    }
-                }
-            }
-
-            PartitionInfo {
-                name: disk.sysname().to_str().unwrap_or("?").to_string(),
-                devpath,
-                used,
-                mounted,
-                filesystem,
-                size: disk.size().ok(),
-                gpt: disk.has_gpt(),
-                uuid,
-            }
-        })
-        .collect()
-}
-
-/// Get disk usage information for multiple disks
-fn get_disks(
-    // filter - list of device names (without leading /dev)
-    disks: Option<Vec<String>>,
-    // do no include data from smartctl
-    no_smart: bool,
-    // include partitions
-    include_partitions: 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 lvm_devices = get_lvm_devices(&lsblk_info)?;
-
-    let file_system_devices = get_file_system_devices(&lsblk_info)?;
-
-    // fixme: ceph journals/volumes
-
-    let mut result = HashMap::new();
-    let mut device_paths = Vec::new();
-
-    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;
-            }
-        }
-
-        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
-            }
-        }
-
-        let disk = disk_manager.clone().disk_by_sys_path(&sys_path)?;
-
-        let devnum = disk.devnum()?;
-
-        let size = match disk.size() {
-            Ok(size) => size,
-            Err(_) => continue, // skip devices with unreadable size
-        };
-
-        let disk_type = match disk.guess_disk_type() {
-            Ok(disk_type) => disk_type,
-            Err(_) => continue, // skip devices with undetectable type
-        };
-
-        let mut usage = DiskUsageType::Unused;
-
-        if lvm_devices.contains(&devnum) {
-            usage = DiskUsageType::LVM;
-        }
-
-        match disk.is_mounted() {
-            Ok(true) => usage = DiskUsageType::Mounted,
-            Ok(false) => {}
-            Err(_) => continue, // skip devices with undetectable mount status
-        }
-
-        if zfs_devices.contains(&devnum) {
-            usage = DiskUsageType::ZFS;
-        }
-
-        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())
-            .map(|p| p.to_string_lossy().to_string());
-
-        device_paths.push((name.clone(), devpath.clone()));
-
-        let wwn = disk.wwn().map(|s| s.to_string_lossy().into_owned());
-
-        let partitions: Option<Vec<PartitionInfo>> = if include_partitions {
-            disk.partitions().map_or(None, |parts| {
-                Some(get_partitions_info(
-                    parts,
-                    &lvm_devices,
-                    &zfs_devices,
-                    &file_system_devices,
-                    &lsblk_info,
-                ))
-            })
-        } else {
-            None
-        };
-
-        if usage != DiskUsageType::Mounted {
-            match scan_partitions(disk_manager.clone(), &lvm_devices, &zfs_devices, &name) {
-                Ok(part_usage) => {
-                    if part_usage != DiskUsageType::Unused {
-                        usage = part_usage;
-                    }
-                }
-                Err(_) => continue, // skip devices if scan_partitions fail
-            };
-        }
-
-        if usage == DiskUsageType::Unused && file_system_devices.contains(&devnum) {
-            usage = DiskUsageType::FileSystem;
-        }
-
-        if usage == DiskUsageType::Unused && disk.has_holders()? {
-            usage = DiskUsageType::DeviceMapper;
-        }
-
-        let info = DiskUsageInfo {
-            name: name.clone(),
-            vendor,
-            model,
-            partitions,
-            serial,
-            devpath,
-            size,
-            wwn,
-            disk_type,
-            status: SmartStatus::Unknown,
-            wearout: None,
-            used: usage,
-            gpt: disk.has_gpt(),
-            rpm: disk.ata_rotation_rate_rpm(),
-        };
-
-        result.insert(name, info);
-    }
-
-    if !no_smart {
-        let (tx, rx) = crossbeam_channel::bounded(result.len());
-
-        let parallel_handler =
-            ParallelHandler::new("smartctl data", 4, move |device: (String, String)| {
-                match get_smart_data(Path::new(&device.1), false) {
-                    Ok(smart_data) => tx.send((device.0, smart_data))?,
-                    // do not fail the whole disk output just because smartctl couldn't query one
-                    Err(err) => log::error!("failed to gather smart data for {} – {err}", device.1),
-                }
-                Ok(())
-            });
-
-        for (name, path) in device_paths.into_iter() {
-            if let Some(p) = path {
-                parallel_handler.send((name, p))?;
-            }
-        }
-
-        parallel_handler.complete()?;
-        while let Ok(msg) = rx.recv() {
-            if let Some(value) = result.get_mut(&msg.0) {
-                value.wearout = msg.1.wearout;
-                value.status = msg.1.status;
-            }
-        }
-    }
-    Ok(result)
-}
-
-/// 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()),
-    };
-
-    let mut command = std::process::Command::new("blockdev");
-    command.arg("--rereadpt");
-    command.arg(disk_path);
-
-    proxmox_sys::command::run_command(command, None)?;
-
-    Ok(())
-}
-
-/// 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()),
-    };
-
-    let uuid = uuid.unwrap_or("R"); // R .. random disk GUID
-
-    let mut command = std::process::Command::new("sgdisk");
-    command.arg(disk_path);
-    command.args(["-U", uuid]);
-
-    proxmox_sys::command::run_command(command, None)?;
-
-    Ok(())
-}
-
-/// Wipes all labels, the first 200 MiB, and the last 4096 bytes of a disk/partition.
-/// If called with a partition, also sets the partition type to 0x83 'Linux filesystem'.
-pub fn wipe_blockdev(disk: &Disk) -> Result<(), Error> {
-    let disk_path = match disk.device_path() {
-        Some(path) => path,
-        None => bail!("disk {:?} has no node in /dev", disk.syspath()),
-    };
-
-    let is_partition = disk.is_partition();
-
-    let mut to_wipe: Vec<PathBuf> = Vec::new();
-
-    let partitions_map = disk.partitions()?;
-    for part_disk in partitions_map.values() {
-        let part_path = match part_disk.device_path() {
-            Some(path) => path,
-            None => bail!("disk {:?} has no node in /dev", part_disk.syspath()),
-        };
-        to_wipe.push(part_path.to_path_buf());
-    }
-
-    to_wipe.push(disk_path.to_path_buf());
-
-    info!("Wiping block device {}", disk_path.display());
-
-    let mut wipefs_command = std::process::Command::new("wipefs");
-    wipefs_command.arg("--all").args(&to_wipe);
-
-    let wipefs_output = proxmox_sys::command::run_command(wipefs_command, None)?;
-    info!("wipefs output: {wipefs_output}");
-
-    zero_disk_start_and_end(disk)?;
-
-    if is_partition {
-        // set the partition type to 0x83 'Linux filesystem'
-        change_parttype(disk, "8300")?;
-    }
-
-    Ok(())
-}
-
-pub fn zero_disk_start_and_end(disk: &Disk) -> Result<(), Error> {
-    let disk_path = match disk.device_path() {
-        Some(path) => path,
-        None => bail!("disk {:?} has no node in /dev", disk.syspath()),
-    };
-
-    let disk_size = disk.size()?;
-    let file = std::fs::OpenOptions::new()
-        .write(true)
-        .custom_flags(libc::O_CLOEXEC | libc::O_DSYNC)
-        .open(disk_path)
-        .with_context(|| "failed to open device {disk_path:?} for writing")?;
-    let write_size = disk_size.min(200 * 1024 * 1024);
-    let zeroes = proxmox_io::boxed::zeroed(write_size as usize);
-    file.write_all_at(&zeroes, 0)
-        .with_context(|| "failed to wipe start of device {disk_path:?}")?;
-    if disk_size > write_size {
-        file.write_all_at(&zeroes[0..4096], disk_size - 4096)
-            .with_context(|| "failed to wipe end of device {disk_path:?}")?;
-    }
-    Ok(())
-}
-
-pub fn change_parttype(part_disk: &Disk, part_type: &str) -> Result<(), Error> {
-    let part_path = match part_disk.device_path() {
-        Some(path) => path,
-        None => bail!("disk {:?} has no node in /dev", part_disk.syspath()),
-    };
-    if let Ok(stat) = nix::sys::stat::stat(part_path) {
-        let mut sgdisk_command = std::process::Command::new("sgdisk");
-        let major = unsafe { libc::major(stat.st_rdev) };
-        let minor = unsafe { libc::minor(stat.st_rdev) };
-        let partnum_path = &format!("/sys/dev/block/{major}:{minor}/partition");
-        let partnum: u32 = std::fs::read_to_string(partnum_path)?.trim_end().parse()?;
-        sgdisk_command.arg(format!("-t{partnum}:{part_type}"));
-        let part_disk_parent = match part_disk.parent() {
-            Some(disk) => disk,
-            None => bail!("disk {:?} has no node in /dev", part_disk.syspath()),
-        };
-        let part_disk_parent_path = match part_disk_parent.device_path() {
-            Some(path) => path,
-            None => bail!("disk {:?} has no node in /dev", part_disk.syspath()),
-        };
-        sgdisk_command.arg(part_disk_parent_path);
-        let sgdisk_output = proxmox_sys::command::run_command(sgdisk_command, None)?;
-        info!("sgdisk output: {sgdisk_output}");
-    }
-    Ok(())
-}
-
-/// 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()),
-    };
-
-    let mut command = std::process::Command::new("sgdisk");
-    command.args(["-n1", "-t1:8300"]);
-    command.arg(disk_path);
-
-    proxmox_sys::command::run_command(command, None)?;
-
-    let mut partitions = disk.partitions()?;
-
-    match partitions.remove(&1) {
-        Some(partition) => Ok(partition),
-        None => bail!("unable to lookup device partition"),
-    }
-}
-
-#[api()]
-#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-/// A file system type supported by our tooling.
-pub enum FileSystemType {
-    /// Linux Ext4
-    Ext4,
-    /// XFS
-    Xfs,
-}
-
-impl std::fmt::Display for FileSystemType {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let text = match self {
-            FileSystemType::Ext4 => "ext4",
-            FileSystemType::Xfs => "xfs",
-        };
-        write!(f, "{text}")
-    }
-}
-
-impl std::str::FromStr for FileSystemType {
-    type Err = serde_json::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        use serde::de::IntoDeserializer;
-        Self::deserialize(s.into_deserializer())
-    }
-}
-
-/// 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()),
-    };
-
-    let fs_type = fs_type.to_string();
-
-    let mut command = std::process::Command::new("mkfs");
-    command.args(["-t", &fs_type]);
-    command.arg(disk_path);
-
-    proxmox_sys::command::run_command(command, None)?;
-
-    Ok(())
-}
-/// 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![],
-        };
-
-    dir.flatten()
-        .map(|item| item.file_name().to_str().unwrap().to_string())
-        .collect()
-}
-
-/// Block device partition name completion helper
-pub fn complete_partition_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
-    let dir = match proxmox_sys::fs::scan_subdir(
-        libc::AT_FDCWD,
-        "/sys/class/block",
-        &BLOCKDEVICE_DISK_AND_PARTITION_NAME_REGEX,
-    ) {
-        Ok(dir) => dir,
-        Err(_) => return vec![],
-    };
-
-    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()),
-    };
-
-    let mut command = std::process::Command::new("blkid");
-    command.args(["-o", "export"]);
-    command.arg(disk_path);
-
-    let output = proxmox_sys::command::run_command(command, None)?;
-
-    for line in output.lines() {
-        if let Some(uuid) = line.strip_prefix("UUID=") {
-            return Ok(uuid.to_string());
-        }
-    }
-
-    bail!("get_fs_uuid failed - missing UUID");
-}
-
-/// Mount a disk by its UUID and the mount point.
-pub fn mount_by_uuid(uuid: &str, mount_point: &Path) -> Result<(), Error> {
-    let mut command = std::process::Command::new("mount");
-    command.arg(format!("UUID={uuid}"));
-    command.arg(mount_point);
-
-    proxmox_sys::command::run_command(command, None)?;
-    Ok(())
-}
-
-/// Create bind mount.
-pub fn bind_mount(path: &Path, target: &Path) -> Result<(), Error> {
-    let mut command = std::process::Command::new("mount");
-    command.arg("--bind");
-    command.arg(path);
-    command.arg(target);
-
-    proxmox_sys::command::run_command(command, None)?;
-    Ok(())
-}
-
-/// Unmount a disk by its mount point.
-pub fn unmount_by_mountpoint(path: &Path) -> Result<(), Error> {
-    let mut command = std::process::Command::new("umount");
-    command.arg(path);
-
-    proxmox_sys::command::run_command(command, None)?;
-    Ok(())
-}
diff --git a/src/tools/disks/smart.rs b/src/tools/disks/smart.rs
deleted file mode 100644
index 1d41cee24..000000000
--- a/src/tools/disks/smart.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-use std::sync::LazyLock;
-use std::{
-    collections::{HashMap, HashSet},
-    path::Path,
-};
-
-use ::serde::{Deserialize, Serialize};
-use anyhow::Error;
-
-use proxmox_schema::api;
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
-/// SMART status
-pub enum SmartStatus {
-    /// Smart tests passed - everything is OK
-    Passed,
-    /// Smart tests failed - disk has problems
-    Failed,
-    /// Unknown status
-    Unknown,
-}
-
-#[api()]
-#[derive(Debug, Serialize, Deserialize)]
-/// SMART Attribute
-pub struct SmartAttribute {
-    /// Attribute name
-    name: String,
-    // FIXME: remove value with next major release (PBS 3.0)
-    /// duplicate of raw - kept for API stability
-    value: String,
-    /// Attribute raw value
-    raw: String,
-    // the rest of the values is available for ATA type
-    /// ATA Attribute ID
-    #[serde(skip_serializing_if = "Option::is_none")]
-    id: Option<u64>,
-    /// ATA Flags
-    #[serde(skip_serializing_if = "Option::is_none")]
-    flags: Option<String>,
-    /// ATA normalized value (0..100)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    normalized: Option<f64>,
-    /// ATA worst
-    #[serde(skip_serializing_if = "Option::is_none")]
-    worst: Option<f64>,
-    /// ATA threshold
-    #[serde(skip_serializing_if = "Option::is_none")]
-    threshold: Option<f64>,
-}
-
-#[api(
-    properties: {
-        status: {
-            type: SmartStatus,
-        },
-        wearout: {
-            description: "Wearout level.",
-            type: f64,
-            optional: true,
-        },
-        attributes: {
-            description: "SMART attributes.",
-            type: Array,
-            items: {
-                type: SmartAttribute,
-            },
-        },
-    },
-)]
-#[derive(Debug, Serialize, Deserialize)]
-/// Data from smartctl
-pub struct SmartData {
-    pub status: SmartStatus,
-    pub wearout: Option<f64>,
-    pub attributes: Vec<SmartAttribute>,
-}
-
-/// Read smartctl data for a disk (/dev/XXX).
-pub fn get_smart_data(disk_path: &Path, 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"]);
-    }
-
-    command.arg(disk_path);
-
-    let output = proxmox_sys::command::run_command(
-        command,
-        Some(
-            |exitcode| (exitcode & 0b0011) == 0, // only bits 0-1 are fatal errors
-        ),
-    )?;
-
-    let output: serde_json::Value = output.parse()?;
-
-    let mut wearout = None;
-
-    let mut attributes = Vec::new();
-    let mut wearout_candidates = HashMap::new();
-
-    // ATA devices
-    if let Some(list) = output["ata_smart_attributes"]["table"].as_array() {
-        for item in list {
-            let id = match item["id"].as_u64() {
-                Some(id) => id,
-                None => continue, // skip attributes without id
-            };
-
-            let name = match item["name"].as_str() {
-                Some(name) => name.to_string(),
-                None => continue, // skip attributes without name
-            };
-
-            let raw_value = match item["raw"]["string"].as_str() {
-                Some(value) => value.to_string(),
-                None => continue, // skip attributes without raw value
-            };
-
-            let flags = match item["flags"]["string"].as_str() {
-                Some(flags) => flags.to_string(),
-                None => continue, // skip attributes without flags
-            };
-
-            let normalized = match item["value"].as_f64() {
-                Some(v) => v,
-                None => continue, // skip attributes without normalize value
-            };
-
-            let worst = match item["worst"].as_f64() {
-                Some(v) => v,
-                None => continue, // skip attributes without worst entry
-            };
-
-            let threshold = match item["thresh"].as_f64() {
-                Some(v) => v,
-                None => continue, // skip attributes without threshold entry
-            };
-
-            if WEAROUT_FIELD_NAMES.contains(&name as &str) {
-                wearout_candidates.insert(name.clone(), normalized);
-            }
-
-            attributes.push(SmartAttribute {
-                name,
-                value: raw_value.clone(),
-                raw: raw_value,
-                id: Some(id),
-                flags: Some(flags),
-                normalized: Some(normalized),
-                worst: Some(worst),
-                threshold: Some(threshold),
-            });
-        }
-    }
-
-    if !wearout_candidates.is_empty() {
-        for field in WEAROUT_FIELD_ORDER {
-            if let Some(value) = wearout_candidates.get(field as &str) {
-                wearout = Some(*value);
-                break;
-            }
-        }
-    }
-
-    // NVME devices
-    if let Some(list) = output["nvme_smart_health_information_log"].as_object() {
-        for (name, value) in list {
-            if name == "percentage_used" {
-                // extract wearout from nvme text, allow for decimal values
-                if let Some(v) = value.as_f64() {
-                    if v <= 100.0 {
-                        wearout = Some(100.0 - v);
-                    }
-                }
-            }
-            if let Some(value) = value.as_f64() {
-                attributes.push(SmartAttribute {
-                    name: name.to_string(),
-                    value: value.to_string(),
-                    raw: value.to_string(),
-                    id: None,
-                    flags: None,
-                    normalized: None,
-                    worst: None,
-                    threshold: None,
-                });
-            }
-        }
-    }
-
-    let status = match output["smart_status"]["passed"].as_bool() {
-        None => SmartStatus::Unknown,
-        Some(true) => SmartStatus::Passed,
-        Some(false) => SmartStatus::Failed,
-    };
-
-    Ok(SmartData {
-        status,
-        wearout,
-        attributes,
-    })
-}
-
-static WEAROUT_FIELD_ORDER: &[&str] = &[
-    "Media_Wearout_Indicator",
-    "SSD_Life_Left",
-    "Wear_Leveling_Count",
-    "Perc_Write/Erase_Ct_BC",
-    "Perc_Rated_Life_Remain",
-    "Remaining_Lifetime_Perc",
-    "Percent_Lifetime_Remain",
-    "Lifetime_Left",
-    "PCT_Life_Remaining",
-    "Lifetime_Remaining",
-    "Percent_Life_Remaining",
-    "Percent_Lifetime_Used",
-    "Perc_Rated_Life_Used",
-];
-
-static WEAROUT_FIELD_NAMES: LazyLock<HashSet<&'static str>> =
-    LazyLock::new(|| WEAROUT_FIELD_ORDER.iter().cloned().collect());
diff --git a/src/tools/disks/zfs.rs b/src/tools/disks/zfs.rs
deleted file mode 100644
index 0babb8870..000000000
--- a/src/tools/disks/zfs.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use std::collections::HashSet;
-use std::os::unix::fs::MetadataExt;
-use std::path::PathBuf;
-use std::sync::{LazyLock, Mutex};
-
-use anyhow::{bail, Error};
-
-use proxmox_schema::const_regex;
-
-use super::*;
-
-static ZFS_UUIDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
-    let mut set = HashSet::new();
-    set.insert("6a898cc3-1dd2-11b2-99a6-080020736631"); // apple
-    set.insert("516e7cba-6ecf-11d6-8ff8-00022d09712b"); // bsd
-    set
-});
-
-fn get_pool_from_dataset(dataset: &str) -> &str {
-    if let Some(idx) = dataset.find('/') {
-        dataset[0..idx].as_ref()
-    } else {
-        dataset
-    }
-}
-
-/// 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);
-        }
-    };
-
-    let lines: Vec<&str> = text.lines().collect();
-
-    if lines.len() < 3 {
-        bail!("unable to parse {:?} - got less than 3 lines", path);
-    }
-
-    // https://github.com/openzfs/zfs/blob/master/lib/libspl/include/sys/kstat.h#L578
-    // nread    nwritten reads    writes   wtime    wlentime wupdate  rtime    rlentime rupdate  wcnt     rcnt
-    // 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| s.parse().unwrap_or_default())
-        .collect();
-
-    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_ios: stat[2],
-        write_ios: stat[3],
-        io_ticks: ticks,
-    };
-
-    Ok(Some(stat))
-}
-
-/// 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> {
-    let list = zpool_list(pool.as_ref(), true)?;
-
-    let mut device_set = HashSet::new();
-    for entry in list {
-        for device in entry.devices {
-            let meta = std::fs::metadata(device)?;
-            device_set.insert(meta.rdev());
-        }
-    }
-    if pool.is_none() {
-        for info in lsblk_info.iter() {
-            if let Some(partition_type) = &info.partition_type {
-                if ZFS_UUIDS.contains(partition_type.as_str()) {
-                    let meta = std::fs::metadata(&info.path)?;
-                    device_set.insert(meta.rdev());
-                }
-            }
-        }
-    }
-
-    Ok(device_set)
-}
-
-const ZFS_KSTAT_BASE_PATH: &str = "/proc/spl/kstat/zfs";
-const_regex! {
-    OBJSET_REGEX = r"^objset-0x[a-fA-F0-9]+$";
-}
-
-static ZFS_DATASET_OBJSET_MAP: LazyLock<Mutex<HashMap<String, (String, String)>>> =
-    LazyLock::new(|| Mutex::new(HashMap::new()));
-
-// parses /proc/spl/kstat/zfs/POOL/objset-ID files
-// they have the following format:
-//
-// 0 0 0x00 0 0000 00000000000 000000000000000000
-// name                            type data
-// dataset_name                    7    pool/dataset
-// writes                          4    0
-// nwritten                        4    0
-// reads                           4    0
-// nread                           4    0
-// nunlinks                        4    0
-// nunlinked                       4    0
-//
-// we are only interested in the dataset_name, writes, nwrites, reads and nread
-fn parse_objset_stat(pool: &str, objset_id: &str) -> Result<(String, BlockDevStat), Error> {
-    let path = PathBuf::from(format!("{ZFS_KSTAT_BASE_PATH}/{pool}/{objset_id}"));
-
-    let text = match proxmox_sys::fs::file_read_optional_string(path)? {
-        Some(text) => text,
-        None => bail!("could not parse '{}' stat file", objset_id),
-    };
-
-    let mut dataset_name = String::new();
-    let mut stat = BlockDevStat {
-        read_sectors: 0,
-        write_sectors: 0,
-        read_ios: 0,
-        write_ios: 0,
-        io_ticks: 0,
-    };
-
-    for (i, line) in text.lines().enumerate() {
-        if i < 2 {
-            continue;
-        }
-
-        let mut parts = line.split_ascii_whitespace();
-        let name = parts.next();
-        parts.next(); // discard type
-        let value = parts.next().ok_or_else(|| format_err!("no value found"))?;
-        match name {
-            Some("dataset_name") => dataset_name = value.to_string(),
-            Some("writes") => stat.write_ios = value.parse().unwrap_or_default(),
-            Some("nwritten") => stat.write_sectors = value.parse::<u64>().unwrap_or_default() / 512,
-            Some("reads") => stat.read_ios = value.parse().unwrap_or_default(),
-            Some("nread") => stat.read_sectors = value.parse::<u64>().unwrap_or_default() / 512,
-            _ => {}
-        }
-    }
-
-    Ok((dataset_name, stat))
-}
-
-fn get_mapping(dataset: &str) -> Option<(String, String)> {
-    ZFS_DATASET_OBJSET_MAP
-        .lock()
-        .unwrap()
-        .get(dataset)
-        .map(|c| c.to_owned())
-}
-
-/// Updates the dataset <-> objset_map
-pub(crate) fn update_zfs_objset_map(pool: &str) -> Result<(), Error> {
-    let mut map = ZFS_DATASET_OBJSET_MAP.lock().unwrap();
-    map.clear();
-    let path = PathBuf::from(format!("{ZFS_KSTAT_BASE_PATH}/{pool}"));
-
-    proxmox_sys::fs::scandir(
-        libc::AT_FDCWD,
-        &path,
-        &OBJSET_REGEX,
-        |_l2_fd, filename, _type| {
-            let (name, _) = parse_objset_stat(pool, filename)?;
-            map.insert(name, (pool.to_string(), filename.to_string()));
-            Ok(())
-        },
-    )?;
-
-    Ok(())
-}
-
-/// Gets io stats for the dataset from /proc/spl/kstat/zfs/POOL/objset-ID
-pub fn zfs_dataset_stats(dataset: &str) -> Result<BlockDevStat, Error> {
-    let mut mapping = get_mapping(dataset);
-    if mapping.is_none() {
-        let pool = get_pool_from_dataset(dataset);
-        update_zfs_objset_map(pool)?;
-        mapping = get_mapping(dataset);
-    }
-    let (pool, objset_id) =
-        mapping.ok_or_else(|| format_err!("could not find objset id for dataset"))?;
-
-    match parse_objset_stat(&pool, &objset_id) {
-        Ok((_, stat)) => Ok(stat),
-        Err(err) => {
-            // on error remove dataset from map, it probably vanished or the
-            // mapping was incorrect
-            ZFS_DATASET_OBJSET_MAP.lock().unwrap().remove(dataset);
-            Err(err)
-        }
-    }
-}
diff --git a/src/tools/mod.rs b/src/tools/mod.rs
index 7f5acc0e3..4a30c1f71 100644
--- a/src/tools/mod.rs
+++ b/src/tools/mod.rs
@@ -14,7 +14,6 @@ use pbs_datastore::backup_info::{BackupDir, BackupInfo};
 use pbs_datastore::manifest::BackupManifest;
 
 pub mod config;
-pub mod disks;
 pub mod fs;
 pub mod statistics;
 pub mod systemd;
-- 
2.47.3





  parent reply	other threads:[~2026-03-12 13:53 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-12 13:52 [PATCH datacenter-manager/proxmox{,-backup,-yew-comp} 00/26] metric collection for the PDM host Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 01/26] sys: procfs: don't read from sysfs during unit tests Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 02/26] parallel-handler: import code from Proxmox Backup Server Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 03/26] parallel-handler: introduce custom error type Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 04/26] parallel-handler: add documentation Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 05/26] parallel-handler: add simple unit-test suite Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 06/26] disks: import from Proxmox Backup Server Lukas Wagner
2026-03-16 13:13   ` Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox 07/26] disks: fix typo in `initialize_gpt_disk` Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 08/26] disks: add parts of gather_disk_stats from PBS Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 09/26] disks: gate api macro behind 'api-types' feature Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 10/26] disks: clippy: collapse if-let chains where possible Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 11/26] procfs: add helpers for querying pressure stall information Lukas Wagner
2026-03-16 13:25   ` Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox 12/26] time: use u64 parse helper from nom Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-backup 13/26] tools: move ParallelHandler to new proxmox-parallel-handler crate Lukas Wagner
2026-03-12 13:52 ` Lukas Wagner [this message]
2026-03-16 13:27   ` [PATCH proxmox-backup 14/26] tools: replace disks module with proxmox-disks Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox-backup 15/26] metric collection: use blockdev_stat_for_path from proxmox_disks Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 16/26] node status panel: add `children` property Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 17/26] RRDGrid: fix size observer by attaching node reference to rendered container Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 18/26] RRDGrid: add padding and increase gap between elements Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 19/26] metric collection: clarify naming for remote metric collection Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 20/26] metric collection: fix minor typo in error message Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 21/26] metric collection: collect PDM host metrics in a new collection task Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 22/26] api: fix /nodes/localhost/rrddata endpoint Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 23/26] pdm: node rrd data: rename 'total-time' to 'metric-collection-total-time' Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 24/26] pdm-api-types: add PDM host metric fields Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 25/26] ui: node status: add RRD graphs for PDM host metrics Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 26/26] ui: lxc/qemu/node: use RRD value render helpers Lukas Wagner
2026-03-16 13:42 ` [PATCH datacenter-manager/proxmox{,-backup,-yew-comp} 00/26] metric collection for the PDM host Arthur Bied-Charreton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260312135229.420729-15-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal