pub mod config;
pub mod error;
pub mod exec;
pub mod fs;
pub mod runtime;
pub mod toolbox;
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::config::SandboxConfig;
use crate::error::{Result, SandboxError};
use crate::fs::overlay::{FsChange, FsOverlay};
use crate::runtime::{ExecResult, WasiRuntime};
pub struct Sandbox {
runtime: WasiRuntime,
overlay: Arc<Mutex<Option<FsOverlay>>>,
config: SandboxConfig,
destroyed: Arc<std::sync::atomic::AtomicBool>,
}
impl Sandbox {
pub fn new(config: SandboxConfig) -> Result<Self> {
let overlay = FsOverlay::new(&config.work_dir)?;
let runtime = WasiRuntime::new(config.clone())?;
Ok(Self {
runtime,
overlay: Arc::new(Mutex::new(Some(overlay))),
config,
destroyed: Arc::new(std::sync::atomic::AtomicBool::new(false)),
})
}
pub async fn exec(&self, command: &str, args: &[String]) -> Result<ExecResult> {
self.check_destroyed()?;
if !toolbox::is_available(command) {
return Err(SandboxError::CommandNotFound(command.to_string()));
}
self.runtime.exec(command, args).await
}
pub async fn read_file(&self, path: &str) -> Result<Vec<u8>> {
self.check_destroyed()?;
let full_path = fs::validate_path(&self.config.work_dir, path)?;
let content = tokio::fs::read(&full_path).await?;
Ok(content)
}
pub async fn write_file(&self, path: &str, contents: &[u8]) -> Result<()> {
self.check_destroyed()?;
let full_path = fs::validate_path(&self.config.work_dir, path)?;
if let Some(parent) = full_path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(&full_path, contents).await?;
Ok(())
}
pub async fn list_dir(&self, path: &str) -> Result<Vec<DirEntry>> {
self.check_destroyed()?;
let full_path = fs::validate_path(&self.config.work_dir, path)?;
let mut entries = Vec::new();
let mut rd = tokio::fs::read_dir(&full_path).await?;
while let Some(entry) = rd.next_entry().await? {
let metadata = entry.metadata().await?;
entries.push(DirEntry {
name: entry.file_name().to_string_lossy().to_string(),
is_dir: metadata.is_dir(),
is_file: metadata.is_file(),
size: metadata.len(),
});
}
entries.sort_by(|a, b| a.name.cmp(&b.name));
Ok(entries)
}
pub async fn diff(&self) -> Result<Vec<FsChange>> {
self.check_destroyed()?;
let overlay = self.overlay.lock().await;
match overlay.as_ref() {
Some(o) => o.diff(),
None => Err(SandboxError::Destroyed),
}
}
pub async fn destroy(&self) -> Result<()> {
self.destroyed
.store(true, std::sync::atomic::Ordering::SeqCst);
let mut overlay = self.overlay.lock().await;
*overlay = None;
Ok(())
}
fn check_destroyed(&self) -> Result<()> {
if self.destroyed.load(std::sync::atomic::Ordering::SeqCst) {
Err(SandboxError::Destroyed)
} else {
Ok(())
}
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub is_dir: bool,
pub is_file: bool,
pub size: u64,
}