diff --git a/Cargo.toml b/Cargo.toml index b3ccdcf..d2a4af7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["judge-core", "judger"] -resolver = "2" \ No newline at end of file +members = ["judge-core", "judger", "runguard"] +resolver = "2" diff --git a/runguard/Cargo.toml b/runguard/Cargo.toml new file mode 100644 index 0000000..973539d --- /dev/null +++ b/runguard/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "runguard" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +libc = "0.2" + +clap = { version = "4", features = ["derive"] } +humantime = "2" diff --git a/runguard/README.md b/runguard/README.md new file mode 100644 index 0000000..03d6527 --- /dev/null +++ b/runguard/README.md @@ -0,0 +1,5 @@ +# runguard + +A Rust version of +[Domjudge runguard](https://github.com/DOMjudge/domjudge/blob/main/judge/runguard.cc) +written in C++. diff --git a/runguard/build.rs b/runguard/build.rs new file mode 100644 index 0000000..2368e89 --- /dev/null +++ b/runguard/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-lib=cgroup"); +} \ No newline at end of file diff --git a/runguard/src/cgroup.rs b/runguard/src/cgroup.rs new file mode 100644 index 0000000..d690fc0 --- /dev/null +++ b/runguard/src/cgroup.rs @@ -0,0 +1,60 @@ +use std::ffi::CString; +use std::fs::File; +use std::io::{self, BufRead}; +use std::os::raw::c_char; + +struct CGroup { + cgroup: *mut libc::c_void, +} + +impl CGroup { + fn new(name: &str) -> Self { + let cgroup_name = CString::new(name).expect("CString::new failed"); + unsafe { + let cgroup = cgroup_new_cgroup(cgroup_name.as_ptr()); + if cgroup.is_null() { + eprintln!("Failed to create new cgroup"); + } else { + println!("Successfully created new cgroup {}", name); + } + CGroup { cgroup } + } + } +} + +extern "C" { + fn cgroup_new_cgroup(name: *const c_char) -> *mut libc::c_void; +} + +fn cgroup_is_v2() -> bool { + let file = match File::open("/proc/mounts") { + Ok(file) => file, + Err(_) => { + eprintln!("Error opening /proc/mounts"); + return false; + } + }; + + let reader = io::BufReader::new(file); + for line in reader.lines() { + if let Ok(line) = line { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 3 && parts[1] == "/sys/fs/cgroup" && parts[2] == "cgroup2" { + return true; + } + } + } + + false +} + +#[test] +fn test_cgroup() { + let _ = CGroup::new("my_cgroup"); + + if cgroup_is_v2() { + println!("cgroup v2 is enabled"); + } else { + println!("cgroup v2 is not enabled"); + } +} diff --git a/runguard/src/cli.rs b/runguard/src/cli.rs new file mode 100644 index 0000000..8d51de4 --- /dev/null +++ b/runguard/src/cli.rs @@ -0,0 +1,102 @@ +use std::path; + +use clap::Parser; + +#[derive(Parser)] +#[command( + override_usage = "runguard [OPTION]... ...", + about = "Run COMMAND with specified options.", + after_help = "Note that root privileges are needed for the `root' and `user' options. \ +If `user' is set, then `group' defaults to the same to prevent security issues, \ +since otherwise the process would retain group root permissions. \ +The COMMAND path is relative to the changed ROOT directory if specified. \ +TIME may be specified as a float; two floats separated by `:' are treated as soft and hard limits. \ +The runtime written to file is that of the last of wall/cpu time options set, \ +and defaults to CPU time when neither is set. \ +When run setuid without the `user' option, the user ID is set to the real user ID." +)] +pub struct Cli { + /// run COMMAND with root directory set to ROOT + #[arg(short, long)] + root: String, + + /// run COMMAND as user with username or ID USER + #[arg(short, long)] + user: String, + + /// run COMMAND under group with name or ID GROUP + #[arg(short, long)] + group: String, + + /// change to directory DIR after setting root directory + #[arg(short = 'd', long, value_name = "DIR")] + chdir: String, + + /// kill COMMAND after TIME wallclock seconds + #[arg(short = 't', long, value_name = "TIME")] + walltime: humantime::Duration, + + /// set maximum CPU time to TIME seconds + #[arg(short = 'C', long, value_name = "TIME")] + cputime: humantime::Duration, + + /// set total memory limit to SIZE kB + #[arg(short = 'm', long, value_name = "SIZE")] + memsize: u64, + + /// set maximum created filesize to SIZE kB + #[arg(short = 'f', long, value_name = "SIZE")] + filesize: u64, + + /// set maximum no. processes to N + #[arg(short = 'p', long, value_name = "N")] + nproc: u64, + + /// use only processor number ID (or set, e.g. \"0,2-3\") + #[arg(short = 'P', long, value_name = "ID")] + cpuset: String, + + /// disable core dumps + #[arg(short = 'c', long)] + no_core: bool, + + /// redirect COMMAND stdout output to FILE + #[arg(short = 'o', long, value_name = "FILE")] + stdout: path::PathBuf, + + /// redirect COMMAND stderr output to FILE + #[arg(short = 'e', long, value_name = "FILE")] + stderr: path::PathBuf, + + /// truncate COMMAND stdout/stderr streams at SIZE kB + #[arg(short, long, value_name = "SIZE")] + streamsize: u64, + + /// preserve environment variables (default only PATH) + #[arg(short = 'E', long)] + environment: String, + + /// write metadata (runtime, exitcode, etc.) to FILE + #[arg(short = 'M', long, value_name = "FILE")] + metadata: path::PathBuf, + + /// process ID of runpipe to send SIGUSR1 signal when + /// timelimit is reached + #[arg(short = 'U', long, value_name = "PID")] + runpipepid: u32, + + /// display some extra warnings and information + #[arg(short, long)] + verbose: bool, + + /// suppress all warnings and verbose output + #[arg(short, long)] + quiet: bool, + + /// output version information and exit + #[arg(long)] + version: bool, + + #[arg(required = true)] + command: Vec, +} diff --git a/runguard/src/main.rs b/runguard/src/main.rs new file mode 100644 index 0000000..45f8e49 --- /dev/null +++ b/runguard/src/main.rs @@ -0,0 +1,8 @@ +use clap::Parser; + +mod cgroup; +mod cli; + +fn main() { + let _ = cli::Cli::parse(); +}