use std::cmp::max;
use std::path::PathBuf;
use std::slice::Iter;
use log::info;
use nix::libc::STDOUT_FILENO;
use nix::sys::signal::Signal;
use nix::unistd::{isatty, Gid, Uid};
pub use builder::{CatBoxBuilder, CatBoxOptionBuilder};
use crate::cgroup::CatBoxUsage;
use crate::syscall::SyscallFilter;
use crate::utils::mount::MountPoint;
use crate::utils::{MemoryLimitType, TimeLimitType};
use crate::CatBoxError;
mod builder;
pub struct CatBox {
context: Box<dyn CatBoxContext>,
options: Vec<CatBoxOption>,
}
pub trait CatBoxContext {
fn add_result(&mut self, label: &String, result: CatBoxResult) -> bool;
fn report(&self) {
let is_tty = isatty(STDOUT_FILENO).unwrap_or(false);
if is_tty {
self.report_human();
} else {
self.report_json();
}
}
fn report_human(&self);
fn report_json(&self);
}
pub struct CatBoxRunContext {
max_time: TimeLimitType,
max_memory: MemoryLimitType,
sum_time: TimeLimitType,
sum_memory: MemoryLimitType,
results: Vec<CatBoxResult>,
}
pub struct CatBoxCompileContext {
ok: bool,
}
pub struct CatBoxJudgeContext {}
#[derive(Debug, Clone)]
pub struct CatBoxOption {
label: String,
time_limit: TimeLimitType,
memory_limit: MemoryLimitType,
program: String,
arguments: Vec<String>,
uid: Uid,
gid: Gid,
cgroup: String,
process: u64,
ptrace: Option<SyscallFilter>,
stack_size: u64,
chroot: Option<PathBuf>,
cwd: PathBuf,
mounts: Vec<MountPoint>,
env: Vec<(String, String)>,
stdin: Option<String>,
stdout: Option<String>,
stderr: Option<String>,
force: bool,
debug: bool,
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct CatBoxResult {
status: Option<i32>,
signal: Option<Signal>,
time: TimeLimitType,
time_user: TimeLimitType,
time_sys: TimeLimitType,
memory: MemoryLimitType,
}
impl CatBox {
pub fn start(&mut self) -> Result<(), CatBoxError> {
for option in self.options.iter() {
info!("Run catbox with options: {:?}", &option);
let result = crate::run(&option)?;
if !self.context.add_result(&option.label.clone(), result) {
break;
}
}
Ok(())
}
pub fn commands(&self) -> Iter<CatBoxOption> {
self.options.iter()
}
pub fn single(&self) -> Option<&CatBoxOption> {
if self.options.len() == 1 {
self.options.first()
} else {
None
}
}
pub fn report(&self) {
self.context.report();
}
pub fn report_json(&self) {
self.context.report_json();
}
pub fn close(self) {
for option in self.options.into_iter() {
option.close();
}
}
}
impl CatBoxResult {
pub(crate) fn new(status: Option<i32>, signal: Option<Signal>, usage: CatBoxUsage) -> Self {
CatBoxResult {
status,
signal,
time: usage.time(),
time_user: usage.time_user(),
time_sys: usage.time_sys(),
memory: usage.memory(),
}
}
pub fn status(&self) -> &Option<i32> {
&self.status
}
pub fn signal(&self) -> &Option<Signal> {
&self.signal
}
pub fn time(&self) -> TimeLimitType {
self.time
}
pub fn time_user(&self) -> TimeLimitType {
self.time_user
}
pub fn time_sys(&self) -> TimeLimitType {
self.time_sys
}
pub fn memory(&self) -> MemoryLimitType {
self.memory
}
}
impl CatBoxRunContext {
pub fn new() -> Self {
CatBoxRunContext {
max_time: 0,
max_memory: 0,
sum_time: 0,
sum_memory: 0,
results: vec![],
}
}
}
impl CatBoxContext for CatBoxRunContext {
fn add_result(&mut self, _label: &String, result: CatBoxResult) -> bool {
self.max_time = max(self.max_time, result.time);
self.max_memory = max(self.max_memory, result.memory);
self.sum_time += result.time;
self.sum_memory += result.memory;
self.results.push(result);
true
}
fn report_human(&self) {
if self.results.len() == 1 {
let result = self.results.first().unwrap();
let status = result.status().map_or_else(
|| "\x1b[91m×\x1b[39m".to_string(),
|v| format!("\x1b[9{}m{}\x1b[39m", if v == 0 { 2 } else { 1 }, v),
);
let signal = result.signal().map_or_else(
|| "\x1b[92m✓\x1b[39m".to_string(),
|v| format!("\x1b[91m{}\x1b[39m", v),
);
println!();
println!("\x1b[1mStatus\x1b[22m {}", status);
println!("\x1b[1mSignal\x1b[22m {}", signal);
println!("\x1b[1mTime\x1b[22m {} ms", result.time());
println!("\x1b[1mTime user\x1b[22m {} ms", result.time_user());
println!("\x1b[1mTime sys\x1b[22m {} ms", result.time_sys());
println!("\x1b[1mMemory\x1b[22m {} KB", result.memory());
println!();
} else {
todo!()
}
}
fn report_json(&self) {
if self.results.len() == 1 {
let result = self.results.first().unwrap();
let status = result
.status()
.map_or_else(|| "null".to_string(), |v| v.to_string());
let signal = result
.signal()
.map_or_else(|| "null".to_string(), |v| format!("\"{}\"", v));
println!("{{");
println!(" \"ok\": true,");
println!(" \"status\": {},", status);
println!(" \"signal\": {},", signal);
println!(" \"time\": {},", result.time());
println!(" \"time_user\": {},", result.time_user());
println!(" \"time_sys\": {},", result.time_sys());
println!(" \"memory\": {}", result.memory());
println!("}}");
} else {
todo!()
}
}
}
impl CatBoxCompileContext {
fn new() -> Self {
CatBoxCompileContext { ok: true }
}
}
impl CatBoxContext for CatBoxCompileContext {
fn add_result(&mut self, _label: &String, result: CatBoxResult) -> bool {
if self.ok {
if result.status.unwrap_or(1) == 0 {
self.ok = true;
true
} else {
false
}
} else {
false
}
}
fn report_human(&self) {
todo!()
}
fn report_json(&self) {
todo!()
}
}
impl CatBoxContext for CatBoxJudgeContext {
fn add_result(&mut self, _label: &String, result: CatBoxResult) -> bool {
todo!()
}
fn report_human(&self) {
todo!()
}
fn report_json(&self) {
todo!()
}
}