use std::collections::HashMap; use std::sync::Mutex; use tokio::process::Command; struct ManagedProcess { cmd: String, } /// Process syscall: spawn and manage subprocesses. /// Uses the real OS PID as the identifier. pub struct ProcessSyscall { procs: Mutex>, children: tokio::sync::Mutex>, } impl ProcessSyscall { pub fn new() -> Self { Self { procs: Mutex::new(HashMap::new()), children: tokio::sync::Mutex::new(HashMap::new()), } } pub async fn spawn( &self, cmd: &str, args: &[String], env: &HashMap, cwd: &str, ) -> Result { let mut command = Command::new(cmd); command.args(args); if !cwd.is_empty() { command.current_dir(cwd); } for (k, v) in env { command.env(k, v); } command .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()); let child = command.spawn().map_err(|e| e.to_string())?; let pid = child .id() .ok_or_else(|| "spawned process has no OS pid".to_string())? as u64; self.procs.lock().unwrap().insert( pid, ManagedProcess { cmd: cmd.to_string(), }, ); self.children.lock().await.insert(pid, child); tracing::info!("process spawned: pid={} cmd={}", pid, cmd); Ok(pid) } pub async fn kill(&self, pid: u64) -> Result<(), String> { let mut child = { let mut children = self.children.lock().await; children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))? }; child.kill().await.map_err(|e| e.to_string())?; self.procs.lock().unwrap().remove(&pid); tracing::info!("process killed: pid={}", pid); Ok(()) } pub async fn wait(&self, pid: u64) -> Result { let mut child = { let mut children = self.children.lock().await; children.remove(&pid).ok_or_else(|| format!("pid {} not found", pid))? }; self.procs.lock().unwrap().remove(&pid); let status = child.wait().await.map_err(|e| e.to_string())?; Ok(status.code().unwrap_or(-1)) } pub async fn write_stdin(&self, pid: u64, data: &[u8]) -> Result<(), String> { use tokio::io::AsyncWriteExt; let mut children = self.children.lock().await; if let Some(child) = children.get_mut(&pid) { if let Some(stdin) = child.stdin.as_mut() { stdin.write_all(data).await.map_err(|e| e.to_string())?; stdin.flush().await.map_err(|e| e.to_string())?; Ok(()) } else { Err("stdin not available".to_string()) } } else { Err(format!("pid {} not found", pid)) } } pub async fn read_stdout(&self, pid: u64, buf: &mut [u8]) -> Result { use tokio::io::AsyncReadExt; let mut children = self.children.lock().await; if let Some(child) = children.get_mut(&pid) { if let Some(stdout) = child.stdout.as_mut() { stdout.read(buf).await.map_err(|e| e.to_string()) } else { Err("stdout not available".to_string()) } } else { Err(format!("pid {} not found", pid)) } } pub async fn signal(&self, pid: u64, _signal: i32) -> Result<(), String> { self.kill(pid).await } pub fn list(&self) -> Vec<(u64, String, String)> { let procs = self.procs.lock().unwrap(); procs .iter() .map(|(pid, mp)| (*pid, mp.cmd.clone(), "running".to_string())) .collect() } }