use std::collections::BTreeMap;
use std::io::{self, Read, Write};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExitStatus {
pub code: Option<i32>,
pub signal: Option<i32>,
}
impl ExitStatus {
pub fn from_code(code: i32) -> Self {
Self {
code: Some(code),
signal: None,
}
}
pub fn from_signal(signal: i32) -> Self {
Self {
code: None,
signal: Some(signal),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EnvMode {
InheritClean,
Replace,
Patch,
}
#[derive(Clone, Debug)]
pub struct SpawnSpec {
pub builtin: &'static str,
pub program: String,
pub args: Vec<String>,
pub cwd: Option<PathBuf>,
pub env: BTreeMap<String, String>,
pub env_mode: EnvMode,
pub use_stdin: bool,
pub configure_process_group: bool,
}
pub trait ProcessHandle: Send {
fn pid(&self) -> Option<u32>;
fn process_group_id(&self) -> Option<u32>;
fn killer(&self) -> Arc<dyn ProcessKiller>;
fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>>;
fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>>;
fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>>;
fn wait_with_timeout(
&mut self,
timeout: Option<Duration>,
) -> io::Result<(Option<ExitStatus>, bool)>;
fn wait(&mut self) -> io::Result<ExitStatus>;
}
pub trait ProcessKiller: Send + Sync {
fn kill(&self);
}
pub trait ProcessSpawner: Send + Sync {
fn spawn(&self, spec: SpawnSpec) -> Result<Box<dyn ProcessHandle>, ProcessError>;
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum ProcessError {
#[error("invalid argv: {0}")]
InvalidArgv(String),
#[error("sandbox setup failed: {0}")]
SandboxSetup(String),
#[error("sandbox cwd rejected: {0}")]
SandboxCwd(String),
#[error("sandbox rejected spawn: {0}")]
SandboxSpawn(String),
#[error("spawn failed: {0}")]
Spawn(String),
}
use std::cell::RefCell;
thread_local! {
static THREAD_SPAWNER: RefCell<Option<Arc<dyn ProcessSpawner>>> = const { RefCell::new(None) };
}
pub fn install_spawner(spawner: Arc<dyn ProcessSpawner>) -> SpawnerGuard {
let prev = THREAD_SPAWNER.with(|slot| slot.replace(Some(spawner)));
SpawnerGuard { prev: Some(prev) }
}
pub struct SpawnerGuard {
prev: Option<Option<Arc<dyn ProcessSpawner>>>,
}
impl Drop for SpawnerGuard {
fn drop(&mut self) {
if let Some(prev) = self.prev.take() {
THREAD_SPAWNER.with(|slot| {
*slot.borrow_mut() = prev;
});
}
}
}
pub fn current_spawner() -> Arc<dyn ProcessSpawner> {
THREAD_SPAWNER
.with(|slot| slot.borrow().clone())
.unwrap_or_else(super::real::default_spawner)
}
pub fn spawn_process(spec: SpawnSpec) -> Result<Box<dyn ProcessHandle>, ProcessError> {
current_spawner().spawn(spec)
}