From b498e140a1ebbf0443cbda7f2434a3c3e794d603 Mon Sep 17 00:00:00 2001 From: David Esparza Date: Fri, 8 Mar 2024 09:12:12 -0600 Subject: [PATCH] runtime-rs: ch: Implement full thread/tid/pid handling Add in the full details once cloud-hypervisor/cloud-hypervisor#6103 has been implemented, and the feature is available in a Cloud Hypervisor release. Fixes: #8799 Signed-off-by: David Esparza --- src/runtime-rs/Cargo.lock | 22 +++- src/runtime-rs/crates/hypervisor/Cargo.toml | 1 + .../hypervisor/src/ch/inner_hypervisor.rs | 118 +++++++++++++++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/runtime-rs/Cargo.lock b/src/runtime-rs/Cargo.lock index 971b92e358..9c944cbe0b 100644 --- a/src/runtime-rs/Cargo.lock +++ b/src/runtime-rs/Cargo.lock @@ -1654,6 +1654,7 @@ dependencies = [ "shim-interface", "slog", "slog-scope", + "tempdir", "test-utils", "tests_utils", "thiserror", @@ -2749,7 +2750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" dependencies = [ "libc", - "nix 0.26.2", + "nix 0.27.1", ] [[package]] @@ -3163,6 +3164,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.11.20" @@ -3919,6 +3929,16 @@ dependencies = [ "xattr", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.7.0" diff --git a/src/runtime-rs/crates/hypervisor/Cargo.toml b/src/runtime-rs/crates/hypervisor/Cargo.toml index 98d013e634..dc2f57e62f 100644 --- a/src/runtime-rs/crates/hypervisor/Cargo.toml +++ b/src/runtime-rs/crates/hypervisor/Cargo.toml @@ -41,6 +41,7 @@ tests_utils = { path = "../../tests/utils" } futures = "0.3.25" safe-path = "0.1.0" crossbeam-channel = "0.5.6" +tempdir = "0.3.7" [target.'cfg(not(target_arch = "s390x"))'.dependencies] dragonball = { path = "../../../dragonball", features = ["atomic-guest-memory", "virtio-vsock", "hotplug", "virtio-blk", "virtio-net", "virtio-fs", "vhost-net", "dbs-upcall", "virtio-mem", "virtio-balloon", "vhost-user-net", "host-device"] } diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs index cc7e33c952..7764baddde 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -30,6 +30,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use std::convert::TryFrom; +use std::fs; use std::fs::create_dir_all; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; @@ -677,13 +678,10 @@ impl CloudHypervisorInner { } pub(crate) async fn get_thread_ids(&self) -> Result { - let mut vcpus = HashMap::new(); - - let vcpu = 0; let thread_id = self.get_vmm_master_tid().await?; + let proc_path = format!("/proc/{thread_id}"); - vcpus.insert(vcpu, thread_id); - + let vcpus = get_ch_vcpu_tids(&proc_path)?; let vcpu_thread_ids = VcpuThreadIds { vcpus }; Ok(vcpu_thread_ids) @@ -908,6 +906,59 @@ fn get_guest_protection() -> Result { Ok(guest_protection) } +// Return a TID/VCPU map from a specified /proc/{pid} path. +fn get_ch_vcpu_tids(proc_path: &str) -> Result> { + const VCPU_STR: &str = "vcpu"; + + let src = std::fs::canonicalize(proc_path) + .map_err(|e| anyhow!("Invalid proc path: {proc_path}: {e}"))?; + + let tid_path = src.join("task"); + + let mut vcpus = HashMap::new(); + + for entry in fs::read_dir(&tid_path)? { + let entry = entry?; + + let tid_str = match entry.file_name().into_string() { + Ok(id) => id, + Err(_) => continue, + }; + + let tid = tid_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid tid."))?; + + let comm_path = tid_path.join(tid_str.clone()).join("comm"); + + if !comm_path.exists() { + return Err(anyhow!("comm path was not found.")); + } + + let p_name = fs::read_to_string(comm_path)?; + + // The CH names it's threads with a vcpu${number} to identify them, where + // the thread name is located at /proc/${ch_pid}/task/${thread_id}/comm. + if !p_name.starts_with(VCPU_STR) { + continue; + } + + let vcpu_id = p_name + .trim_start_matches(VCPU_STR) + .trim() + .parse::() + .map_err(|e| anyhow!(e).context("Invalid vcpu id."))?; + + vcpus.insert(tid, vcpu_id); + } + + if vcpus.is_empty() { + return Err(anyhow!("The contents of proc path are not available.")); + } + + Ok(vcpus) +} + #[cfg(test)] mod tests { use super::*; @@ -922,6 +973,9 @@ mod tests { use std::path::PathBuf; use test_utils::{assert_result, skip_if_not_root}; + use std::fs::File; + use tempdir::TempDir; + fn set_fake_guest_protection(protection: Option) { let existing_ref = FAKE_GUEST_PROTECTION.clone(); @@ -1333,4 +1387,58 @@ mod tests { assert_eq!(d.level, level, "{}", msg); } } + + #[actix_rt::test] + async fn test_get_thread_ids() { + let path_dir = "/tmp/proc"; + let file_name = "1"; + + let tmp_dir = TempDir::new(path_dir).unwrap(); + let file_path = tmp_dir.path().join(file_name); + let _tmp_file = File::create(file_path.as_os_str()).unwrap(); + let file_path_name = file_path.as_path().to_str().map(|s| s.to_string()); + let file_path_name_str = file_path_name.as_ref().unwrap().to_string(); + + #[derive(Debug)] + struct TestData<'a> { + proc_path: &'a str, + result: Result>, + } + + let tests = &[ + TestData { + // Test on a non-existent directory. + proc_path: path_dir, + result: Err(anyhow!( + "Invalid proc path: {path_dir}: No such file or directory (os error 2)" + )), + }, + TestData { + // Test on an existing path, however it is not valid because it does not point to a pid. + proc_path: &file_path_name_str, + result: Err(anyhow!("Not a directory (os error 20)")), + }, + TestData { + // Test on an existing proc/${pid} but that does not correspond to a CH pid. + proc_path: "/proc/1", + result: Err(anyhow!("The contents of proc path are not available.")), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test: [{}]: {:?}", i, d); + + if std::env::var("DEBUG").is_ok() { + println!("DEBUG: {msg}"); + } + + let result = get_ch_vcpu_tids(d.proc_path); + let msg = format!("{}, result: {:?}", msg, result); + + let expected_error = format!("{}", d.result.as_ref().unwrap_err()); + let actual_error = format!("{}", result.unwrap_err()); + + assert!(actual_error == expected_error, "{}", msg); + } + } }