use std::future::Future;
use std::process::{ExitStatus, Output, Stdio};
use blocking::unblock;
use crate::config::SandboxConfigData;
use crate::error::Result;
use crate::sandbox::ProcessTracker;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "windows")]
pub mod windows;
pub struct Child {
inner: Option<std::process::Child>,
tracker: Option<ProcessTracker>,
pid: u32,
}
impl Child {
pub(crate) fn new(inner: std::process::Child) -> Self {
let pid = inner.id();
Self {
inner: Some(inner),
tracker: None,
pid,
}
}
pub(crate) fn with_tracker(mut self, tracker: ProcessTracker) -> Self {
self.tracker = Some(tracker);
self
}
fn unregister_if_tracked(&mut self) {
if let Some(tracker) = self.tracker.take() {
tracker.unregister(self.id());
}
}
pub fn stdin(&mut self) -> Option<&mut std::process::ChildStdin> {
self.inner.as_mut().and_then(|child| child.stdin.as_mut())
}
pub fn stdout(&mut self) -> Option<&mut std::process::ChildStdout> {
self.inner.as_mut().and_then(|child| child.stdout.as_mut())
}
pub fn stderr(&mut self) -> Option<&mut std::process::ChildStderr> {
self.inner.as_mut().and_then(|child| child.stderr.as_mut())
}
pub fn take_stdin(&mut self) -> Option<std::process::ChildStdin> {
self.inner.as_mut().and_then(|child| child.stdin.take())
}
pub fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
self.inner.as_mut().and_then(|child| child.stdout.take())
}
pub fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
self.inner.as_mut().and_then(|child| child.stderr.take())
}
pub fn id(&self) -> u32 {
self.pid
}
pub async fn wait(&mut self) -> Result<ExitStatus> {
let pid = self.pid;
let tracker = self.tracker.take();
let mut inner = self
.inner
.take()
.expect("child process no longer available");
let (inner, status) = unblock(move || {
let status = inner.wait();
(inner, status)
})
.await;
self.inner = Some(inner);
if let Some(tracker) = tracker {
tracker.unregister(pid);
}
Ok(status?)
}
pub async fn wait_with_output(self) -> Result<Output> {
let inner = self.inner.expect("child process no longer available");
let pid = inner.id();
let tracker = self.tracker;
let output = unblock(move || inner.wait_with_output()).await?;
if let Some(tracker) = tracker {
tracker.unregister(pid);
}
Ok(output)
}
pub fn kill(&mut self) -> Result<()> {
let inner = self
.inner
.as_mut()
.expect("child process no longer available");
Ok(inner.kill()?)
}
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
let inner = self
.inner
.as_mut()
.expect("child process no longer available");
let status = inner.try_wait()?;
if status.is_some() {
self.unregister_if_tracked();
}
Ok(status)
}
}
pub(crate) trait Backend: Sized + Send + Sync {
#[allow(clippy::too_many_arguments)]
fn execute(
&self,
config: &SandboxConfigData,
proxy_port: u16,
program: &str,
args: &[String],
envs: &[(String, String)],
current_dir: Option<&std::path::Path>,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> impl Future<Output = Result<Output>> + Send;
#[allow(clippy::too_many_arguments)]
fn spawn(
&self,
config: &SandboxConfigData,
proxy_port: u16,
program: &str,
args: &[String],
envs: &[(String, String)],
current_dir: Option<&std::path::Path>,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> impl Future<Output = Result<Child>> + Send;
}
#[cfg(target_os = "macos")]
pub(crate) fn create_native_backend() -> Result<macos::MacOSBackend> {
macos::MacOSBackend::new()
}
#[cfg(target_os = "linux")]
pub(crate) fn create_native_backend() -> Result<linux::LinuxBackend> {
linux::LinuxBackend::new()
}
#[cfg(target_os = "windows")]
pub(crate) fn create_native_backend() -> Result<windows::WindowsBackend> {
windows::WindowsBackend::new()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
pub(crate) fn create_native_backend() -> Result<()> {
Err(crate::error::Error::UnsupportedPlatform)
}