use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use anyhow::{bail, Context};
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use crate::configuration::SandboxConfiguration;
use crate::result::{ExitStatus, ResourceUsage};
use crate::Result;
extern "C" {
fn wait4(
pid: libc::pid_t,
status: *mut libc::c_int,
options: libc::c_int,
rusage: *mut libc::rusage,
) -> libc::pid_t;
}
pub fn setup_resource_limits(config: &SandboxConfiguration) -> Result<()> {
#[cfg(not(target_os = "macos"))]
{
if let Some(memory_limit) = config.memory_limit {
set_resource_limit(libc::RLIMIT_AS, memory_limit).context("Failed to set RLIMIT_AS")?;
}
}
if let Some(stack_limit) = config.stack_limit {
set_resource_limit(libc::RLIMIT_STACK, stack_limit)
.context("Failed to set RLIMIT_STACK")?;
} else {
set_resource_limit(libc::RLIMIT_STACK, libc::RLIM_INFINITY)
.context("Failed to set RLIMIT_STACK")?;
}
if let Some(time_limit) = config.time_limit {
set_resource_limit(libc::RLIMIT_CPU, time_limit).context("Failed to set RLIMIT_CPU")?;
}
set_resource_limit(libc::RLIMIT_CORE, 0).context("Failed to set RLIMIT_CORE")
}
#[cfg(target_env = "gnu")]
type Resource = u32;
#[cfg(not(target_env = "gnu"))]
type Resource = i32;
fn set_resource_limit(resource: Resource, limit: u64) -> Result<()> {
unsafe {
let rlim = limit as libc::rlim_t;
let mut current_limit: libc::rlimit = std::mem::zeroed();
let code = libc::getrlimit(resource, &mut current_limit);
if code < 0 {
panic!("getrlimit() error: {}", code);
}
let new_limit = libc::rlimit {
rlim_cur: if rlim < current_limit.rlim_max {
rlim
} else {
current_limit.rlim_max
},
rlim_max: if rlim < current_limit.rlim_max {
rlim
} else {
current_limit.rlim_max
},
};
let code = libc::setrlimit(resource, &new_limit);
if code < 0 {
bail!("Error calling setrlimit(): {}", strerror());
} else {
Ok(())
}
}
}
pub fn wait(pid: libc::pid_t) -> Result<(ExitStatus, ResourceUsage)> {
let mut status = 0;
let mut rusage: libc::rusage = unsafe { std::mem::zeroed() };
if unsafe { wait4(pid, &mut status, 0, &mut rusage) } != pid {
bail!("Error waiting for child completion: {}", strerror());
};
let status = if libc::WIFEXITED(status) {
ExitStatus::ExitCode(libc::WEXITSTATUS(status))
} else if libc::WIFSIGNALED(status) {
ExitStatus::Signal(libc::WTERMSIG(status))
} else {
bail!("Child terminated with unknown status");
};
let resource_usage = ResourceUsage {
memory_usage: rusage.ru_maxrss as u64 * 1024,
user_cpu_time: rusage.ru_utime.tv_usec as f64 / 1_000_000.0 + rusage.ru_utime.tv_sec as f64,
system_cpu_time: rusage.ru_stime.tv_usec as f64 / 1_000_000.0
+ rusage.ru_stime.tv_sec as f64,
wall_time_usage: 0.0,
};
Ok((status, resource_usage))
}
pub fn start_wall_time_watcher(limit: u64, child_pid: i32, killed: Arc<AtomicBool>) -> Result<()> {
std::thread::Builder::new()
.name("Wall time watcher".into())
.spawn(move || {
std::thread::sleep(Duration::new(limit, 0));
kill(Pid::from_raw(child_pid), Signal::SIGKILL)
.expect("Error killing child due to wall limit exceeded");
killed.store(true, Ordering::SeqCst);
})
.context("Failed to spawn Wall time watcher thread")?;
Ok(())
}
pub fn strerror() -> &'static str {
nix::errno::Errno::last().desc()
}
#[cfg(unix)]
mod unix {
use std::os::raw::c_char;
extern "C" {
pub fn strsignal(signal: i32) -> *mut c_char;
}
}
pub fn strsignal(signal: i32) -> Option<String> {
#[cfg(unix)]
{
unsafe {
let cstr = std::ffi::CStr::from_ptr(unix::strsignal(signal));
if cstr.is_empty() {
None
} else {
Some(cstr.to_string_lossy().to_string())
}
}
}
#[cfg(not(unix))]
{
None
}
}