69 lines
3.7 KiB
Rust
69 lines
3.7 KiB
Rust
//! 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<String>,
|
|
env: BTreeMap<String, String>,
|
|
cwd: PathBuf,
|
|
group: Option<u32>,
|
|
stdin: Stdio, stdout: Stdio, stderr: Stdio,
|
|
}
|
|
impl Command {
|
|
/// Use the parent's cwd, environment, and streams.
|
|
pub fn new(program: impl AsRef<str>) -> 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<str>) -> &mut Self { self.args.push(arg.as_ref().into()); self }
|
|
pub fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<str>>) -> &mut Self {
|
|
for arg in args { self.arg(arg); } self
|
|
}
|
|
pub fn env(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> &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<Path>) -> &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<Child> {
|
|
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<i32> { 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<OutputStream>, pub stdout: Option<InputStream>, pub stderr: Option<InputStream>,
|
|
}
|
|
impl Child {
|
|
pub fn id(&self) -> u32 { self.child.pid() }
|
|
pub fn subscribe(&self) -> Pollable { self.child.subscribe() }
|
|
pub fn try_wait(&self) -> Option<i32> { 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(); }
|
|
}
|