1use alloc::{format, string::String, vec::Vec};
2use core::fmt::Write;
3use strat9_syscall::{call, flag};
4
5use crate::net;
6const WEBRTC_PATH: &str = "/srv/strate-webrtc/default";
7const WEBRTC_OP_SESSION_OPEN: u32 = 0x300;
8const WEBRTC_OP_SESSION_CLOSE: u32 = 0x301;
9const WEBRTC_OP_SESSION_INFO: u32 = 0x30A;
10
11pub struct ProcInfo {
16 pub pid: u32,
17 pub ppid: u32,
18 pub name: String,
19 pub state: String,
20 pub silo_id: String,
21 pub mem_used: String,
22}
23
24fn parse_proc_status(raw: &str) -> ProcInfo {
26 let mut info = ProcInfo {
27 pid: 0,
28 ppid: 0,
29 name: String::new(),
30 state: String::new(),
31 silo_id: String::new(),
32 mem_used: String::new(),
33 };
34 for line in raw.lines() {
35 let (key, val) = match line.split_once(':') {
36 Some((k, v)) => (k.trim(), v.trim()),
37 None => continue,
38 };
39 match key {
40 "Pid" => info.pid = val.parse().unwrap_or(0),
41 "PPid" => info.ppid = val.parse().unwrap_or(0),
42 "Name" => info.name = val.into(),
43 "State" => info.state = val.into(),
44 "SiloId" => info.silo_id = val.into(),
45 "SiloMemUsed" => info.mem_used = val.into(),
46 _ => {}
47 }
48 }
49 info
50}
51
52pub fn kernel_version() -> String {
58 net::read_file_text("/proc/version")
59}
60
61pub fn cpu_info() -> String {
63 net::read_file_text("/proc/cpuinfo")
64}
65
66pub fn mem_info() -> String {
68 net::read_file_text("/proc/meminfo")
69}
70
71pub fn silo_info() -> String {
73 net::read_file_text("/proc/silos")
74}
75
76pub fn net_routes() -> String {
78 net::read_file_text("/net/routes")
79}
80
81pub fn net_address() -> String {
87 net::read_file_text("/net/address").trim().into()
88}
89
90pub fn net_gateway() -> String {
92 net::read_file_text("/net/gateway").trim().into()
93}
94
95pub fn net_dns() -> String {
97 net::read_file_text("/net/dns").trim().into()
98}
99
100pub fn net_ip() -> String {
102 net::read_file_text("/net/ip").trim().into()
103}
104
105pub fn net_netmask() -> String {
107 net::read_file_text("/net/netmask").trim().into()
108}
109
110pub fn process_list() -> Vec<ProcInfo> {
116 let fd = match call::openat(
117 0,
118 "/proc",
119 (flag::OpenFlags::RDONLY | flag::OpenFlags::DIRECTORY).bits() as usize,
120 0,
121 ) {
122 Ok(fd) => fd as usize,
123 Err(_) => return Vec::new(),
124 };
125
126 let mut pids = Vec::new();
127 let mut raw = [0u8; 4096];
128 const K_DIRENT_HDR: usize = 11; loop {
131 let n = match call::getdents(fd, &mut raw) {
132 Ok(0) => break,
133 Ok(n) => n,
134 Err(_) => break,
135 };
136
137 let mut offset = 0usize;
138 while offset + K_DIRENT_HDR <= n {
139 let name_len = u16::from_le_bytes([raw[offset + 9], raw[offset + 10]]) as usize;
140 let entry_size = K_DIRENT_HDR.saturating_add(name_len).saturating_add(1);
141 if entry_size == 0 || offset + entry_size > n {
142 break;
143 }
144 let name_start = offset + K_DIRENT_HDR;
145 let name_end = name_start + name_len;
146 if let Ok(name_str) = core::str::from_utf8(&raw[name_start..name_end]) {
147 if let Ok(pid) = name_str.parse::<u32>() {
148 pids.push(pid);
149 }
150 }
151 offset += entry_size;
152 }
153 }
154 let _ = call::close(fd);
155
156 let mut result = Vec::with_capacity(pids.len());
157 for pid in pids {
158 let status_raw = net::read_file_text(&format!("/proc/{}/status", pid));
159 if !status_raw.is_empty() {
160 result.push(parse_proc_status(&status_raw));
161 }
162 }
163 result
164}
165
166pub fn uptime_secs() -> u64 {
168 net::clock_gettime_ns() / 1_000_000_000
169}
170
171pub fn kill_process(pid: u32) -> bool {
177 if pid <= 2 {
178 return false;
179 }
180 call::kill(pid as isize, 9).is_ok()
181}
182
183pub fn json_health() -> String {
189 let pid = call::getpid().unwrap_or(0);
190 let up = uptime_secs();
191 format!(r#"{{"status":"ok","pid":{},"uptime_secs":{}}}"#, pid, up)
192}
193
194pub fn json_uptime() -> String {
196 let ns = net::clock_gettime_ns();
197 let secs = ns / 1_000_000_000;
198 let mins = secs / 60;
199 let hours = mins / 60;
200 let days = hours / 24;
201 format!(
202 r#"{{"uptime_ns":{},"secs":{},"human":"{}d {}h {}m {}s"}}"#,
203 ns,
204 secs,
205 days,
206 hours % 24,
207 mins % 60,
208 secs % 60
209 )
210}
211
212pub fn json_version() -> String {
214 format!(r#"{{"version":"{}"}}"#, json_escape(&kernel_version()))
215}
216
217pub fn json_cpuinfo() -> String {
219 format!(r#"{{"cpuinfo":"{}"}}"#, json_escape(&cpu_info()))
220}
221
222pub fn json_meminfo() -> String {
224 format!(r#"{{"meminfo":"{}"}}"#, json_escape(&mem_info()))
225}
226
227pub fn json_silos() -> String {
229 format!(r#"{{"silos":"{}"}}"#, json_escape(&silo_info()))
230}
231
232pub fn json_network() -> String {
234 format!(
235 r#"{{"address":"{}","gateway":"{}","dns":"{}","ip":"{}","netmask":"{}"}}"#,
236 json_escape(&net_address()),
237 json_escape(&net_gateway()),
238 json_escape(&net_dns()),
239 json_escape(&net_ip()),
240 json_escape(&net_netmask()),
241 )
242}
243
244pub fn json_routes() -> String {
246 format!(r#"{{"routes":"{}"}}"#, json_escape(&net_routes()))
247}
248
249pub fn json_processes() -> String {
251 let procs = process_list();
252 let mut out = String::with_capacity(procs.len() * 120 + 32);
253 let _ = write!(out, r#"{{"count":{},"processes":["#, procs.len());
254 for (i, p) in procs.iter().enumerate() {
255 if i > 0 {
256 out.push(',');
257 }
258 let _ = write!(
259 out,
260 r#"{{"pid":{},"ppid":{},"name":"{}","state":"{}","silo":"{}","mem":"{}"}}"#,
261 p.pid,
262 p.ppid,
263 json_escape(&p.name),
264 json_escape(&p.state),
265 json_escape(&p.silo_id),
266 json_escape(&p.mem_used),
267 );
268 }
269 out.push_str("]}");
270 out
271}
272
273pub fn json_kill_result(pid: u32) -> String {
275 let ok = kill_process(pid);
276 format!(
277 r#"{{"pid":{},"killed":{},"error":{}}}"#,
278 pid,
279 ok,
280 if ok { "null" } else { "\"EPERM or ESRCH\"" }
281 )
282}
283
284pub fn json_all() -> String {
286 let mut out = String::with_capacity(4096);
287 out.push_str(r#"{"health":"#);
288 out.push_str(&json_health());
289 out.push_str(r#","uptime":"#);
290 out.push_str(&json_uptime());
291 out.push_str(r#","version":"#);
292 out.push_str(&json_version());
293 out.push_str(r#","cpuinfo":"#);
294 out.push_str(&json_cpuinfo());
295 out.push_str(r#","meminfo":"#);
296 out.push_str(&json_meminfo());
297 out.push_str(r#","silos":"#);
298 out.push_str(&json_silos());
299 out.push_str(r#","network":"#);
300 out.push_str(&json_network());
301 out.push_str(r#","routes":"#);
302 out.push_str(&json_routes());
303 out.push_str(r#","processes":"#);
304 out.push_str(&json_processes());
305 out.push('}');
306 out
307}
308
309pub fn json_graphics_open(sid: u32) -> String {
310 let mut payload = [0u8; 5];
311 payload[0..4].copy_from_slice(&sid.to_le_bytes());
312 payload[4] = 0;
313 let Some(reply) = net::ipc_call_path(WEBRTC_PATH, WEBRTC_OP_SESSION_OPEN, &payload) else {
314 return String::from(r#"{"ok":false,"error":"ipc_unreachable"}"#);
315 };
316 let status = u32::from_le_bytes([
317 reply.payload[0],
318 reply.payload[1],
319 reply.payload[2],
320 reply.payload[3],
321 ]);
322 if status != 0 {
323 return format!(r#"{{"ok":false,"status":{}}}"#, status);
324 }
325 let session = u64::from_le_bytes([
326 reply.payload[4],
327 reply.payload[5],
328 reply.payload[6],
329 reply.payload[7],
330 reply.payload[8],
331 reply.payload[9],
332 reply.payload[10],
333 reply.payload[11],
334 ]);
335 let token = u64::from_le_bytes([
336 reply.payload[12],
337 reply.payload[13],
338 reply.payload[14],
339 reply.payload[15],
340 reply.payload[16],
341 reply.payload[17],
342 reply.payload[18],
343 reply.payload[19],
344 ]);
345 let ttl = u32::from_le_bytes([
346 reply.payload[20],
347 reply.payload[21],
348 reply.payload[22],
349 reply.payload[23],
350 ]);
351 format!(
352 r#"{{"ok":true,"status":0,"session_id":{},"token":{},"ttl_sec":{}}}"#,
353 session, token, ttl
354 )
355}
356
357pub fn json_graphics_close(session_id: u64) -> String {
358 let payload = session_id.to_le_bytes();
359 let Some(reply) = net::ipc_call_path(WEBRTC_PATH, WEBRTC_OP_SESSION_CLOSE, &payload) else {
360 return String::from(r#"{"ok":false,"error":"ipc_unreachable"}"#);
361 };
362 let status = u32::from_le_bytes([
363 reply.payload[0],
364 reply.payload[1],
365 reply.payload[2],
366 reply.payload[3],
367 ]);
368 format!(r#"{{"ok":{},"status":{}}}"#, status == 0, status)
369}
370
371pub fn json_graphics_info(session_id: u64) -> String {
372 let payload = session_id.to_le_bytes();
373 let Some(reply) = net::ipc_call_path(WEBRTC_PATH, WEBRTC_OP_SESSION_INFO, &payload) else {
374 return String::from(r#"{"ok":false,"error":"ipc_unreachable"}"#);
375 };
376 let status = u32::from_le_bytes([
377 reply.payload[0],
378 reply.payload[1],
379 reply.payload[2],
380 reply.payload[3],
381 ]);
382 if status != 0 {
383 return format!(r#"{{"ok":false,"status":{}}}"#, status);
384 }
385 let sid = u32::from_le_bytes([
386 reply.payload[12],
387 reply.payload[13],
388 reply.payload[14],
389 reply.payload[15],
390 ]);
391 let token = u64::from_le_bytes([
392 reply.payload[16],
393 reply.payload[17],
394 reply.payload[18],
395 reply.payload[19],
396 reply.payload[20],
397 reply.payload[21],
398 reply.payload[22],
399 reply.payload[23],
400 ]);
401 let flags = u64::from_le_bytes([
402 reply.payload[24],
403 reply.payload[25],
404 reply.payload[26],
405 reply.payload[27],
406 reply.payload[28],
407 reply.payload[29],
408 reply.payload[30],
409 reply.payload[31],
410 ]);
411 let expires_at_ns = u64::from_le_bytes([
412 reply.payload[32],
413 reply.payload[33],
414 reply.payload[34],
415 reply.payload[35],
416 reply.payload[36],
417 reply.payload[37],
418 reply.payload[38],
419 reply.payload[39],
420 ]);
421 format!(
422 r#"{{"ok":true,"status":0,"silo_id":{},"token":{},"flags":{},"expires_at_ns":{}}}"#,
423 sid, token, flags, expires_at_ns
424 )
425}
426
427pub fn json_escape(s: &str) -> String {
433 let mut out = String::with_capacity(s.len() + s.len() / 8);
434 for c in s.chars() {
435 match c {
436 '"' => out.push_str("\\\""),
437 '\\' => out.push_str("\\\\"),
438 '\n' => out.push_str("\\n"),
439 '\r' => out.push_str("\\r"),
440 '\t' => out.push_str("\\t"),
441 c if c.is_control() => {
442 let _ = write!(out, "\\u{:04x}", c as u32);
443 }
444 c => out.push(c),
445 }
446 }
447 out
448}