1use serde_json::{json, Value};
7use std::path::{Path, PathBuf};
8
9pub fn handle(cmd: &str, req: &Value) -> Value {
11 match cmd {
12 "ps" => ps(req),
13 "kill" => kill(req),
14 "which" => which(req),
15 _ => json!({"ok": false, "err": "unknown_cmd"}),
16 }
17}
18
19fn ps(req: &Value) -> Value {
23 use sysinfo::System;
24 let sys = System::new_all();
25 let filter = req["filter"].as_str();
26 let limit = req["limit"].as_u64().unwrap_or(50) as usize;
27
28 let mut procs: Vec<(u32, String, u64, f32)> = sys
29 .processes()
30 .values()
31 .map(|p| {
32 (
33 p.pid().as_u32(),
34 p.name().to_string_lossy().into_owned(),
35 p.memory(),
36 p.cpu_usage(),
37 )
38 })
39 .filter(|(_, name, _, _)| filter.is_none_or(|f| name.contains(f)))
40 .collect();
41 procs.sort_unstable_by_key(|p| std::cmp::Reverse(p.2));
42 procs.truncate(limit);
43
44 let list: Vec<Value> = procs
45 .into_iter()
46 .map(|(pid, name, mem, cpu)| {
47 json!({"pid": pid, "name": name, "mem": mem, "cpu": (cpu * 100.0).round() / 100.0})
48 })
49 .collect();
50 json!({"ok": true, "procs": list})
51}
52
53fn kill(req: &Value) -> Value {
57 use sysinfo::{Pid, Signal, System};
58 let Some(pid) = req["pid"].as_u64() else {
59 return json!({"ok": false, "err": "no_pid"});
60 };
61 let pid = Pid::from_u32(pid as u32);
62 let sys = System::new_all();
63 let Some(proc_) = sys.process(pid) else {
64 return json!({"ok": false, "err": "no_such_process"});
65 };
66 let signal = match req["signal"].as_str() {
67 Some("kill") => Signal::Kill,
68 _ => Signal::Term,
69 };
70 let ok = proc_.kill_with(signal).unwrap_or_else(|| proc_.kill());
71 json!({ "ok": ok })
72}
73
74fn which(req: &Value) -> Value {
77 let Some(program) = req["program"].as_str() else {
78 return json!({"ok": false, "err": "no_program"});
79 };
80 let path = locate(program).map(|p| p.to_string_lossy().into_owned());
81 json!({"ok": true, "path": path})
82}
83
84fn has_separator(p: &str) -> bool {
85 p.contains('/') || (cfg!(windows) && p.contains('\\'))
86}
87
88fn locate(program: &str) -> Option<PathBuf> {
89 if has_separator(program) {
90 let p = crate::store::expand(program);
91 return is_executable(&p).then_some(p);
92 }
93 let path = std::env::var_os("PATH")?;
94 for dir in std::env::split_paths(&path) {
95 let cand = dir.join(program);
96 if is_executable(&cand) {
97 return Some(cand);
98 }
99 #[cfg(windows)]
100 for ext in ["exe", "cmd", "bat", "com"] {
101 let c = dir.join(format!("{program}.{ext}"));
102 if c.is_file() {
103 return Some(c);
104 }
105 }
106 }
107 None
108}
109
110fn is_executable(p: &Path) -> bool {
111 #[cfg(unix)]
112 {
113 use std::os::unix::fs::PermissionsExt;
114 std::fs::metadata(p)
115 .map(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
116 .unwrap_or(false)
117 }
118 #[cfg(not(unix))]
119 {
120 p.is_file()
121 }
122}