use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use crate::sandbox::linux::proctrack;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, OwnedHandle};
#[derive(Debug, Clone, Copy)]
#[cfg(unix)]
pub enum StdioMode {
Pty { slave_fd: RawFd },
Captured { stdout_w: RawFd, stderr_w: RawFd },
Inherit,
}
#[derive(Debug, Clone, Copy)]
#[cfg(windows)]
pub enum StdioMode {
Inherit,
}
#[derive(Debug, Clone)]
pub struct SpawnOptions {
pub agent_cmd: Vec<String>,
pub cwd: PathBuf,
pub env_extra: HashMap<String, String>,
pub stdio: StdioMode,
}
pub enum KillStrategy {
#[cfg(target_os = "linux")]
PidNsPipe(std::os::unix::io::OwnedFd),
#[cfg(unix)]
ProcessGroup { pid: i32, pgid: i32, sweep: bool },
#[cfg(windows)]
JobObject {
job: OwnedHandle,
process: OwnedHandle,
},
}
pub struct SandboxHandle {
pub root_pid: u32,
pub strategy: Option<KillStrategy>,
#[cfg(target_os = "linux")]
pub _cgroup: Option<crate::sandbox::linux::cgroup::CgroupHandle>,
}
impl SandboxHandle {
pub fn pause(&mut self) {
#[cfg(unix)]
{
match self.strategy.as_ref() {
#[cfg(target_os = "linux")]
Some(KillStrategy::PidNsPipe(_)) => unsafe {
libc::kill(self.root_pid as i32, libc::SIGSTOP);
},
#[cfg(unix)]
Some(KillStrategy::ProcessGroup { pid, pgid, .. }) => unsafe {
libc::kill(-*pgid, libc::SIGSTOP);
libc::kill(*pid, libc::SIGSTOP);
},
None => {}
}
}
}
pub fn resume(&mut self) {
#[cfg(unix)]
{
match self.strategy.as_ref() {
#[cfg(target_os = "linux")]
Some(KillStrategy::PidNsPipe(_)) => unsafe {
libc::kill(self.root_pid as i32, libc::SIGCONT);
},
#[cfg(unix)]
Some(KillStrategy::ProcessGroup { pid, pgid, .. }) => unsafe {
libc::kill(-*pgid, libc::SIGCONT);
libc::kill(*pid, libc::SIGCONT);
},
None => {}
}
}
}
pub fn try_wait(&mut self) -> Option<i32> {
#[cfg(unix)]
{
let pid = self.root_pid as i32;
let mut status = 0i32;
let r = unsafe { libc::waitpid(pid, &mut status, libc::WNOHANG) };
if r == pid {
Some(decode_status(status))
} else if r == 0 {
None
} else if errno() == libc::ECHILD {
Some(-1)
} else {
None
}
}
#[cfg(windows)]
{
windows_try_wait(self.strategy.as_ref())
}
}
pub fn wait(&mut self) -> i32 {
#[cfg(unix)]
{
let pid = self.root_pid as i32;
loop {
let mut status = 0i32;
let r = unsafe { libc::waitpid(pid, &mut status, 0) };
if r == pid {
return decode_status(status);
}
if r < 0 && errno() != libc::EINTR {
return -1;
}
}
}
#[cfg(windows)]
{
windows_wait(self.strategy.as_ref())
}
}
pub fn terminate(&mut self) {
if let Some(strategy) = self.strategy.take() {
match strategy {
#[cfg(target_os = "linux")]
KillStrategy::PidNsPipe(fd) => drop(fd), #[cfg(unix)]
KillStrategy::ProcessGroup { pid, pgid, sweep } => {
unsafe {
libc::kill(-pgid, libc::SIGKILL);
libc::kill(pid, libc::SIGKILL);
}
#[cfg(target_os = "linux")]
if sweep {
proctrack::sweep_reparented(
proctrack::SWEEP_BUDGET_MS,
self.root_pid as i32,
);
}
#[cfg(not(target_os = "linux"))]
let _ = sweep;
}
#[cfg(windows)]
KillStrategy::JobObject { job, process } => {
drop(job);
drop(process);
}
}
}
}
}
impl Drop for SandboxHandle {
fn drop(&mut self) {
self.terminate();
}
}
#[cfg(unix)]
fn decode_status(status: i32) -> i32 {
if libc::WIFEXITED(status) {
libc::WEXITSTATUS(status)
} else if libc::WIFSIGNALED(status) {
-libc::WTERMSIG(status)
} else {
-1
}
}
#[cfg(unix)]
fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[cfg(windows)]
const WINDOWS_WAIT_OBJECT_0: u32 = 0;
#[cfg(windows)]
const WINDOWS_WAIT_FAILED: u32 = 0xffff_ffff;
#[cfg(windows)]
const WINDOWS_INFINITE: u32 = 0xffff_ffff;
#[cfg(windows)]
type WindowsHandle = *mut std::ffi::c_void;
#[cfg(windows)]
#[link(name = "kernel32")]
#[allow(non_snake_case)]
extern "system" {
fn WaitForSingleObject(handle: WindowsHandle, milliseconds: u32) -> u32;
fn GetExitCodeProcess(handle: WindowsHandle, exit_code: *mut u32) -> i32;
}
#[cfg(windows)]
fn windows_exit_code(handle: &OwnedHandle) -> i32 {
let mut code = 1u32;
if unsafe { GetExitCodeProcess(handle.as_raw_handle().cast(), &mut code) } == 0 {
-1
} else {
code as i32
}
}
#[cfg(windows)]
fn windows_try_wait(strategy: Option<&KillStrategy>) -> Option<i32> {
let KillStrategy::JobObject { process, .. } = strategy?;
match unsafe { WaitForSingleObject(process.as_raw_handle().cast(), 0) } {
WINDOWS_WAIT_OBJECT_0 => Some(windows_exit_code(process)),
WINDOWS_WAIT_FAILED => None,
_ => None,
}
}
#[cfg(windows)]
fn windows_wait(strategy: Option<&KillStrategy>) -> i32 {
let Some(KillStrategy::JobObject { process, .. }) = strategy else {
return -1;
};
if unsafe { WaitForSingleObject(process.as_raw_handle().cast(), WINDOWS_INFINITE) }
== WINDOWS_WAIT_OBJECT_0
{
windows_exit_code(process)
} else {
-1
}
}