use std::env;
use std::fs;
use std::io::Read;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use serde_json::json;
mod platform;
use platform::ResolvedLimits;
const MAX_OUTPUT_BYTES: u64 = 64 * 1024;
const AS_LIMIT_BYTES: u64 = 2048 * 1024 * 1024 * 1024; const FSIZE_LIMIT_BYTES: u64 = 256 * 1024 * 1024;
const NFILE_LIMIT: u64 = 256;
const DEFAULT_PROCESS_HEADROOM: u64 = 512;
const FALLBACK_NPROC_LIMIT: u64 = 4096;
const MAX_PROCESSES_ENV: &str = "CODECALC_MAX_PROCESSES";
const PROCESS_HEADROOM_ENV: &str = "CODECALC_PROCESS_HEADROOM";
const CPU_GRACE_SECONDS: u64 = 8;
const ENV_ALLOWLIST: &[&str] = &[
"PATH",
"HOME",
"LANG",
"LC_ALL",
"TMPDIR",
"PYTHONUNBUFFERED",
"JAVA_HOME",
"CARGO_HOME",
"RUSTUP_HOME",
"GOPATH",
"GOMODCACHE",
"SystemRoot",
"SYSTEMROOT",
"windir",
"COMSPEC",
"PATHEXT",
"TEMP",
"TMP",
"USERPROFILE",
"APPDATA",
"LOCALAPPDATA",
"NUMBER_OF_PROCESSORS",
"PROCESSOR_ARCHITECTURE",
];
const RUNTIME_PATH_ENV: &str = "CODECALC_RUNTIME_PATH";
const DEFAULT_RUNTIME_PATH: &str = "/usr/local/bin:/usr/bin:/bin";
fn runtime_path() -> String {
env::var(RUNTIME_PATH_ENV)
.ok()
.filter(|s| !s.is_empty())
.or_else(|| env::var("PATH").ok().filter(|s| !s.is_empty()))
.unwrap_or_else(|| DEFAULT_RUNTIME_PATH.to_string())
}
fn nproc_limit() -> u64 {
if let Some(v) = env::var(MAX_PROCESSES_ENV)
.ok()
.and_then(|s| s.parse::<u64>().ok())
{
return v; }
let headroom = env::var(PROCESS_HEADROOM_ENV)
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(DEFAULT_PROCESS_HEADROOM);
match platform::current_uid_tasks() {
Some(n) => n.saturating_add(headroom),
None => FALLBACK_NPROC_LIMIT,
}
}
struct Lang {
name: &'static str,
ext: &'static str,
compile: Option<&'static [&'static str]>,
run: &'static [&'static str],
}
const LANGS: &[Lang] = &[
Lang {
name: "python3",
ext: "py",
compile: None,
run: &["python3", "{file}"],
},
Lang {
name: "node",
ext: "js",
compile: None,
run: &["node", "{file}"],
},
Lang {
name: "bun",
ext: "ts",
compile: None,
run: &["bun", "run", "{file}"],
},
Lang {
name: "deno",
ext: "ts",
compile: None,
run: &["deno", "run", "{file}"],
},
Lang {
name: "typescript",
ext: "ts",
compile: None,
run: &["deno", "run", "{file}"],
},
Lang {
name: "ruby",
ext: "rb",
compile: None,
run: &["ruby", "{file}"],
},
Lang {
name: "php",
ext: "php",
compile: None,
run: &["php", "{file}"],
},
Lang {
name: "perl",
ext: "pl",
compile: None,
run: &["perl", "{file}"],
},
Lang {
name: "lua",
ext: "lua",
compile: None,
run: &["lua", "{file}"],
},
Lang {
name: "tcl",
ext: "tcl",
compile: None,
run: &["tclsh", "{file}"],
},
Lang {
name: "r",
ext: "R",
compile: None,
run: &["Rscript", "{file}"],
},
Lang {
name: "elixir",
ext: "exs",
compile: None,
run: &["elixir", "{file}"],
},
Lang {
name: "erlang",
ext: "erl",
compile: None,
run: &["escript", "{file}"],
},
Lang {
name: "bash",
ext: "sh",
compile: None,
run: &["bash", "{file}"],
},
Lang {
name: "zsh",
ext: "zsh",
compile: None,
run: &["zsh", "{file}"],
},
Lang {
name: "mojo",
ext: "mojo",
compile: None,
run: &["mojo", "run", "{file}"],
},
Lang {
name: "swift",
ext: "swift",
compile: None,
run: &["swift", "{file}"],
},
Lang {
name: "c",
ext: "c",
compile: Some(&["gcc", "-O2", "-o", "{exe}", "{file}"]),
run: &["{exe}"],
},
Lang {
name: "cpp",
ext: "cpp",
compile: Some(&["g++", "-O2", "-o", "{exe}", "{file}"]),
run: &["{exe}"],
},
Lang {
name: "c++",
ext: "cpp",
compile: Some(&["g++", "-O2", "-o", "{exe}", "{file}"]),
run: &["{exe}"],
},
Lang {
name: "rust",
ext: "rs",
compile: Some(&["rustc", "-O", "-o", "{exe}", "{file}"]),
run: &["{exe}"],
},
Lang {
name: "go",
ext: "go",
compile: None,
run: &["go", "run", "{file}"],
},
Lang {
name: "fortran",
ext: "f90",
compile: Some(&["gfortran", "-O2", "-o", "{exe}", "{file}"]),
run: &["{exe}"],
},
Lang {
name: "zig",
ext: "zig",
compile: None,
run: &["zig", "run", "{file}"],
},
Lang {
name: "java",
ext: "java",
compile: None,
run: &["java", "{file}"],
},
Lang {
name: "kotlin",
ext: "kt",
compile: Some(&[
"kotlinc",
"{file}",
"-include-runtime",
"-d",
"{work}/out.jar",
]),
run: &["java", "-jar", "{work}/out.jar"],
},
Lang {
name: "csharp",
ext: "cs",
compile: None,
run: &["dotnet", "run", "{file}"],
},
Lang {
name: "gleam",
ext: "gleam",
compile: None,
run: &[
"bash",
"-c",
"gleam new \"$2/proj\" --name prog --skip-git && cp \"$1\" \"$2/proj/src/prog.gleam\" && cd \"$2/proj\" && gleam run",
"codecalc",
"{file}",
"{work}",
],
},
Lang {
name: "haskell",
ext: "hs",
compile: None,
run: &[
"bash",
"-c",
"f=$(printf %q \"$1\"); e=$(printf %q \"$3\"); nix-shell -p ghc --run \"ghc -O2 -o $e $f && $e\"",
"codecalc",
"{file}",
"{work}",
"{exe}",
],
},
Lang {
name: "sqlite",
ext: "sql",
compile: None,
run: &["sqlite3", ":memory:", ".read {file}"],
},
Lang {
name: "jq",
ext: "jq",
compile: None,
run: &["jq", "-n", "-f", "{file}"],
},
Lang {
name: "awk",
ext: "awk",
compile: None,
run: &["awk", "-f", "{file}"],
},
];
fn canonical(name: &str) -> Option<&'static Lang> {
let n = name.trim().to_lowercase();
LANGS
.iter()
.find(|l| l.name == n)
.or_else(|| match n.as_str() {
"python" | "py" | "python3.14" | "python3.12" => {
LANGS.iter().find(|l| l.name == "python3")
}
"js" | "javascript" | "nodejs" => LANGS.iter().find(|l| l.name == "node"),
"ts" => LANGS.iter().find(|l| l.name == "typescript"),
"cxx" => LANGS.iter().find(|l| l.name == "c++"),
"rscript" => LANGS.iter().find(|l| l.name == "r"),
"sh" | "shell" => LANGS.iter().find(|l| l.name == "bash"),
"cs" | "c#" | "dotnet" => LANGS.iter().find(|l| l.name == "csharp"),
"ghc" | "hs" => LANGS.iter().find(|l| l.name == "haskell"),
_ => None,
})
}
fn on_path(cmd: &str) -> bool {
if cmd.contains('/') || cmd.contains('\\') {
return Path::new(cmd).is_file();
}
let path_var = env::var_os("PATH").unwrap_or_default();
for dir in env::split_paths(&path_var) {
if dir.as_os_str().is_empty() {
continue;
}
for candidate in executable_names(cmd) {
let p = dir.join(&candidate);
if p.is_file() && is_executable(&p) {
return true;
}
}
}
false
}
fn executable_names(cmd: &str) -> Vec<String> {
if !cfg!(windows) {
return vec![cmd.to_string()];
}
let mut names = vec![cmd.to_string()];
let pathext = env::var("PATHEXT").unwrap_or_else(|_| ".COM;.EXE;.BAT;.CMD".into());
for ext in pathext.split(';').filter(|e| !e.is_empty()) {
names.push(format!("{cmd}{}", ext.to_lowercase()));
}
names
}
#[cfg(unix)]
fn is_executable(p: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(p)
.map(|m| m.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(windows)]
fn is_executable(p: &Path) -> bool {
p.is_file()
}
fn first_cmd(template: &[&'static str]) -> &'static str {
template
.iter()
.find(|a| !a.starts_with('{'))
.copied()
.unwrap_or("")
}
fn probe() -> serde_json::Value {
let mut out = serde_json::Map::new();
for lang in LANGS {
let available = if let Some(tool) = wrapped_tool(lang.name) {
plan_supported(lang.name, cfg!(windows)) && on_path(tool) && on_path("bash")
} else {
let cmd = match first_cmd(lang.run) {
"" => first_cmd(lang.compile.unwrap_or(&[])),
c => c,
};
if cmd.is_empty() || cmd == "bash" || cmd == "sh" {
on_path(if cmd.is_empty() { "bash" } else { cmd })
} else {
on_path(cmd)
}
};
out.insert(lang.name.to_string(), json!(available));
}
serde_json::Value::Object(out)
}
#[cfg(unix)]
fn dir_identity(path: &Path) -> Option<(u64, u64)> {
use std::os::unix::fs::MetadataExt;
let md = fs::symlink_metadata(path).ok()?;
if !md.is_dir() {
return None;
}
Some((md.dev(), md.ino()))
}
#[cfg(not(unix))]
fn dir_identity(path: &Path) -> Option<(u64, u64)> {
let _ = path;
None
}
fn remove_own_workdir(work: &Path, created: Option<(u64, u64)>) {
if created.is_some() && dir_identity(work) != created {
eprintln!(
"codecalc-exec: refusing to delete {} — it is not the directory this run created",
work.display()
);
return;
}
let _ = fs::remove_dir_all(work);
}
fn substitute(template: &str, file: &str, exe: &str, work: &str) -> String {
template
.replace("{file}", file)
.replace("{exe}", exe)
.replace("{work}", work)
}
const POSIX_ARGV_LANGUAGES: &[&str] = &["bash", "zsh"];
const SHELL_WRAPPED: &[&str] = &["gleam", "haskell"];
fn wrapped_tool(lang: &str) -> Option<&'static str> {
match lang {
"gleam" => Some("gleam"),
"haskell" => Some("nix-shell"),
_ => None,
}
}
fn plan_supported(lang: &str, windows: bool) -> bool {
!(windows && SHELL_WRAPPED.contains(&lang))
}
fn source_arg<'a>(language: &str, file: &'a str, windows: bool) -> &'a str {
if !windows || !POSIX_ARGV_LANGUAGES.contains(&language) {
return file;
}
match file.rsplit(['\\', '/']).next() {
Some(base) if !base.is_empty() => base,
_ => file,
}
}
#[derive(Clone, Copy)]
struct Limits {
timeout: u64, max_cpu: u64, max_memory_mb: u64, max_output_kb: u64, no_net: bool, }
impl Default for Limits {
fn default() -> Self {
Limits {
timeout: 10,
max_cpu: 0,
max_memory_mb: 0,
max_output_kb: 0,
no_net: false,
}
}
}
struct StepResult {
exit_code: i64,
signal: Option<i32>,
stdout: String,
stderr: String,
timed_out: bool,
cpu_ms: u64,
peak_memory_kb: u64,
output_truncated: bool,
output_error: Option<String>,
unenforced: Vec<&'static str>,
stdout_bytes: Option<u64>,
stderr_bytes: Option<u64>,
}
fn run_step(
argv: &[String],
work: &Path,
tag: &str,
stdin_data: &[u8],
limits: &Limits,
) -> StepResult {
let out_path = work.join(format!("{tag}.out"));
let err_path = work.join(format!("{tag}.err"));
let in_path = work.join(format!("{tag}.in"));
let _ = fs::write(&in_path, stdin_data);
let (out_f, err_f, in_f) = match (
fs::File::create(&out_path),
fs::File::create(&err_path),
fs::File::open(&in_path),
) {
(Ok(o), Ok(e), Ok(i)) => (o, e, i),
_ => {
return StepResult {
exit_code: -2,
signal: None,
stdout: String::new(),
stderr: format!("cannot create I/O files in {}", work.display()),
timed_out: false,
cpu_ms: 0,
peak_memory_kb: 0,
output_truncated: false,
output_error: None,
unenforced: Vec::new(),
stdout_bytes: None,
stderr_bytes: None,
};
}
};
let mut cmd = Command::new(&argv[0]);
cmd.args(&argv[1..])
.current_dir(work)
.env_clear();
for key in ENV_ALLOWLIST {
if let Ok(val) = std::env::var(key) {
cmd.env(key, val);
}
}
#[cfg(windows)]
let raw_stdio = {
use std::os::windows::io::AsRawHandle;
platform::RawStdio {
stdin: in_f.as_raw_handle() as isize,
stdout: out_f.as_raw_handle() as isize,
stderr: err_f.as_raw_handle() as isize,
}
};
#[cfg(not(windows))]
let raw_stdio = platform::RawStdio::default();
cmd.env("PATH", runtime_path()) .env("PYTHONUNBUFFERED", "1")
.stdin(Stdio::from(in_f))
.stdout(Stdio::from(out_f))
.stderr(Stdio::from(err_f));
let mut no_net_applied = true;
if limits.no_net {
let exe_dir = env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_path_buf()))
.unwrap_or_else(|| Path::new(".").to_path_buf());
no_net_applied = platform::apply_no_net(&mut cmd, &exe_dir);
}
let resolved = resolve_limits(limits);
let waited = match platform::spawn_and_wait(cmd, &resolved, raw_stdio) {
Ok(w) => w,
Err(e) => {
return StepResult {
exit_code: -2,
signal: None,
stdout: String::new(),
stderr: format!("spawn failed: {e}"),
timed_out: false,
cpu_ms: 0,
peak_memory_kb: 0,
output_truncated: false,
output_error: None,
unenforced: Vec::new(),
stdout_bytes: None,
stderr_bytes: None,
};
}
};
let mut unenforced = waited.unenforced;
if limits.no_net {
if waited.no_net_seccomp_enforced {
} else if no_net_applied {
unenforced.push("no_net_best_effort_shim");
} else {
unenforced.push("no_net_requested_but_no_shim_available");
}
}
let (stdout, out_trunc, out_err, out_bytes) = read_capped(&out_path, limits.max_output_kb);
let (stderr, err_trunc, err_err, err_bytes) = read_capped(&err_path, limits.max_output_kb);
let output_error = match (out_err, err_err) {
(Some(a), Some(b)) => Some(format!("stdout: {a}; stderr: {b}")),
(Some(a), None) => Some(format!("stdout: {a}")),
(None, Some(b)) => Some(format!("stderr: {b}")),
(None, None) => None,
};
let stderr = if waited.timed_out && stderr.is_empty() {
"<killed: exceeded wall-clock timeout>".to_string()
} else {
stderr
};
StepResult {
exit_code: waited.exit_code,
signal: waited.signal,
stdout,
stderr,
timed_out: waited.timed_out,
cpu_ms: waited.cpu_ms,
peak_memory_kb: waited.peak_memory_kb,
output_truncated: out_trunc || err_trunc,
output_error,
unenforced,
stdout_bytes: out_bytes,
stderr_bytes: err_bytes,
}
}
fn resolve_limits(limits: &Limits) -> ResolvedLimits {
ResolvedLimits {
timeout_secs: limits.timeout,
cpu_secs: if limits.max_cpu > 0 {
limits.max_cpu
} else {
limits.timeout + CPU_GRACE_SECONDS
},
memory_bytes: if limits.max_memory_mb > 0 {
limits.max_memory_mb * 1024 * 1024
} else {
AS_LIMIT_BYTES
},
fsize_bytes: if limits.max_output_kb > 0 {
(limits.max_output_kb * 1024 * 4).clamp(4096, FSIZE_LIMIT_BYTES)
} else {
FSIZE_LIMIT_BYTES
},
nofile: NFILE_LIMIT,
max_processes: nproc_limit(),
no_net: limits.no_net,
}
}
fn read_capped(path: &Path, max_output_kb: u64) -> (String, bool, Option<String>, Option<u64>) {
let cap = if max_output_kb > 0 {
max_output_kb * 1024
} else {
MAX_OUTPUT_BYTES
};
let original = fs::metadata(path).ok().map(|m| m.len());
let mut buf = Vec::new();
let mut error = None;
match fs::File::open(path) {
Ok(mut f) => {
if let Err(e) = (&mut f).take(cap + 1).read_to_end(&mut buf) {
error = Some(format!(
"read {} failed after {} bytes: {e}",
path.display(),
buf.len()
));
}
}
Err(e) => {
error = Some(format!("open {} failed: {e}", path.display()));
}
}
let truncated = buf.len() as u64 > cap;
if truncated {
buf.truncate(cap as usize);
buf.extend_from_slice(b"\n...[truncated]");
}
(
String::from_utf8_lossy(&buf).into_owned(),
truncated,
error,
original,
)
}
fn verdict(sr: &StepResult, limits: &Limits) -> &'static str {
if sr.timed_out {
return "TLE";
}
if sr.output_truncated {
return "OLE";
}
if sr.signal.is_some() {
if limits.max_memory_mb > 0 && sr.peak_memory_kb >= limits.max_memory_mb * 1024 / 2 {
return "MLE";
}
return "RTE";
}
if sr.exit_code != 0 {
return "RTE";
}
"OK"
}
fn remaining_run_timeout_secs(budget_secs: u64, elapsed_ms: u64) -> Option<u64> {
let budget_ms = budget_secs.saturating_mul(1000);
if elapsed_ms >= budget_ms {
return None;
}
Some((budget_ms - elapsed_ms).div_ceil(1000))
}
fn execute(
lang_name: &str,
code: &str,
stdin_data: &str,
limits: &Limits,
workdir: Option<&str>,
) -> serde_json::Value {
let lang = match canonical(lang_name) {
Some(l) => l,
None => {
let known: Vec<&str> = LANGS.iter().map(|l| l.name).collect();
return json!({
"ok": false,
"error": format!("unknown language '{lang_name}'. Available: {}", known.join(", "))
});
}
};
if !plan_supported(lang.name, cfg!(windows)) {
return json!({
"ok": false,
"error": format!(
"'{}' is unsupported on this platform: its plan needs a POSIX \
shell to scaffold a project", lang.name),
});
}
static COUNTER: AtomicU64 = AtomicU64::new(0);
let work = match workdir {
Some(dir) => Path::new(dir).to_path_buf(),
None => {
const MAX_TEMPDIR_ATTEMPTS: u32 = 64;
let mut last_err: Option<std::io::Error> = None;
let mut chosen: Option<std::path::PathBuf> = None;
for _ in 0..MAX_TEMPDIR_ATTEMPTS {
let nonce = COUNTER.fetch_add(1, Ordering::Relaxed)
^ std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| u64::from(d.subsec_nanos()))
.unwrap_or(0);
let candidate =
env::temp_dir().join(format!("codecalc-{}-{nonce:x}", std::process::id()));
match fs::create_dir(&candidate) {
Ok(()) => {
chosen = Some(candidate);
break;
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(e) => {
last_err = Some(e);
break;
}
}
}
match chosen {
Some(dir) => dir,
None => {
let why = last_err.map_or_else(
|| format!("{MAX_TEMPDIR_ATTEMPTS} name collisions in a row"),
|e| e.to_string(),
);
return json!({
"ok": false,
"error": format!("cannot create a work directory in {}: {why}",
env::temp_dir().display()),
});
}
}
}
};
let created_identity = if workdir.is_none() {
dir_identity(&work)
} else {
None
};
let file = work.join(format!("main.{ext}", ext = lang.ext));
let exe = work.join(if cfg!(windows) { "a.exe" } else { "a.out" });
if let Err(e) = fs::write(&file, code) {
if workdir.is_none() {
remove_own_workdir(&work, created_identity);
}
return json!({
"ok": false,
"error": format!("failed to write source to {}: {e}", file.display()),
});
}
let started = Instant::now();
let work_s = work.to_string_lossy().into_owned();
let mut compile_ms: u64 = 0;
if let Some(compile) = lang.compile {
let argv: Vec<String> = compile
.iter()
.map(|t| {
substitute(
t,
source_arg(lang.name, &file.to_string_lossy(), cfg!(windows)),
&exe.to_string_lossy(),
&work_s,
)
})
.collect();
let sr = run_step(&argv, &work, "compile", b"", limits);
compile_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
if sr.timed_out || sr.exit_code != 0 || sr.signal.is_some() {
let result = json!({
"ok": false, "language": lang.name, "phase": "compile",
"stdout": sr.stdout, "stderr": sr.stderr,
"exit_code": if sr.signal.is_some() { serde_json::Value::Null } else { serde_json::Value::from(sr.exit_code) },
"duration_ms": compile_ms, "compile_ms": compile_ms,
"cpu_ms": sr.cpu_ms, "peak_memory_kb": sr.peak_memory_kb,
"timed_out": sr.timed_out, "verdict": verdict(&sr, limits),
"unenforced": sr.unenforced,
"output_error": sr.output_error,
"output_truncated": sr.output_truncated,
"stdout_bytes": sr.stdout_bytes,
"stderr_bytes": sr.stderr_bytes,
"total_ms": u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
"platform": std::env::consts::OS,
"workdir": work_s,
});
if workdir.is_none() {
remove_own_workdir(&work, created_identity);
}
return result;
}
}
let argv: Vec<String> = lang
.run
.iter()
.map(|t| {
substitute(
t,
source_arg(lang.name, &file.to_string_lossy(), cfg!(windows)),
&exe.to_string_lossy(),
&work_s,
)
})
.collect();
let mut run_limits = *limits;
if compile_ms > 0 {
let elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
match remaining_run_timeout_secs(limits.timeout, elapsed_ms) {
Some(secs) => run_limits.timeout = secs,
None => {
let result = json!({
"ok": false, "language": lang.name, "phase": "run",
"stdout": "", "stderr": "<killed: exceeded wall-clock timeout>",
"exit_code": serde_json::Value::Null,
"duration_ms": 0, "compile_ms": compile_ms,
"total_ms": elapsed_ms,
"cpu_ms": 0, "peak_memory_kb": 0,
"timed_out": true, "verdict": "TLE",
"unenforced": Vec::<&str>::new(),
"output_truncated": false,
"output_error": serde_json::Value::Null,
"stdout_bytes": serde_json::Value::Null,
"stderr_bytes": serde_json::Value::Null,
"platform": std::env::consts::OS,
"workdir": work_s,
});
if workdir.is_none() {
remove_own_workdir(&work, created_identity);
}
return result;
}
}
}
let run_started = Instant::now();
let sr = run_step(&argv, &work, "run", stdin_data.as_bytes(), &run_limits);
let duration_ms = u64::try_from(run_started.elapsed().as_millis()).unwrap_or(u64::MAX);
let total_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX);
let result = json!({
"ok": sr.exit_code == 0 && !sr.timed_out && sr.signal.is_none()
&& sr.output_error.is_none(),
"language": lang.name,
"phase": "run",
"stdout": sr.stdout,
"stderr": sr.stderr,
"exit_code": if sr.signal.is_some() { serde_json::Value::Null } else { serde_json::Value::from(sr.exit_code) },
"duration_ms": duration_ms,
"compile_ms": compile_ms,
"total_ms": total_ms,
"cpu_ms": sr.cpu_ms,
"peak_memory_kb": sr.peak_memory_kb,
"timed_out": sr.timed_out,
"verdict": verdict(&sr, &run_limits),
"unenforced": sr.unenforced,
"output_error": sr.output_error,
"output_truncated": sr.output_truncated,
"stdout_bytes": sr.stdout_bytes,
"stderr_bytes": sr.stderr_bytes,
"platform": std::env::consts::OS,
"workdir": work_s,
});
if workdir.is_none() {
remove_own_workdir(&work, created_identity);
}
result
}
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
if args.iter().any(|a| a == "--probe") {
println!("{}", probe());
return;
}
if args.iter().any(|a| a == "--languages") {
let names: Vec<&str> = LANGS.iter().map(|l| l.name).collect();
println!("{}", json!(names));
return;
}
if args.iter().any(|a| a == "--capabilities") {
println!(
"{}",
json!({
"no_net_kernel_enforcement": platform::no_net_kernel_enforcement_available(),
})
);
return;
}
let mut lang = String::new();
let mut stdin_data = String::new();
let mut stdin_file: Option<String> = None;
let mut workdir: Option<String> = None;
let mut limits = Limits::default();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--lang" => {
i += 1;
if i < args.len() {
lang = args[i].clone();
}
}
"--timeout" => {
i += 1;
if i < args.len() {
limits.timeout = args[i].parse().unwrap_or(10);
}
}
"--max-cpu" => {
i += 1;
if i < args.len() {
limits.max_cpu = args[i].parse().unwrap_or(0);
}
}
"--max-memory-mb" => {
i += 1;
if i < args.len() {
limits.max_memory_mb = args[i].parse().unwrap_or(0);
}
}
"--max-output-kb" => {
i += 1;
if i < args.len() {
limits.max_output_kb = args[i].parse().unwrap_or(0);
}
}
"--stdin" => {
i += 1;
if i < args.len() {
stdin_data = args[i].clone();
}
}
"--stdin-file" => {
i += 1;
if i < args.len() {
stdin_file = Some(args[i].clone());
}
}
"--workdir" => {
i += 1;
if i < args.len() {
workdir = Some(args[i].clone());
}
}
"--no-net" => {
limits.no_net = true;
}
_ => {}
}
i += 1;
}
if let Some(path) = stdin_file {
stdin_data = fs::read_to_string(&path).unwrap_or_default();
}
let mut code = String::new();
let _ = std::io::stdin().read_to_string(&mut code);
let result = execute(&lang, &code, &stdin_data, &limits, workdir.as_deref());
println!("{result}");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compile_that_exactly_exhausts_the_budget_gets_no_run() {
assert_eq!(remaining_run_timeout_secs(10, 10_000), None);
}
#[test]
fn compile_that_overruns_the_budget_gets_no_run() {
assert_eq!(remaining_run_timeout_secs(10, 10_500), None);
}
#[test]
fn compile_that_leaves_a_sub_second_remainder_still_gets_one_second() {
assert_eq!(remaining_run_timeout_secs(10, 9_900), Some(1));
}
#[test]
fn compile_using_half_the_budget_leaves_the_other_half() {
assert_eq!(remaining_run_timeout_secs(10, 5_000), Some(5));
}
#[test]
fn posix_argv_languages_get_a_name_with_no_separator_on_windows() {
let win = r"C:\Users\John Smith\AppData\Local\Temp\codecalc-ab12\main.sh";
for lang in POSIX_ARGV_LANGUAGES {
let got = source_arg(lang, win, true);
assert_eq!(got, "main.sh", "{lang} kept a path");
assert!(!got.contains('\\'), "{lang} kept a backslash");
assert!(!got.contains(' '), "{lang} kept a space");
}
}
#[test]
fn a_mixed_separator_path_is_still_reduced_to_the_name() {
assert_eq!(
source_arg("bash", r"C:/Users/me\tmp/main.sh", true),
"main.sh"
);
}
#[test]
fn unix_keeps_the_absolute_path() {
assert_eq!(
source_arg("bash", "/tmp/codecalc-ab12/main.sh", false),
"/tmp/codecalc-ab12/main.sh"
);
}
#[test]
fn a_normal_language_is_untouched_on_windows() {
let win = r"C:\Temp\codecalc-ab12\main.py";
assert_eq!(source_arg("python3", win, true), win);
}
#[test]
fn the_compiled_artifact_keeps_its_absolute_path() {
let out = substitute("{exe}", "main.sh", r"C:\Temp\w\a.exe", r"C:\Temp\w");
assert_eq!(out, r"C:\Temp\w\a.exe");
}
#[test]
fn shell_wrapped_plans_are_unsupported_on_windows() {
for lang in SHELL_WRAPPED {
assert!(!plan_supported(lang, true), "{lang} claimed a Windows plan");
assert!(plan_supported(lang, false), "{lang} lost its POSIX plan");
}
}
#[test]
fn csharp_is_no_longer_shell_wrapped_and_runs_everywhere() {
let lang = canonical("csharp").expect("csharp is registered");
assert_eq!(lang.run[0], "dotnet", "csharp reacquired a wrapper");
assert!(plan_supported("csharp", true));
assert!(!SHELL_WRAPPED.contains(&"csharp"));
}
#[test]
fn every_wrapped_language_names_its_real_tool() {
for lang in SHELL_WRAPPED {
assert!(wrapped_tool(lang).is_some(), "{lang} has no wrapped_tool");
}
assert!(wrapped_tool("python3").is_none());
}
}