//! Launch world programs through the process interface. No native subprocesses. //! Commands inherit the environment, cwd, and streams unless the caller changes them. //! Pipe callers must drain stdout and stderr before wait to avoid bounded-pipe deadlocks. use std::{collections::BTreeMap, io, path::{Path, PathBuf}}; mod bindings { wit_bindgen::generate!({ path: "../../host/wit", world: "program", generate_all, with: { "wasi:io/poll@0.2.12": ::wasi::io::poll } }); } use bindings::sausage::process::host; pub use bindings::wasi::io::streams::{InputStream, OutputStream}; pub use host::{Stdio, Watcher, Change, ChangeKind}; pub use wasi::io::poll::{Pollable, poll}; /// Hard-stop a process group by a live process ID. pub fn kill(pid: u32) -> io::Result<()> { host::kill(pid).map_err(io::Error::other) } /// A prepared child process. pub struct Command { program: String, args: Vec, env: BTreeMap, cwd: PathBuf, group: Option, stdin: Stdio, stdout: Stdio, stderr: Stdio, } impl Command { /// Use the parent's cwd, environment, and streams. pub fn new(program: impl AsRef) -> Self { Self { program: program.as_ref().into(), args: Vec::new(), env: std::env::vars().collect(), cwd: std::env::current_dir().unwrap_or_else(|_| "/".into()), group: None, stdin: Stdio::Inherit, stdout: Stdio::Inherit, stderr: Stdio::Inherit } } pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { self.args.push(arg.as_ref().into()); self } pub fn args(&mut self, args: impl IntoIterator>) -> &mut Self { for arg in args { self.arg(arg); } self } pub fn env(&mut self, key: impl AsRef, value: impl AsRef) -> &mut Self { self.env.insert(key.as_ref().into(), value.as_ref().into()); self } pub fn env_clear(&mut self) -> &mut Self { self.env.clear(); self } pub fn current_dir(&mut self, cwd: impl AsRef) -> &mut Self { self.cwd = cwd.as_ref().into(); self } /// Isolate the child group so kill does not stop the caller's group. pub fn new_group(&mut self) -> &mut Self { self.group = Some(0); self } pub fn stdin(&mut self, mode: Stdio) -> &mut Self { self.stdin = mode; self } pub fn stdout(&mut self, mode: Stdio) -> &mut Self { self.stdout = mode; self } pub fn stderr(&mut self, mode: Stdio) -> &mut Self { self.stderr = mode; self } /// Resolve PATH and start the component. pub fn spawn(&self) -> io::Result { let child = host::spawn(&host::Request { program: self.program.clone(), argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(), env: self.env.clone().into_iter().collect(), cwd: self.cwd.to_string_lossy().into_owned(), group: self.group, stdin: self.stdin, stdout: self.stdout, stderr: self.stderr }).map_err(io::Error::other)?; Ok(Child { stdin: matches!(self.stdin, Stdio::Pipe).then(|| child.stdin()), stdout: matches!(self.stdout, Stdio::Pipe).then(|| child.stdout()), stderr: matches!(self.stderr, Stdio::Pipe).then(|| child.stderr()), child }) } pub fn status(&self) -> io::Result { Ok(self.spawn()?.wait()) } } /// A child handle. Drop does not kill the child. Drop stdin to send EOF. pub struct Child { child: host::Child, pub stdin: Option, pub stdout: Option, pub stderr: Option, } impl Child { pub fn id(&self) -> u32 { self.child.pid() } pub fn subscribe(&self) -> Pollable { self.child.subscribe() } pub fn try_wait(&self) -> Option { self.child.status() } pub fn wait(&self) -> i32 { self.child.wait() } /// Stop every process in this child's group. pub fn kill(&self) { self.child.kill(); } }