use std::{
error::Error,
io::{Read, Write},
process::ExitStatus,
};
use cageforge_policy::PathResolutionContext;
use crate::{BackendRequest, PreparedBackendRequest, SandboxBackend};
pub trait DynSandbox: SandboxBackend + Send + Sync {
fn launch(
&self,
request: BackendRequest<'_>,
context: &PathResolutionContext,
) -> Result<Box<dyn SandboxChild<Error = SandboxExecutionError> + Send>, SandboxExecutionError>;
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SandboxExecutionError {
#[error("sandbox preparation failed: {source}")]
Prepare {
source: Box<dyn Error + Send + Sync>,
},
#[error("sandbox launch failed: {source}")]
Spawn {
source: Box<dyn Error + Send + Sync>,
},
#[error("sandbox status check failed: {source}")]
TryWait {
source: Box<dyn Error + Send + Sync>,
},
#[error("sandbox wait failed: {source}")]
Wait {
source: Box<dyn Error + Send + Sync>,
},
#[error("sandbox termination failed: {source}")]
Kill {
source: Box<dyn Error + Send + Sync>,
},
}
struct ErasedChild<C> {
child: C,
}
pub trait Sandbox: SandboxBackend {
type Child: SandboxChild<Error = Self::Error>;
type Error: std::error::Error + 'static;
fn prepare<'a>(
&self,
request: BackendRequest<'a>,
context: &PathResolutionContext,
) -> Result<PreparedBackendRequest<'a, Self>, Self::Error>
where
Self: Sized;
fn spawn<'a>(
&self,
prepared: PreparedBackendRequest<'a, Self>,
) -> Result<Self::Child, Self::Error>
where
Self: Sized;
}
pub trait SandboxChild {
fn id(&self) -> u32;
fn stdin(&mut self) -> Option<&mut dyn Write>;
fn stdout(&mut self) -> Option<&mut dyn Read>;
fn stderr(&mut self) -> Option<&mut dyn Read>;
fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>> {
None
}
fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>> {
None
}
fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>> {
None
}
type Error: std::error::Error + 'static;
fn try_wait(&mut self) -> Result<Option<ExitStatus>, Self::Error>;
fn wait(&mut self) -> Result<ExitStatus, Self::Error>;
fn kill(&mut self) -> Result<(), Self::Error>;
}
impl<B> DynSandbox for B
where
B: Sandbox + Send + Sync,
B::Child: Send + 'static,
B::Error: Send + Sync,
{
fn launch(
&self,
request: BackendRequest<'_>,
context: &PathResolutionContext,
) -> Result<Box<dyn SandboxChild<Error = SandboxExecutionError> + Send>, SandboxExecutionError>
{
let prepared =
self.prepare(request, context)
.map_err(|source| SandboxExecutionError::Prepare {
source: Box::new(source),
})?;
let child = self
.spawn(prepared)
.map_err(|source| SandboxExecutionError::Spawn {
source: Box::new(source),
})?;
Ok(Box::new(ErasedChild { child }))
}
}
impl<C> SandboxChild for ErasedChild<C>
where
C: SandboxChild,
C::Error: Send + Sync,
{
type Error = SandboxExecutionError;
fn id(&self) -> u32 {
self.child.id()
}
fn stdin(&mut self) -> Option<&mut dyn Write> {
self.child.stdin()
}
fn stdout(&mut self) -> Option<&mut dyn Read> {
self.child.stdout()
}
fn stderr(&mut self) -> Option<&mut dyn Read> {
self.child.stderr()
}
fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>> {
self.child.take_stdin()
}
fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>> {
self.child.take_stdout()
}
fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>> {
self.child.take_stderr()
}
fn try_wait(&mut self) -> Result<Option<ExitStatus>, Self::Error> {
self.child
.try_wait()
.map_err(|source| SandboxExecutionError::TryWait {
source: Box::new(source),
})
}
fn wait(&mut self) -> Result<ExitStatus, Self::Error> {
self.child
.wait()
.map_err(|source| SandboxExecutionError::Wait {
source: Box::new(source),
})
}
fn kill(&mut self) -> Result<(), Self::Error> {
self.child
.kill()
.map_err(|source| SandboxExecutionError::Kill {
source: Box::new(source),
})
}
}