use std::path::PathBuf;
use std::time::Duration;
use evalbox_sandbox::{Executor, Mount, Plan};
use crate::error::Result;
use crate::output::Output;
#[derive(Debug, Clone)]
pub struct ShellBuilder {
script: String,
timeout: Duration,
memory: u64,
max_pids: u32,
max_output: u64,
network: bool,
mounts: Vec<Mount>,
stdin: Option<Vec<u8>>,
env: Vec<(String, String)>,
}
impl ShellBuilder {
pub fn new(script: &str) -> Self {
Self {
script: script.to_string(),
timeout: Duration::from_secs(30),
memory: 256 * 1024 * 1024,
max_pids: 64,
max_output: 16 * 1024 * 1024,
network: false,
mounts: Vec::new(),
stdin: None,
env: Vec::new(),
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn memory(mut self, bytes: u64) -> Self {
self.memory = bytes;
self
}
pub fn max_pids(mut self, max: u32) -> Self {
self.max_pids = max;
self
}
pub fn max_output(mut self, bytes: u64) -> Self {
self.max_output = bytes;
self
}
pub fn network(mut self, enabled: bool) -> Self {
self.network = enabled;
self
}
pub fn with(mut self, path: impl Into<PathBuf>) -> Self {
let path = path.into();
self.mounts.push(Mount::ro(&path));
self
}
pub fn with_bind(mut self, host: impl Into<PathBuf>, sandbox: impl Into<PathBuf>) -> Self {
self.mounts.push(Mount::bind(host.into(), sandbox.into()));
self
}
pub fn with_rw(mut self, path: impl Into<PathBuf>) -> Self {
let path = path.into();
self.mounts.push(Mount::rw(&path));
self
}
pub fn with_rw_bind(mut self, host: impl Into<PathBuf>, sandbox: impl Into<PathBuf>) -> Self {
self.mounts
.push(Mount::bind(host.into(), sandbox.into()).writable());
self
}
pub fn stdin(mut self, data: impl Into<Vec<u8>>) -> Self {
self.stdin = Some(data.into());
self
}
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.push((key.into(), value.into()));
self
}
pub fn build(self) -> Plan {
let mut plan = Plan::new(["sh", "-c", &self.script])
.timeout(self.timeout)
.memory(self.memory)
.max_pids(self.max_pids)
.max_output(self.max_output)
.network(self.network)
.mounts(self.mounts);
if let Some(stdin) = self.stdin {
plan = plan.stdin(stdin);
}
for (key, value) in self.env {
plan = plan.env(key, value);
}
plan
}
pub fn exec(self) -> Result<Output> {
let plan = self.build();
let output = Executor::run(plan)?;
Ok(output.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_defaults() {
let builder = ShellBuilder::new("echo hello");
assert_eq!(builder.script, "echo hello");
assert!(!builder.network);
assert_eq!(builder.timeout, Duration::from_secs(30));
}
#[test]
fn test_builder_options() {
let builder = ShellBuilder::new("echo hello")
.timeout(Duration::from_secs(10))
.network(true)
.memory(512 * 1024 * 1024);
assert_eq!(builder.timeout, Duration::from_secs(10));
assert!(builder.network);
assert_eq!(builder.memory, 512 * 1024 * 1024);
}
#[test]
fn test_builder_mounts() {
let builder = ShellBuilder::new("echo").with("/data").with_rw("/output");
assert_eq!(builder.mounts.len(), 2);
}
#[test]
fn test_build_plan() {
let builder = ShellBuilder::new("echo hello")
.timeout(Duration::from_secs(5))
.network(true);
let plan = builder.build();
assert_eq!(plan.cmd, vec!["sh", "-c", "echo hello"]);
assert_eq!(plan.timeout, Duration::from_secs(5));
assert!(!plan.network_blocked);
}
}