use crate::unitd_cmd::UnitdCmd; use crate::unitd_docker::{pid_is_dockerized, UnitdContainer}; use crate::unitd_instance::UNITD_BINARY_NAMES; use crate::unitd_process_user::UnitdProcessUser; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; use std::collections::HashMap; use std::path::Path; use sysinfo::{Pid, Process, ProcessRefreshKind, System, UpdateKind, Users}; #[derive(Debug, Clone)] pub struct UnitdProcess { pub binary_name: String, pub process_id: u64, pub executable_path: Option>, pub environ: Vec, pub all_cmds: Vec, pub working_dir: Option>, pub child_pids: Vec, pub user: Option, pub effective_user: Option, pub container: Option, } impl Serialize for UnitdProcess { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_map(Some(6))?; state.serialize_entry("pid", &self.process_id)?; state.serialize_entry("user", &self.user)?; state.serialize_entry("effective_user", &self.effective_user)?; state.serialize_entry("executable", &self.executable_path())?; state.serialize_entry("child_pids", &self.child_pids)?; state.serialize_entry("container", &self.container)?; state.end() } } impl UnitdProcess { pub fn find_unitd_processes() -> Vec { let process_refresh_kind = ProcessRefreshKind::new() .with_cmd(UpdateKind::Always) .with_cwd(UpdateKind::Always) .with_exe(UpdateKind::Always) .with_user(UpdateKind::Always); let refresh_kind = sysinfo::RefreshKind::new().with_processes(process_refresh_kind); let sys = System::new_with_specifics(refresh_kind); let unitd_processes: HashMap<&Pid, &Process> = sys .processes() .iter() .filter(|p| { let process_name = p.1.name(); UNITD_BINARY_NAMES.contains(&process_name) }) .collect::>(); let users = Users::new_with_refreshed_list(); unitd_processes .iter() // Filter out child processes .filter(|p| { #[cfg(target_os = "linux")] if pid_is_dockerized(p.0.as_u32().into()) { false } let parent_pid = p.1.parent(); match parent_pid { Some(pid) => !unitd_processes.contains_key(&pid), None => false, } }) .map(|p| { let tuple = p.to_owned(); /* The sysinfo library only supports 32-bit pids, yet larger values are possible * if the OS is configured to support it, thus we use 64-bit integers internally * because it is just a matter of time until the library changes to larger values. */ let pid = *tuple.0; let process = *tuple.1; let process_id: u64 = pid.as_u32().into(); let executable_path: Option> = process.exe().map(|p| p.to_path_buf().into_boxed_path()); let environ: Vec = process.environ().into(); let cmd: Vec = process.cmd().into(); let working_dir: Option> = process.cwd().map(|p| p.to_path_buf().into_boxed_path()); let child_pids = unitd_processes .iter() .filter_map(|p| p.to_owned().1.parent()) .filter(|parent_pid| parent_pid == pid) .map(|p| p.as_u32() as u64) .collect::>(); let user = process .user_id() .and_then(|uid| users.get_user_by_id(uid)) .map(UnitdProcessUser::from); let effective_user = process .effective_user_id() .and_then(|uid| users.get_user_by_id(uid)) .map(UnitdProcessUser::from); UnitdProcess { binary_name: process.name().to_string(), process_id, executable_path, environ, all_cmds: cmd, working_dir, child_pids, user, effective_user, container: None, } }) .collect::>() } pub fn cmd(&self) -> Option { if self.all_cmds.is_empty() { return None; } match UnitdCmd::new(self.all_cmds[0].clone(), self.binary_name.as_ref()) { Ok(cmd) => Some(cmd), Err(error) => { eprintln!("Failed to parse process cmd: {}", error); None } } } pub fn executable_path(&self) -> Option> { if self.executable_path.is_some() { return self.executable_path.clone(); } self.cmd().and_then(|cmd| cmd.process_executable_path) } } #[cfg(test)] mod tests { use super::*; fn can_parse_runtime_cmd_absolute_path(binary_name: &str) { let cmd = format!( "unit: main v1.28.0 [/usr/sbin/{} --log /var/log/unit.log --pid /var/run/unit.pid]", binary_name ); let unitd_cmd = UnitdCmd::new(cmd, binary_name).expect("Failed to parse unitd cmd"); assert_eq!(unitd_cmd.version.unwrap(), "1.28.0"); assert_eq!( unitd_cmd.process_executable_path.unwrap().to_string_lossy(), format!("/usr/sbin/{}", binary_name) ); let flags = unitd_cmd.flags.unwrap(); assert_eq!(flags.get_flag_value("log").unwrap(), "/var/log/unit.log"); assert_eq!(flags.get_flag_value("pid").unwrap(), "/var/run/unit.pid"); } fn can_parse_runtime_cmd_relative_path(binary_name: &str) { let cmd = format!( "unit: main v1.29.0 [./sbin/{} --no-daemon --tmp /tmp --something]", binary_name ); let unitd_cmd = UnitdCmd::new(cmd, binary_name).expect("Failed to parse unitd cmd"); assert_eq!(unitd_cmd.version.unwrap(), "1.29.0"); assert_eq!( unitd_cmd.process_executable_path.unwrap().to_string_lossy(), format!("./sbin/{}", binary_name) ); let flags = unitd_cmd.flags.unwrap(); assert_eq!(flags.get_flag_value("tmp").unwrap(), "/tmp"); assert!(flags.has_flag("something")); } #[test] fn can_parse_runtime_cmd_unitd_absolute_path() { can_parse_runtime_cmd_absolute_path("unitd"); } #[test] fn can_parse_runtime_cmd_unitd_debug_absolute_path() { can_parse_runtime_cmd_absolute_path("unitd-debug"); } #[test] fn can_parse_runtime_cmd_unitd_relative_path() { can_parse_runtime_cmd_relative_path("unitd"); } #[test] fn can_parse_runtime_cmd_unitd_debug_relative_path() { can_parse_runtime_cmd_relative_path("unitd-debug"); } }