From 68de43f8f461a596ece874c2e45b612e36833faa Mon Sep 17 00:00:00 2001 From: Leask Wong [email protected] Date: Tue, 2 Jun 2026 00:17:40 -0400 Subject: [PATCH proxmox] proxmox-disks: resolve block devices by mount source
blockdev_stat_for_path currently relies on st_dev matching the corresponding mountinfo entry. On btrfs root filesystems backed by mdraid, stat(“/”) can return a different anonymous device number than the mountinfo entry, while the mount source still points at the real block device. Fall back to the longest matching mount point when the st_dev lookup does not match, and prefer the mount source block device for the returned Device. This lets callers read sysfs statistics for the actual backing block device instead of failing to resolve the path. This fixes repeated PDM collection log messages on such systems: failed to collect blockdev statistics for ‘/’: could not determine mounted device for path / Add coverage for the btrfs dev-number mismatch and longest-mount fallback cases. Signed-off-by: Leask Wong [email protected] proxmox-disks/src/disks.rs | 149 ++++++++++++++++++++++++++++++++++— 1 file changed, 140 insertions(+), 9 deletions(-) diff –git a/proxmox-disks/src/disks.rs b/proxmox-disks/src/disks.rs index a194768..11bdb12 100644 — a/proxmox-disks/src/disks.rs +++ b/proxmox-disks/src/disks.rs @@ -9,7 +9,10 @@ use anyhow::{Context as _, Error, format_err}; use libc::dev_t; use once_cell::sync::OnceCell; -use proxmox_sys::linux::procfs::{MountInfo, mountinfo::Device}; +use proxmox_sys::linux::procfs::{ + MountInfo, + mountinfo::{Device, Entry}, +}; use crate::{BlockDevStat, Disk}; @@ -132,20 +135,72 @@ impl Disks { ) -> Result<Option<(String, Device, Option)>, Error> { let meta = std::fs::metadata(path)?; let device = Device::from_dev_t(meta.dev()); + let path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()); - let entry = match Self::find_mount_entry_for_path(self.mount_info()?, &path, device) { - Some(entry) => entry, - None => return Ok(None), - }; + - Ok(Some(( - entry.fs_type.clone(), - Self::device_from_mount_entry(entry), - entry.mount_source.clone(), - ))) - } + - fn find_mount_entry_for_path<’a>( - mount_info: &’a MountInfo, - path: &std::path::Path, - device: Device, - ) -> Option<&’a Entry> { let root_path = std::path::Path::new(“/”); - let mut device_match = None; - let mut mount_point_match = None; + - for (_id, entry) in mount_info { - if entry.root != root_path { - continue; - } - 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(), - ))); - if !path.starts_with(&entry.mount_point) { - continue; } + - if entry.device == device { - Self::select_longer_mount_point(&mut device_match, entry); - } + - Self::select_longer_mount_point(&mut mount_point_match, entry); } - Ok(None) - device_match.or(mount_point_match) - } + - fn select_longer_mount_point<’a>(slot: &mut Option<&’a Entry>, entry: &’a Entry) { - let old_len = slot - .map(|entry| entry.mount_point.as_os_str().len()) - .unwrap_or_default(); - let new_len = entry.mount_point.as_os_str().len(); + - if new_len > old_len { - *slot = Some(entry); - } - } + - fn device_from_mount_entry(entry: &Entry) -> Device { - if let Some(source) = entry.mount_source.as_deref() { - let path = Path::new(source); - if path.is_absolute() { - if let Ok(meta) = std::fs::metadata(path) { - if (meta.mode() & libc::S_IFBLK) == libc::S_IFBLK { - return Device::from_dev_t(meta.rdev()); - } - } - } - } + - entry.device } /// Check whether a specific device node is mounted. @@ -194,3 +249,79 @@ impl Disks { .ok_or_else(|| format_err!(“could not read disk stats for {}”, path.as_ref().display())) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn parse_mountinfo(input: &str) -> MountInfo { + MountInfo::parse(input.as_bytes()).expect(“failed to parse mountinfo”) + } + + fn device(input: &str) -> Device { + input.parse().expect(“failed to parse device”) + } + + #[test] + fn find_mount_entry_prefers_matching_device() { + let mount_info = parse_mountinfo( + “35 2 8:1 / / rw,relatime - ext4 /dev/sda1 rw\n\ + 36 35 8:2 / /boot rw,relatime - ext4 /dev/sda2 rw\n”, + ); + + let entry = + Disks::find_mount_entry_for_path(&mount_info, Path::new(“/boot/grub”), device(“8:2”)) + .expect(“expected mount entry”); + + assert_eq!(entry.mount_point, Path::new(“/boot”)); + assert_eq!(entry.device, device(“8:2”)); + } + + #[test] + fn find_mount_entry_falls_back_to_mount_point() { + let mount_info = + parse_mountinfo(“35 2 0:30 / / rw,relatime - btrfs /dev/md0 rw,subvol=/\n”); + + let entry = Disks::find_mount_entry_for_path(&mount_info, Path::new(“/”), device(“0:31”)) + .expect(“expected mount entry”); + + assert_eq!(entry.mount_point, Path::new(“/”)); + assert_eq!(entry.device, device(“0:30”)); + } + + #[test] + fn find_mount_entry_uses_longest_mount_point_fallback() { + let mount_info = parse_mountinfo( + “35 2 0:30 / / rw,relatime - btrfs /dev/md0 rw,subvol=/\n\ + 36 35 0:32 / /srv/data rw,relatime - btrfs /dev/md1 rw,subvol=/data\n”, + ); + + let entry = Disks::find_mount_entry_for_path( + &mount_info, + Path::new(“/srv/data/project”), + device(“0:33”), + ) + .expect(“expected mount entry”); + + assert_eq!(entry.mount_point, Path::new(“/srv/data”)); + assert_eq!(entry.device, device(“0:32”)); + } + + #[test] + fn find_mount_entry_ignores_non_root_mountinfo_entries() { + let mount_info = parse_mountinfo( + “35 2 0:30 / / rw,relatime - btrfs /dev/md0 rw,subvol=/\n\ + 36 35 0:32 /subvol /srv/data rw,relatime - btrfs /dev/md1 rw\n”, + ); + + let entry = Disks::find_mount_entry_for_path( + &mount_info, + Path::new(“/srv/data/project”), + device(“0:33”), + ) + .expect(“expected mount entry”); + + assert_eq!(entry.mount_point, Path::new(“/”)); + assert_eq!(entry.device, device(“0:30”)); + } +} – 2.54.0
