From 8aba07be92768aea58aca079307432371a9f7feb Mon Sep 17 00:00:00 2001 From: Leonardo Forchini Date: Fri, 25 Sep 2026 08:44:34 +0000 Subject: [PATCH] Read /proc through the mock-root path Host CPU and per-task samples were opening the real /proc, so IO_THREAD_CONTROLLER_ROOT_PATH could not redirect them at a test filesystem. Signed-off-by: Leonardo Forchini --- src/backends/qemu/client.rs | 3 ++- src/backends/qemu/helpers.rs | 7 +++++-- src/controller.rs | 7 +++++-- src/instance.rs | 2 +- src/util.rs | 10 ++++++++++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/backends/qemu/client.rs b/src/backends/qemu/client.rs index e618623..eae7a0b 100644 --- a/src/backends/qemu/client.rs +++ b/src/backends/qemu/client.rs @@ -36,6 +36,7 @@ use crate::{ }, }, instance::{InstanceClient, ThreadPoolSnapshot}, + util::Path, }; /// Wire client for an QEMU-managed VM. @@ -261,7 +262,7 @@ impl InstanceClient for QemuInstanceClient { "qemu topology reported no iothread TIDs".into(), )); } - let pid = Process::new(first_tid)? + let pid = Process::new_with_root(Path::proc_pid(first_tid))? .status() .map_err(|e| BackendClientError::InvalidState(format!("pid resolution: {e}")))? .tgid; diff --git a/src/backends/qemu/helpers.rs b/src/backends/qemu/helpers.rs index 34b5479..59dcccb 100644 --- a/src/backends/qemu/helpers.rs +++ b/src/backends/qemu/helpers.rs @@ -29,7 +29,10 @@ use regex::Regex; use rustix::param; use thiserror::Error; -use crate::backends::{BackendClientError, VqMapping}; +use crate::{ + backends::{BackendClientError, VqMapping}, + util::Path, +}; /// Errors produced while querying or reconfiguring a QEMU vm. #[derive(Debug, Error)] @@ -200,7 +203,7 @@ impl CpuSample { wall: Instant::now(), }; - let p = Process::new(pid)?; + let p = Process::new_with_root(Path::proc_pid(pid))?; for &tid in iothread_tids.values() { let stat = p.task_from_tid(tid)?.stat()?; out.jiffies.insert(tid, stat.utime + stat.stime); diff --git a/src/controller.rs b/src/controller.rs index 09b64de..c534a60 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -12,7 +12,7 @@ use std::{ }; use futures_util::future::join_all; -use procfs::{CurrentSI, ProcError}; +use procfs::{FromReadSI, ProcError}; use serde::{Deserialize, Serialize}; use statistical::median; use thiserror::Error; @@ -25,6 +25,7 @@ use crate::{ instance::{Instance, InstanceStatus}, rolling::format_1_5_15, state::{StateError, VmOwnership, VmStateStore}, + util::Path, }; /// Wire-facing container for the D-Bus `GetSnapshot` reply. #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -804,7 +805,9 @@ impl Controller { /// Read aggregate host CPU counters. fn read_host_cpu_sample() -> Result { - let total = procfs::KernelStats::current()?.total; + let total = + procfs::KernelStats::from_file(Path::new("/proc/stat"), procfs::current_system_info())? + .total; let idle_ticks = total.idle.saturating_add(total.iowait.unwrap_or(0)); let total_ticks = total .user diff --git a/src/instance.rs b/src/instance.rs index 82fffe2..b31cf49 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -523,7 +523,7 @@ fn read_cpu_sample( pid: i32, filter: &crate::instance::ThreadNameFilter, ) -> Result { - let process = Process::new(pid)?; + let process = Process::new_with_root(Path::proc_pid(pid))?; let tasks = process.tasks()?; let mut cpu_ticks = 0u64; let mut thread_count = 0u32; diff --git a/src/util.rs b/src/util.rs index 68bac0e..edfc89c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -90,6 +90,16 @@ impl Path { pub fn glob(&self) -> glob::Paths { glob::glob(&self.path.to_string_lossy()).expect("valid glob pattern") } + + /// `/proc/` after applying [`IO_THREAD_CONTROLLER_ROOT_PATH`]. + /// + /// `procfs::process::Process::new` always opens the real `/proc`. Callers + /// that should honor the mock root pass this path to + /// `Process::new_with_root`. + pub fn proc_pid(pid: i32) -> std::path::PathBuf { + let path = Self::new(&format!("/proc/{pid}")); + std::path::PathBuf::from(std::path::Path::as_os_str(&path)) + } } impl From<&str> for Path {