use std::collections::HashMap;
use std::fs;
pub fn is_running_in_container() -> bool {
if std::path::Path::new("/.dockerenv").exists() {
return true;
}
if let Ok(cgroup) = fs::read_to_string("/proc/self/cgroup")
&& (cgroup.contains("/docker/")
|| cgroup.contains("/containerd/")
|| cgroup.contains("/lxc/")
|| cgroup.contains("/kubepods/"))
{
return true;
}
if let Ok(cmdline) = fs::read_to_string("/proc/1/cmdline") {
let cmd = cmdline.split('\0').next().unwrap_or("");
if !cmd.contains("systemd") && !cmd.contains("init") && !cmd.is_empty() {
return true;
}
}
if std::env::var("KUBERNETES_SERVICE_HOST").is_ok() || std::env::var("DOCKER_CONTAINER").is_ok()
{
return true;
}
false
}
#[allow(dead_code)]
pub fn is_containerized_process(pid: u32) -> bool {
if let (Ok(init_ns), Ok(proc_ns)) = (
fs::read_link("/proc/1/ns/pid"),
fs::read_link(format!("/proc/{pid}/ns/pid")),
) {
return init_ns != proc_ns;
}
false
}
pub fn get_container_pid_mapping(host_pid: u32) -> Option<u32> {
if let Ok(status) = fs::read_to_string(format!("/proc/{host_pid}/status")) {
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if pids.len() > 1 {
return pids[1].parse::<u32>().ok();
}
}
}
}
None
}
#[allow(dead_code)]
pub fn get_self_pid_mapping() -> Option<(u32, u32)> {
if let Ok(status) = fs::read_to_string("/proc/self/status") {
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if std::env::var("ALL_SMI_DEBUG_PID").is_ok() {
eprintln!("Debug: Self NSpid: {line}");
}
if pids.len() == 1 {
return None;
} else if pids.len() >= 2 {
let container_pid = pids[0].parse::<u32>().ok()?;
let host_pid = pids[1].parse::<u32>().ok()?;
return Some((container_pid, host_pid));
}
}
}
}
None
}
#[allow(dead_code)]
pub fn map_host_to_container_pid(host_pid: u32) -> Option<u32> {
if !is_running_in_container() {
return Some(host_pid);
}
if std::path::Path::new(&format!("/proc/{host_pid}")).exists() {
return Some(host_pid);
}
let host_proc_paths = vec![
"/host/proc", "/hostproc", "/proc_host", ];
for host_proc in &host_proc_paths {
let status_path = format!("{host_proc}/{host_pid}/status");
if let Ok(status) = fs::read_to_string(&status_path) {
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if let Some((our_container_pid, our_host_pid)) = get_self_pid_mapping() {
if our_host_pid == host_pid {
return Some(our_container_pid);
}
}
if pids.len() == 2 && pids[0].parse::<u32>().ok() == Some(host_pid) {
if let Ok(container_pid) = pids[1].parse::<u32>() {
return Some(container_pid);
}
} else if pids.len() > 2 {
if let Some(container_pid_str) = pids.last()
&& let Ok(container_pid) = container_pid_str.parse::<u32>()
{
return Some(container_pid);
}
}
}
}
}
}
let _our_namespace_info = get_self_pid_mapping();
if let Ok(entries) = fs::read_dir("/proc") {
for entry in entries.flatten() {
if let Some(pid_str) = entry.file_name().to_str()
&& let Ok(pid) = pid_str.parse::<u32>()
{
if let Ok(status) = fs::read_to_string(format!("/proc/{pid}/status")) {
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if std::env::var("ALL_SMI_DEBUG_PID").is_ok() {
eprintln!("Debug: PID {pid} has NSpid: {line}");
}
if pids.len() >= 2 && pids[1].parse::<u32>().ok() == Some(host_pid) {
return Some(pid);
}
if !pids.is_empty() && pids[0].parse::<u32>().ok() == Some(host_pid) {
if let Ok(our_ns) = fs::read_link("/proc/self/ns/pid")
&& let Ok(proc_ns) =
fs::read_link(format!("/proc/{pid}/ns/pid"))
&& our_ns == proc_ns
{
return Some(pid);
}
}
}
}
}
}
}
}
None
}
#[allow(dead_code)]
pub fn find_host_pid_from_container_pid(
container_pid: u32,
container_init_pid: Option<u32>,
) -> Option<u32> {
if let Some(init_pid) = container_init_pid {
if let Ok(container_ns) = fs::read_link(format!("/proc/{init_pid}/ns/pid")) {
if let Ok(entries) = fs::read_dir("/proc") {
for entry in entries.flatten() {
if let Some(pid_str) = entry.file_name().to_str()
&& let Ok(pid) = pid_str.parse::<u32>()
{
if let Ok(proc_ns) = fs::read_link(format!("/proc/{pid}/ns/pid"))
&& proc_ns == container_ns
{
if get_container_pid_mapping(pid) == Some(container_pid) {
return Some(pid);
}
}
}
}
}
}
}
if let Ok(entries) = fs::read_dir("/proc") {
for entry in entries.flatten() {
if let Some(pid_str) = entry.file_name().to_str()
&& let Ok(pid) = pid_str.parse::<u32>()
&& get_container_pid_mapping(pid) == Some(container_pid)
{
return Some(pid);
}
}
}
None
}
#[allow(dead_code)]
pub fn build_pid_mapping_cache() -> HashMap<u32, u32> {
let mut cache = HashMap::new();
if !is_running_in_container() {
return cache;
}
let host_proc_paths = vec!["/host/proc", "/hostproc", "/proc_host"];
for host_proc in &host_proc_paths {
if let Ok(entries) = fs::read_dir(host_proc) {
for entry in entries.flatten() {
if let Some(pid_str) = entry.file_name().to_str()
&& let Ok(host_pid) = pid_str.parse::<u32>()
{
let status_path = format!("{host_proc}/{host_pid}/status");
if let Ok(status) = fs::read_to_string(&status_path) {
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if pids.len() >= 2 {
if let Ok(container_pid) = pids[1].parse::<u32>() {
cache.insert(host_pid, container_pid);
}
}
}
}
}
}
}
if !cache.is_empty() {
return cache;
}
}
}
if let Ok(entries) = fs::read_dir("/proc") {
for entry in entries.flatten() {
if let Some(pid_str) = entry.file_name().to_str()
&& let Ok(container_pid) = pid_str.parse::<u32>()
&& let Ok(status) = fs::read_to_string(format!("/proc/{container_pid}/status"))
{
for line in status.lines() {
if line.starts_with("NSpid:") {
let pids: Vec<&str> = line.split_whitespace().skip(1).collect();
if pids.len() >= 2 {
if let Ok(host_pid) = pids[1].parse::<u32>() {
cache.insert(host_pid, container_pid);
}
}
}
}
}
}
}
cache
}
#[allow(dead_code)]
pub fn format_process_name_with_container_info(process_name: String, pid: u32) -> String {
if is_running_in_container() {
format!("{process_name} [container]")
} else {
if is_containerized_process(pid) {
if let Some(container_pid) = get_container_pid_mapping(pid) {
format!("{process_name} [c:{container_pid}]")
} else {
process_name
}
} else {
process_name
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_container_detection() {
let in_container = is_running_in_container();
println!("Running in container: {in_container}");
}
#[test]
fn test_self_pid_mapping() {
if let Some((container_pid, host_pid)) = get_self_pid_mapping() {
println!("Self PID mapping: container={container_pid}, host={host_pid}");
assert!(container_pid > 0);
assert!(host_pid > 0);
}
}
}