use std::path::Path;
use std::process::Command;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::{current_uid_tasks, spawn_and_wait};
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::{current_uid_tasks, spawn_and_wait};
#[derive(Clone, Copy, Default)]
#[cfg_attr(not(windows), allow(dead_code))]
pub struct RawStdio {
pub stdin: isize,
pub stdout: isize,
pub stderr: isize,
}
#[derive(Clone, Copy)]
pub struct ResolvedLimits {
pub timeout_secs: u64,
#[cfg_attr(windows, allow(dead_code))]
pub cpu_secs: u64,
pub memory_bytes: u64,
#[cfg_attr(windows, allow(dead_code))]
pub fsize_bytes: u64,
#[cfg_attr(windows, allow(dead_code))]
pub nofile: u64,
pub max_processes: u64,
#[cfg_attr(unix, allow(dead_code))]
pub no_net: bool,
}
pub struct Wait {
pub exit_code: i64,
pub signal: Option<i32>,
pub timed_out: bool,
pub cpu_ms: u64,
pub peak_memory_kb: u64,
pub unenforced: Vec<&'static str>,
}
pub fn no_net_shim(exe_dir: &Path) -> Option<std::path::PathBuf> {
let name = if cfg!(target_os = "macos") {
"blocknet.dylib"
} else if cfg!(target_os = "linux") {
"blocknet.so"
} else {
return None; };
let p = exe_dir.join(name);
if p.is_file() { Some(p) } else { None }
}
pub fn preload_env_var() -> Option<&'static str> {
if cfg!(target_os = "macos") {
Some("DYLD_INSERT_LIBRARIES")
} else if cfg!(target_os = "linux") {
Some("LD_PRELOAD")
} else {
None
}
}
pub fn apply_no_net(cmd: &mut Command, exe_dir: &Path) -> bool {
match (preload_env_var(), no_net_shim(exe_dir)) {
(Some(var), Some(shim)) => {
cmd.env(var, shim.to_string_lossy().into_owned());
true
}
_ => false,
}
}
#[cfg_attr(not(windows), allow(dead_code))]
pub fn quote_arg(arg: &std::ffi::OsStr) -> String {
let s = arg.to_string_lossy();
if !s.is_empty() && !s.contains([' ', '\t', '"']) {
return s.into_owned();
}
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
let mut backslashes = 0usize;
for c in s.chars() {
match c {
'\\' => {
backslashes += 1;
out.push('\\');
}
'"' => {
for _ in 0..backslashes {
out.push('\\');
}
backslashes = 0;
out.push('\\');
out.push('"');
}
_ => {
backslashes = 0;
out.push(c);
}
}
}
for _ in 0..backslashes {
out.push('\\');
}
out.push('"');
out
}
#[cfg(test)]
mod quoting_tests {
use super::quote_arg;
use std::ffi::OsStr;
fn q(s: &str) -> String {
quote_arg(OsStr::new(s))
}
#[test]
fn a_plain_argument_is_not_quoted() {
assert_eq!(q("main.py"), "main.py");
}
#[test]
fn backslashes_not_before_a_quote_are_literal() {
assert_eq!(q(r"C:\Temp\main.py"), r"C:\Temp\main.py");
}
#[test]
fn a_space_forces_quoting() {
assert_eq!(q("John Smith"), "\"John Smith\"");
}
#[test]
fn the_backslash_run_before_the_closing_quote_is_doubled() {
assert_eq!(q(r"C:\a b\"), "\"C:\\a b\\\\\"");
}
#[test]
fn an_embedded_quote_is_escaped() {
assert_eq!(q(r#"say "hi""#), r#""say \"hi\"""#);
}
#[test]
fn a_backslash_run_before_an_embedded_quote_is_doubled() {
assert_eq!(q(r#"a\"b"#), r#""a\\\"b""#);
}
#[test]
fn the_empty_argument_survives_as_an_empty_quoted_string() {
assert_eq!(q(""), "\"\"");
}
}