1use crate::error::{ProcError, Result};
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9use sysinfo::{Pid, ProcessStatus as SysProcessStatus, System};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ProcessStatus {
15 Running,
17 Sleeping,
19 Stopped,
21 Zombie,
23 Dead,
25 Unknown,
27}
28
29impl From<SysProcessStatus> for ProcessStatus {
30 fn from(status: SysProcessStatus) -> Self {
31 match status {
32 SysProcessStatus::Run => ProcessStatus::Running,
33 SysProcessStatus::Sleep => ProcessStatus::Sleeping,
34 SysProcessStatus::Stop => ProcessStatus::Stopped,
35 SysProcessStatus::Zombie => ProcessStatus::Zombie,
36 SysProcessStatus::Dead => ProcessStatus::Dead,
37 _ => ProcessStatus::Unknown,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Process {
45 pub pid: u32,
47 pub name: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub exe_path: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub cwd: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub command: Option<String>,
58 pub cpu_percent: f32,
60 pub memory_mb: f64,
62 pub status: ProcessStatus,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub user: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub parent_pid: Option<u32>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub start_time: Option<u64>,
73}
74
75impl Process {
76 pub fn find_by_name(pattern: &str) -> Result<Vec<Process>> {
78 let mut sys = System::new_all();
79 sys.refresh_all();
80
81 let pattern_lower = pattern.to_lowercase();
82 let self_pid = sysinfo::Pid::from_u32(std::process::id());
83 let processes: Vec<Process> = sys
84 .processes()
85 .iter()
86 .filter_map(|(pid, proc)| {
87 if *pid == self_pid {
90 return None;
91 }
92
93 let name = proc.name().to_string_lossy().to_string();
94 let cmd: String = proc
95 .cmd()
96 .iter()
97 .map(|s| s.to_string_lossy())
98 .collect::<Vec<_>>()
99 .join(" ");
100
101 if name.to_lowercase().contains(&pattern_lower)
103 || cmd.to_lowercase().contains(&pattern_lower)
104 {
105 Some(Process::from_sysinfo(*pid, proc))
106 } else {
107 None
108 }
109 })
110 .collect();
111
112 if processes.is_empty() {
113 return Err(ProcError::ProcessNotFound(pattern.to_string()));
114 }
115
116 Ok(processes)
117 }
118
119 pub fn find_by_pid(pid: u32) -> Result<Option<Process>> {
121 let mut sys = System::new_all();
122 sys.refresh_all();
123
124 let sysinfo_pid = Pid::from_u32(pid);
125
126 Ok(sys
127 .processes()
128 .get(&sysinfo_pid)
129 .map(|proc| Process::from_sysinfo(sysinfo_pid, proc)))
130 }
131
132 pub fn find_all() -> Result<Vec<Process>> {
134 let mut sys = System::new_all();
135 sys.refresh_all();
136
137 let self_pid = sysinfo::Pid::from_u32(std::process::id());
138 let processes: Vec<Process> = sys
139 .processes()
140 .iter()
141 .filter(|(pid, _)| **pid != self_pid)
142 .map(|(pid, proc)| Process::from_sysinfo(*pid, proc))
143 .collect();
144
145 Ok(processes)
146 }
147
148 pub fn find_by_exe_path(path: &std::path::Path) -> Result<Vec<Process>> {
150 let all = Self::find_all()?;
151 let path_str = path.to_string_lossy();
152
153 Ok(all
154 .into_iter()
155 .filter(|p| {
156 if let Some(ref exe) = p.exe_path {
157 exe == &*path_str || std::path::Path::new(exe) == path
159 } else {
160 false
161 }
162 })
163 .collect())
164 }
165
166 #[cfg(unix)]
168 pub fn find_by_open_file(path: &std::path::Path) -> Result<Vec<Process>> {
169 use std::process::Command;
170
171 let output = Command::new("lsof")
172 .args(["-t", &path.to_string_lossy()]) .output();
174
175 let output = match output {
176 Ok(o) => o,
177 Err(_) => return Ok(vec![]), };
179
180 if !output.status.success() {
181 return Ok(vec![]); }
183
184 let pids: Vec<u32> = String::from_utf8_lossy(&output.stdout)
185 .lines()
186 .filter_map(|line| line.trim().parse().ok())
187 .collect();
188
189 let mut processes = Vec::new();
190 for pid in pids {
191 if let Ok(Some(proc)) = Self::find_by_pid(pid) {
192 processes.push(proc);
193 }
194 }
195
196 Ok(processes)
197 }
198
199 #[cfg(not(unix))]
201 pub fn find_by_open_file(_path: &std::path::Path) -> Result<Vec<Process>> {
202 Ok(vec![])
204 }
205
206 pub fn find_stuck(timeout: Duration) -> Result<Vec<Process>> {
209 let mut sys = System::new_all();
210 sys.refresh_all();
211
212 std::thread::sleep(Duration::from_millis(500));
214 sys.refresh_all();
215
216 let timeout_secs = timeout.as_secs();
217 let processes: Vec<Process> = sys
218 .processes()
219 .iter()
220 .filter_map(|(pid, proc)| {
221 let cpu = proc.cpu_usage();
222 let run_time = proc.run_time();
223
224 if run_time > timeout_secs && cpu > 50.0 {
227 Some(Process::from_sysinfo(*pid, proc))
228 } else {
229 None
230 }
231 })
232 .collect();
233
234 Ok(processes)
235 }
236
237 pub fn kill(&self) -> Result<()> {
239 let mut sys = System::new();
240 sys.refresh_processes(
241 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
242 true,
243 );
244
245 if let Some(proc) = sys.process(Pid::from_u32(self.pid)) {
246 if proc.kill() {
247 Ok(())
248 } else {
249 Err(ProcError::SignalError(format!(
250 "Failed to kill process {}",
251 self.pid
252 )))
253 }
254 } else {
255 Err(ProcError::ProcessNotFound(self.pid.to_string()))
256 }
257 }
258
259 pub fn kill_and_wait(&self) -> Result<Option<std::process::ExitStatus>> {
262 let mut sys = System::new();
263 sys.refresh_processes(
264 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
265 true,
266 );
267
268 if let Some(proc) = sys.process(Pid::from_u32(self.pid)) {
269 proc.kill_and_wait().map_err(|e| {
270 ProcError::SignalError(format!("Failed to kill process {}: {:?}", self.pid, e))
271 })
272 } else {
273 Err(ProcError::ProcessNotFound(self.pid.to_string()))
274 }
275 }
276
277 #[cfg(unix)]
279 pub fn terminate(&self) -> Result<()> {
280 use nix::sys::signal::{kill, Signal};
281 use nix::unistd::Pid as NixPid;
282
283 kill(NixPid::from_raw(self.pid as i32), Signal::SIGTERM)
284 .map_err(|e| ProcError::SignalError(e.to_string()))
285 }
286
287 #[cfg(windows)]
289 pub fn terminate(&self) -> Result<()> {
290 use std::process::Command;
291
292 Command::new("taskkill")
293 .args(["/PID", &self.pid.to_string()])
294 .output()
295 .map_err(|e| ProcError::SystemError(e.to_string()))?;
296
297 Ok(())
298 }
299
300 pub fn exists(&self) -> bool {
302 let mut sys = System::new();
303 sys.refresh_processes(
304 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
305 true,
306 );
307 sys.process(Pid::from_u32(self.pid)).is_some()
308 }
309
310 pub fn is_running(&self) -> bool {
312 self.exists()
313 }
314
315 pub fn wait(&self) -> Option<std::process::ExitStatus> {
318 let mut sys = System::new();
319 sys.refresh_processes(
320 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
321 true,
322 );
323
324 sys.process(Pid::from_u32(self.pid))
325 .and_then(|proc| proc.wait())
326 }
327
328 fn from_sysinfo(pid: Pid, proc: &sysinfo::Process) -> Self {
330 let cmd_vec = proc.cmd();
331 let command = if cmd_vec.is_empty() {
332 None
333 } else {
334 Some(
335 cmd_vec
336 .iter()
337 .map(|s| s.to_string_lossy())
338 .collect::<Vec<_>>()
339 .join(" "),
340 )
341 };
342
343 let exe_path = proc.exe().map(|p| p.to_string_lossy().to_string());
344 let cwd = proc.cwd().map(|p| p.to_string_lossy().to_string());
345
346 Process {
347 pid: pid.as_u32(),
348 name: proc.name().to_string_lossy().to_string(),
349 exe_path,
350 cwd,
351 command,
352 cpu_percent: proc.cpu_usage(),
353 memory_mb: proc.memory() as f64 / 1024.0 / 1024.0,
354 status: ProcessStatus::from(proc.status()),
355 user: proc.user_id().map(|u| u.to_string()),
356 parent_pid: proc.parent().map(|p| p.as_u32()),
357 start_time: Some(proc.start_time()),
358 }
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_find_all_processes() {
368 let processes = Process::find_all().unwrap();
369 assert!(!processes.is_empty(), "Should find at least one process");
370 }
371
372 #[test]
373 fn test_find_by_pid_self() {
374 let pid = std::process::id();
375 let process = Process::find_by_pid(pid).unwrap();
376 assert!(process.is_some(), "Should find own process");
377 }
378
379 #[test]
380 fn test_find_nonexistent_process() {
381 let result = Process::find_by_name("nonexistent_process_12345");
382 assert!(result.is_err());
383 }
384}