use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use crate::sandbox::linux::proctrack;
#[cfg(target_os = "linux")]
use std::os::fd::{AsRawFd, OwnedFd};
#[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>,
#[cfg(target_os = "linux")]
pub pidfd: Option<OwnedFd>,
}
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(target_os = "linux")]
{
if let Some(ref pfd) = self.pidfd {
let mut pfd_poll = libc::pollfd {
fd: pfd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let r = unsafe { libc::poll(&mut pfd_poll, 1, 0) };
if r <= 0
|| (pfd_poll.revents & (libc::POLLIN | libc::POLLHUP | libc::POLLERR)) == 0
{
return None;
}
}
}
#[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_graceful(&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::SIGTERM);
},
#[cfg(unix)]
Some(KillStrategy::ProcessGroup { pid, pgid, .. }) => unsafe {
libc::kill(-*pgid, libc::SIGTERM);
libc::kill(*pid, libc::SIGTERM);
},
_ => {}
}
}
#[cfg(not(unix))]
{
let _ = self;
}
}
pub fn terminate(&mut self) {
#[cfg(target_os = "linux")]
{
const SYS_PIDFD_SEND_SIGNAL: libc::c_long = 424;
if let Some(ref pfd) = self.pidfd {
unsafe {
libc::syscall(
SYS_PIDFD_SEND_SIGNAL,
pfd.as_raw_fd(),
libc::SIGKILL,
std::ptr::null::<libc::siginfo_t>(),
0u32,
);
}
}
}
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);
}
}
}
#[cfg(target_os = "linux")]
{
if let Some(cg) = self._cgroup.as_ref() {
cg.cleanup();
}
}
}
}
impl Drop for SandboxHandle {
fn drop(&mut self) {
self.terminate();
}
}
#[cfg(windows)]
impl SandboxHandle {
pub fn windows_raw_handles(&self) -> Option<(*mut std::ffi::c_void, *mut std::ffi::c_void)> {
let KillStrategy::JobObject { job, process } = self.strategy.as_ref()?;
Some((process.as_raw_handle(), job.as_raw_handle()))
}
}
#[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
}
}