use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use crate::error::Result;
pub trait SandboxExecutor: Send + Sync {
fn name(&self) -> &str;
fn isolation_level(&self) -> IsolationLevel;
fn is_available(&self) -> BoxFuture<'_, bool>;
fn execute(&self, command: SandboxCommand) -> BoxFuture<'_, Result<ExecutionResult>>;
fn execute_with_limits(
&self,
command: SandboxCommand,
limits: ResourceLimits,
) -> BoxFuture<'_, Result<ExecutionResult>> {
let _ = limits;
self.execute(command)
}
fn cleanup(&self) -> BoxFuture<'_, Result<()>> {
Box::pin(async { Ok(()) })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum IsolationLevel {
None = 0,
Process = 1,
OsSandbox = 2,
Container = 3,
Orchestrated = 4,
}
impl std::fmt::Display for IsolationLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IsolationLevel::None => write!(f, "none"),
IsolationLevel::Process => write!(f, "process"),
IsolationLevel::OsSandbox => write!(f, "os-sandbox"),
IsolationLevel::Container => write!(f, "container"),
IsolationLevel::Orchestrated => write!(f, "orchestrated"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SandboxCommand {
pub kind: CommandKind,
pub working_dir: Option<PathBuf>,
pub env: HashMap<String, String>,
pub timeout: Duration,
pub stdin: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommandKind {
Shell(String),
Program { program: String, args: Vec<String> },
Code { language: String, code: String },
}
impl SandboxCommand {
pub fn shell(cmd: impl Into<String>) -> Self {
Self {
kind: CommandKind::Shell(cmd.into()),
working_dir: None,
env: HashMap::new(),
timeout: Duration::from_secs(30),
stdin: None,
}
}
pub fn program(program: impl Into<String>, args: Vec<String>) -> Self {
Self {
kind: CommandKind::Program {
program: program.into(),
args,
},
working_dir: None,
env: HashMap::new(),
timeout: Duration::from_secs(30),
stdin: None,
}
}
pub fn code(language: impl Into<String>, code: impl Into<String>) -> Self {
Self {
kind: CommandKind::Code {
language: language.into(),
code: code.into(),
},
working_dir: None,
env: HashMap::new(),
timeout: Duration::from_secs(30),
stdin: None,
}
}
pub fn with_working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.working_dir = Some(dir.into());
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.insert(key.into(), value.into());
self
}
pub fn with_stdin(mut self, stdin: impl Into<String>) -> Self {
self.stdin = Some(stdin.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionResult {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub duration: Duration,
pub sandbox_type: String,
pub timed_out: bool,
}
impl ExecutionResult {
pub fn success(&self) -> bool {
self.exit_code == 0 && !self.timed_out
}
pub fn combined_output(&self) -> String {
if self.stderr.is_empty() {
self.stdout.clone()
} else if self.stdout.is_empty() {
self.stderr.clone()
} else {
format!("{}\n{}", self.stdout, self.stderr)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimits {
pub cpu_time_secs: Option<u64>,
pub memory_bytes: Option<u64>,
pub max_output_bytes: Option<u64>,
pub max_processes: Option<u32>,
pub network: bool,
pub read_only_paths: Vec<PathBuf>,
pub writable_paths: Vec<PathBuf>,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
cpu_time_secs: Some(30),
memory_bytes: Some(256 * 1024 * 1024), max_output_bytes: Some(1024 * 1024), max_processes: Some(64),
network: false,
read_only_paths: vec![],
writable_paths: vec![],
}
}
}
impl ResourceLimits {
pub fn unrestricted() -> Self {
Self {
cpu_time_secs: None,
memory_bytes: None,
max_output_bytes: None,
max_processes: None,
network: true,
read_only_paths: vec![],
writable_paths: vec![],
}
}
pub fn strict() -> Self {
Self {
cpu_time_secs: Some(10),
memory_bytes: Some(64 * 1024 * 1024), max_output_bytes: Some(256 * 1024), max_processes: Some(8),
network: false,
read_only_paths: vec![],
writable_paths: vec![],
}
}
}