1use crate::common::{resolve_pid, MaybeHasPid};
2use crate::{Pid, ProcCtlError, ProcCtlResult};
3use std::path::PathBuf;
4use std::process::Child;
5use std::sync::Mutex;
6use std::sync::OnceLock;
7use sysinfo::{Process, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
8
9#[derive(Debug, Clone)]
11pub struct ProcInfo {
12 pub name: String,
14 pub cmd: Vec<String>,
16 pub exe: Option<PathBuf>,
18 pub pid: Pid,
20 pub parent: Option<Pid>,
22 pub env: Vec<String>,
24 pub cwd: Option<PathBuf>,
26}
27
28#[derive(Debug)]
30pub struct ProcQuery {
31 process_id: Option<Pid>,
32 name: Option<String>,
33 min_num_children: Option<usize>,
34}
35
36impl ProcQuery {
37 pub fn new() -> Self {
39 ProcQuery {
40 process_id: None,
41 name: None,
42 min_num_children: None,
43 }
44 }
45
46 pub fn process_id(mut self, pid: Pid) -> Self {
50 self.process_id = Some(pid);
51 self
52 }
53
54 pub fn process_name(mut self, name: impl AsRef<str>) -> Self {
58 let name = name.as_ref().to_string();
59 #[cfg(target_os = "windows")]
60 let name = {
61 let mut name = name;
62 if !name.ends_with(".exe") {
63 name.push_str(".exe");
64 }
65 name
66 };
67 self.name = Some(name);
68 self
69 }
70
71 pub fn process_id_from_child(self, child: &Child) -> Self {
75 self.process_id(child.id())
76 }
77
78 pub fn expect_min_num_children(mut self, num_children: usize) -> Self {
80 self.min_num_children = Some(num_children);
81 self
82 }
83
84 pub fn list_processes(&self) -> ProcCtlResult<Vec<ProcInfo>> {
86 let mut sys_handle = sys_handle().lock().unwrap();
87 sys_handle.refresh_processes_specifics(
88 ProcessesToUpdate::All,
89 true,
90 ProcessRefreshKind::everything(),
91 );
92 let processes = sys_handle.processes();
93
94 let infos: Vec<ProcInfo> = processes
95 .values()
96 .filter(|p| {
97 if p.thread_kind().is_some() {
99 return false;
100 }
101
102 if let Some(pid) = self.process_id {
103 if p.pid().as_u32() != pid {
104 return false;
105 }
106 }
107
108 if let Some(name) = &self.name {
109 if p.name().to_string_lossy().as_ref() != name {
110 return false;
111 }
112 }
113
114 true
115 })
116 .map(|p| p.into())
117 .collect();
118
119 Ok(infos)
120 }
121
122 pub fn children(&self) -> ProcCtlResult<Vec<ProcInfo>> {
124 let pid = resolve_pid(self)?;
125
126 let mut sys_handle = sys_handle().lock().unwrap();
127 sys_handle.refresh_processes(ProcessesToUpdate::All, true);
128 let processes = sys_handle.processes();
129 let children: Vec<ProcInfo> = processes
130 .values()
131 .filter(|p| p.parent() == Some(sysinfo::Pid::from(pid as usize)))
132 .map(|p| p.into())
133 .collect();
134
135 if let Some(num) = &self.min_num_children {
136 if children.len() < *num {
137 return Err(ProcCtlError::TooFewChildren(children.len(), *num));
138 }
139 }
140
141 Ok(children)
142 }
143
144 #[cfg(feature = "resilience")]
146 pub fn children_with_retry_sync(
147 &self,
148 delay: std::time::Duration,
149 count: usize,
150 ) -> ProcCtlResult<Vec<ProcInfo>> {
151 retry::retry(retry::delay::Fixed::from(delay).take(count), || {
152 self.children()
153 })
154 .map_err(|e| e.error)
155 }
156
157 #[cfg(feature = "async")]
159 #[async_recursion::async_recursion]
160 pub async fn children_with_retry(
161 &self,
162 delay: std::time::Duration,
163 count: usize,
164 ) -> ProcCtlResult<Vec<ProcInfo>> {
165 match self.children() {
166 Ok(infos) => Ok(infos),
167 Err(e) => {
168 if count == 0 {
169 Err(e)
170 } else {
171 tokio::time::sleep(delay).await;
172 self.children_with_retry(delay, count - 1).await
173 }
174 }
175 }
176 }
177}
178
179fn sys_handle() -> &'static Mutex<System> {
180 static SYS_HANDLE: OnceLock<Mutex<System>> = OnceLock::new();
181 SYS_HANDLE.get_or_init(|| {
182 let mut sys = System::new_with_specifics(
183 RefreshKind::new().with_processes(ProcessRefreshKind::new()),
184 );
185 sys.refresh_processes(ProcessesToUpdate::All, true);
186
187 Mutex::new(sys)
188 })
189}
190
191impl From<&Process> for ProcInfo {
192 fn from(value: &Process) -> Self {
193 ProcInfo {
194 name: value.name().to_string_lossy().to_string(),
195 cmd: value
196 .cmd()
197 .iter()
198 .map(|p| p.to_string_lossy().to_string())
199 .collect(),
200 exe: value.exe().map(|p| p.to_owned()),
201 pid: value.pid().as_u32() as Pid,
202 parent: value.parent().map(|p| p.as_u32() as Pid),
203 env: value
204 .environ()
205 .iter()
206 .map(|p| p.to_string_lossy().to_string())
207 .collect(),
208 cwd: value.cwd().map(|p| p.to_owned()),
209 }
210 }
211}
212
213impl MaybeHasPid for ProcQuery {
214 fn get_pid(&self) -> Option<Pid> {
215 self.process_id
216 }
217}
218
219impl Default for ProcQuery {
220 fn default() -> Self {
221 ProcQuery::new()
222 }
223}