use std::future::Future;
use std::path::{Component, Path, PathBuf};
use std::pin::Pin;
use std::sync::Arc;
use crate::agent::client::AgentProcess;
use crate::log::Logger;
use crate::log::fields;
use crate::sandbox::backend::CapabilityReport;
use crate::sandbox::backend::SandboxLaunch;
use crate::sandbox::backend::SandboxLaunchError;
use crate::sandbox::backend::SandboxUnavailableError;
use crate::session::manager::SandboxPool;
use crate::session::session::RunningBox;
pub mod backend;
pub mod bailey;
pub mod broker;
pub mod paths;
pub mod podman;
pub mod policy;
pub mod runtime;
pub mod spawn;
#[derive(Debug, Clone)]
pub struct RunResult {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub(crate) type RunFuture<T> = Pin<Box<dyn Future<Output = std::io::Result<T>> + Send>>;
pub(crate) type Run =
Arc<dyn Fn(Vec<String>, Option<String>) -> RunFuture<RunResult> + Send + Sync>;
pub struct BaileyStop {
pub session_id: String,
pub name: String,
pub project_path: String,
pub spawned: Arc<spawn::SpawnedAgent>,
pub policy: String,
pub run: Run,
pub grace_ms: u64,
pub log: Logger,
pub stopped: std::sync::atomic::AtomicBool,
}
pub struct PodmanStop {
pub session_id: String,
pub name: String,
pub project_path: String,
pub spawned: Arc<spawn::SpawnedAgent>,
pub run: Run,
pub grace_ms: u64,
pub log: Logger,
pub stopped: std::sync::atomic::AtomicBool,
}
pub enum SandboxHandle {
Bailey(Box<BaileyStop>),
Podman(Box<PodmanStop>),
}
impl SandboxHandle {
pub fn project_path(&self) -> String {
match self {
SandboxHandle::Bailey(stop) => stop.project_path(),
SandboxHandle::Podman(stop) => stop.project_path(),
}
}
pub fn to_host_path(&self, agent_path: &str) -> Option<String> {
let project = self.project_path();
paths::host_path_under(backend::WORKSPACE_PATH, &project, agent_path)
}
pub fn process(&self) -> Arc<dyn AgentProcess> {
match self {
SandboxHandle::Bailey(stop) => Arc::clone(&stop.spawned.process),
SandboxHandle::Podman(stop) => Arc::clone(&stop.spawned.process),
}
}
pub async fn stop(&self) -> bool {
match self {
SandboxHandle::Bailey(stop) => stop.stop().await,
SandboxHandle::Podman(stop) => stop.stop().await,
}
}
}
impl BaileyStop {
fn project_path(&self) -> String {
self.project_path.clone()
}
async fn stop(&self) -> bool {
if self
.stopped
.swap(true, std::sync::atomic::Ordering::Relaxed)
{
return false;
}
self.spawned.kill(nix::sys::signal::Signal::SIGTERM);
let exited = tokio::time::timeout(
std::time::Duration::from_millis(self.grace_ms),
self.spawned.process.exited(),
)
.await;
let mut killed = false;
match exited {
Ok(_code) => {}
Err(_elapsed) => {
self.spawned.kill(nix::sys::signal::Signal::SIGKILL);
killed = true;
self.log.warn(
"confined process did not stop and was killed",
&fields([
("session", self.session_id.as_str().into()),
("name", self.name.as_str().into()),
]),
);
}
}
let policy = self.policy.clone();
let run = Arc::clone(&self.run);
let _ = run(vec!["untrust".to_owned(), policy], None).await;
killed
}
}
impl PodmanStop {
fn project_path(&self) -> String {
self.project_path.clone()
}
async fn stop(&self) -> bool {
if self
.stopped
.swap(true, std::sync::atomic::Ordering::Relaxed)
{
return false;
}
let grace_seconds = (self.grace_ms / 1000).max(1);
let name = self.name.clone();
let result = (self.run)(
vec![
"stop".to_owned(),
"--time".to_owned(),
grace_seconds.to_string(),
name.clone(),
],
None,
)
.await
.unwrap_or_else(|error| RunResult {
code: -1,
stdout: String::new(),
stderr: error.to_string(),
});
if result.code == 0 {
self.spawned.kill(nix::sys::signal::Signal::SIGKILL);
return false;
}
let _ = (self.run)(
vec!["rm".to_owned(), "--force".to_owned(), name.clone()],
None,
)
.await;
self.spawned.kill(nix::sys::signal::Signal::SIGKILL);
self.log.warn(
"container did not stop and was killed",
&fields([
("session", self.session_id.as_str().into()),
("name", self.name.as_str().into()),
]),
);
true
}
}
pub enum Backend {
Bailey(Arc<bailey::BaileySandbox>),
Podman(Arc<podman::PodmanSandbox>),
}
impl Backend {
pub async fn probe(
&self,
) -> Result<backend::CapabilityReport, backend::SandboxUnavailableError> {
match self {
Backend::Bailey(bailey) => bailey.probe().await,
Backend::Podman(podman) => podman.probe().await,
}
}
pub async fn launch(
&self,
launch: &backend::SandboxLaunch,
) -> Result<SandboxHandle, backend::SandboxLaunchError> {
match self {
Backend::Bailey(bailey) => bailey.launch(launch).await,
Backend::Podman(podman) => podman.launch(launch),
}
}
pub async fn list_orphans(&self) -> Result<Vec<String>, backend::SandboxLaunchError> {
match self {
Backend::Bailey(bailey) => Ok(bailey.list_orphans()),
Backend::Podman(podman) => podman.list_orphans().await,
}
}
pub async fn remove_orphans(&self, names: &[String]) -> usize {
match self {
Backend::Bailey(bailey) => bailey.remove_orphans(names),
Backend::Podman(podman) => podman.remove_orphans(names).await,
}
}
}
pub(crate) fn resolve_root(path: &str) -> String {
let path = Path::new(path);
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
match std::env::current_dir() {
Ok(cwd) => cwd.join(path),
Err(_) => path.to_path_buf(),
}
};
let mut parts: Vec<Component> = Vec::new();
for component in absolute.components() {
match component {
Component::CurDir => {}
Component::ParentDir => match parts.last() {
Some(Component::RootDir) | None => {}
Some(Component::Normal(_)) => {
parts.pop();
}
Some(_) => parts.push(component),
},
other => parts.push(other),
}
}
let mut resolved = PathBuf::new();
for part in parts {
resolved.push(part.as_os_str());
}
resolved.to_string_lossy().into_owned()
}
impl SandboxPool for Backend {
fn probe(
&self,
) -> Pin<Box<dyn Future<Output = Result<CapabilityReport, SandboxUnavailableError>> + Send + '_>>
{
Box::pin(self.probe())
}
fn launch(
self: Arc<Self>,
launch: SandboxLaunch,
) -> Pin<Box<dyn Future<Output = Result<RunningBox, SandboxLaunchError>> + Send>> {
let launch = launch;
Box::pin(async move {
let handle = std::sync::Arc::new(Self::launch(&self, &launch).await?);
Ok(RunningBox {
process: handle.process(),
to_host_path: Arc::new({
let handle = std::sync::Arc::clone(&handle);
move |path: &str| handle.to_host_path(path)
}),
stop: Arc::new({
let handle = std::sync::Arc::clone(&handle);
move || {
let handle = std::sync::Arc::clone(&handle);
Box::pin(async move { handle.stop().await })
as Pin<Box<dyn Future<Output = bool> + Send>>
}
}),
})
})
}
fn list_orphans(&self) -> Pin<Box<dyn Future<Output = Vec<String>> + Send + '_>> {
Box::pin(async move { Self::list_orphans(self).await.unwrap_or_default() })
}
fn remove_orphans<'a>(
&'a self,
names: &'a [String],
) -> Pin<Box<dyn Future<Output = usize> + Send + 'a>> {
Box::pin(async move { Self::remove_orphans(self, names).await })
}
}