diff --git a/.gitignore b/.gitignore index a78f13c..121b0c7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ Cargo.lock nohup.out config.yaml +logs diff --git a/Cargo.toml b/Cargo.toml index 4b4c830..93db0f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ tempfile = "3" libc = "0.2" log = "0.4.0" env_logger = "0.8.1" +log4rs = "1" nix = "0.19.0" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" diff --git a/config.template.yaml b/config.template.yaml index dae5493..d52fd49 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -1,7 +1,42 @@ -data_dir: /data +data_dir: /root/river/runtime/data judge_dir: /tmp languages: C: compile_cmd: /usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static code_file: main.c run_cmd: ./a.out + + Cpp: + compile_cmd: /usr/bin/g++ main.cpp -O2 -Wall --static -o a.out --std=gnu++17 + code_file: main.cpp + run_cmd: ./a.out + + Python: + compile_cmd: /usr/bin/python3.8 -m compileall main.py + code_file: main.py + run_cmd: /usr/bin/python3.8 main.py + + Rust: + compile_cmd: /root/.cargo/bin/rustc main.rs -o a.out -C opt-level=2 + code_file: main.rs + run_cmd: ./a.out + + Node: + compile_cmd: /usr/bin/node /plugins/node/validate.js main.js + code_file: main.js + run_cmd: /usr/bin/node main.js + + TypeScript: + compile_cmd: /usr/bin/tsc -p /tsconfig.json + code_file: main.ts + run_cmd: /usr/bin/node main.js + + Go: + compile_cmd: /usr/bin/go build -o a.out main.go + code_file: main.go + run_cmd: ./a.out + + Java: + compile_cmd: /usr/bin/javac Main.java + code_file: Main.java + run_cmd: /usr/bin/java -cp . Main diff --git a/log4rs.yaml b/log4rs.yaml new file mode 100644 index 0000000..e3b847f --- /dev/null +++ b/log4rs.yaml @@ -0,0 +1,16 @@ +refresh_rate: 30 seconds +appenders: + console: + kind: console + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S)} [{h({l})}] {m}{n}" + file: + kind: file + path: "logs/river.log" + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S)} [{l}] {m}{n}" +root: + level: debug + appenders: + - file + - console diff --git a/runtime/Dockerfile b/runtime/Dockerfile index 10b935f..3f51dd0 100644 --- a/runtime/Dockerfile +++ b/runtime/Dockerfile @@ -1,11 +1,37 @@ FROM ubuntu:focal +ENV LANG C.UTF-8 + RUN apt-get update -y +# install gcc g++ RUN apt-get install -y gcc g++ -RUN apt-get install -y software-properties-common && \ - add-apt-repository -y ppa:deadsnakes/ppa && \ - apt-get install -y python3.8 python3-pip +# install python3.8 +RUN apt-get install -y software-properties-common && add-apt-repository -y ppa:deadsnakes/ppa && apt-get install -y python3.8 python3-pip + +# install rust +RUN apt-get install -y curl && curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y +ENV PATH="/root/.cargo/bin:${PATH}" + +# install node +RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && apt-get install -y nodejs + +# node compiler +COPY plugins /plugins +RUN cd /plugins/node && npm install + +# node runtime +COPY node / +RUN cd / && npm install + +# install typescript +RUN npm install -g ts-node typescript + +# install go +RUN add-apt-repository -y ppa:longsleep/golang-backports && apt-get install -y golang-go + +# install openjdk +RUN apt-get install -y default-jdk RUN rm -rf /var/lib/apt/lists/* diff --git a/runtime/build.sh b/runtime/build.sh index 02fd7ee..83b2f57 100755 --- a/runtime/build.sh +++ b/runtime/build.sh @@ -6,3 +6,5 @@ docker stop river docker rm river chmod -R 755 rootfs + +mknod -m 0666 rootfs/dev/null c 1 3 diff --git a/runtime/node/package.json b/runtime/node/package.json new file mode 100644 index 0000000..b36f3a3 --- /dev/null +++ b/runtime/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "river", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/node": "^14.14.6" + } +} \ No newline at end of file diff --git a/runtime/node/tsconfig.json b/runtime/node/tsconfig.json new file mode 100644 index 0000000..732b378 --- /dev/null +++ b/runtime/node/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ESNext", + "skipLibCheck": true, + "strict": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "noErrorTruncation": true, + "allowSyntheticDefaultImports": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + } +} \ No newline at end of file diff --git a/runtime/plugins/node/.gitignore b/runtime/plugins/node/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/runtime/plugins/node/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/runtime/plugins/node/failure.js b/runtime/plugins/node/failure.js new file mode 100644 index 0000000..da01a3b --- /dev/null +++ b/runtime/plugins/node/failure.js @@ -0,0 +1,6 @@ +return 42; // should be inside a function +function f() { + 'use strict'; + var x = 042; + with (z) { } +} \ No newline at end of file diff --git a/runtime/plugins/node/package-lock.json b/runtime/plugins/node/package-lock.json new file mode 100644 index 0000000..64a0d15 --- /dev/null +++ b/runtime/plugins/node/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "js", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } +} \ No newline at end of file diff --git a/runtime/plugins/node/package.json b/runtime/plugins/node/package.json new file mode 100644 index 0000000..5199a21 --- /dev/null +++ b/runtime/plugins/node/package.json @@ -0,0 +1,16 @@ +{ + "name": "js", + "version": "1.0.0", + "description": "```bash cd plugins/js ./validate.js success.js ./validate.js failure.js ```", + "main": "failure.js", + "dependencies": { + "esprima": "^4.0.1" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/runtime/plugins/node/success.js b/runtime/plugins/node/success.js new file mode 100644 index 0000000..de88829 --- /dev/null +++ b/runtime/plugins/node/success.js @@ -0,0 +1 @@ +console.log("Hello World!"); \ No newline at end of file diff --git a/runtime/plugins/node/validate.js b/runtime/plugins/node/validate.js new file mode 100644 index 0000000..5daa8dd --- /dev/null +++ b/runtime/plugins/node/validate.js @@ -0,0 +1,23 @@ +#!/usr/bin/node +const fs = require('fs'); +const esprima = require('esprima'); + +const file = process.argv[2]; +const code = fs.readFileSync(file).toString(); + +const res = esprima.parseScript(code, { tolerant: true }); +if (res.errors.length != 0) { + const split = code.split('\n'); + for (const error of res.errors) { + console.error(split[error.lineNumber - 1]); + for (let i = 1; i < error.column; i++) { + process.stderr.write(' '); + } + console.error('^'); + console.error(`${error.toString()} + at (${file}:${error.lineNumber}:${error.column})`); + console.error('--------------------------------------------------------------------------'); + } + process.exit(1); +} +process.exit(0); diff --git a/src/exec_args.rs b/src/exec_args.rs index d0dffab..39ac465 100644 --- a/src/exec_args.rs +++ b/src/exec_args.rs @@ -1,5 +1,5 @@ use super::error::{Error, Result}; -use std::env; +use std::collections::HashMap; use std::ffi::CString; use std::mem; use std::ptr; @@ -39,17 +39,18 @@ impl ExecArgs { argv_vec.push(ptr::null()); let argv: *const *const libc::c_char = argv_vec.as_ptr() as *const *const libc::c_char; - // env 环境变量传递当前进程环境变量 + // env 传递环境变量 + let mut envs: HashMap<&str, &str> = HashMap::new(); + envs.insert( + "PATH", + "/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ); + envs.insert("HOME", "/tmp"); + // envs.insert("GOCACHE", "/tmp/.cache"); + envs.insert("TERM", "xterm"); let mut envp_vec: Vec<*const libc::c_char> = vec![]; - for (key, value) in env::vars_os() { - let mut key = match key.to_str() { - Some(val) => val.to_string(), - None => return Err(Error::OsStringToStringError(key)), - }; - let value = match value.to_str() { - Some(val) => val.to_string(), - None => return Err(Error::OsStringToStringError(value)), - }; + for (key, value) in envs { + let mut key = String::from(key); key.push_str("="); key.push_str(&value); let cstr = match CString::new(key) { diff --git a/src/judger.rs b/src/judger.rs index 02754a5..37f41a3 100644 --- a/src/judger.rs +++ b/src/judger.rs @@ -75,18 +75,13 @@ pub async fn judge( } else { status.cgroup_memory_used }; - if status.time_used > time_limit.into() { + if status.time_used > time_limit.into() || status.real_time_used as i64 > time_limit.into() { // TLE return Ok(time_limit_exceeded(status.time_used, mem_used)); } else if mem_used > memory_limit.into() { // MLE return Ok(memory_limit_exceeded(status.time_used, mem_used)); } else if status.signal != 0 { - // 因墙上时钟超时被主动中断 - if status.real_time_used as i64 > time_limit.into() { - // TLE - return Ok(time_limit_exceeded(status.time_used, mem_used)); - } // RE return Ok(runtime_error( status.time_used, diff --git a/src/main.rs b/src/main.rs index e26e66e..c8b9b70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,9 @@ #[macro_use] extern crate log; -use env_logger::Env; use futures::StreamExt; use futures_core::Stream; +use log4rs; use river::judge_request::Data; use river::river_server::{River, RiverServer}; use river::{JudgeRequest, JudgeResponse, JudgeResultEnum}; @@ -49,6 +49,7 @@ impl River for RiverService { return; } }; + debug!("{:?}", pwd); // 是否通过编译 let mut compile_success = false; let mut language = String::from(""); @@ -106,11 +107,7 @@ impl River for RiverService { #[tokio::main] async fn main() -> Result<(), Box> { - let env = Env::default() - .filter_or("LOG_LEVEL", "debug,h2=info,hyper=info") - .write_style_or("LOG_STYLE", "always"); - - env_logger::init_from_env(env); + log4rs::init_file("log4rs.yaml", Default::default()).unwrap(); let addr = "0.0.0.0:4003".parse()?; let river = RiverService::default(); diff --git a/src/process.rs b/src/process.rs index 7d84785..a35128e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -294,6 +294,15 @@ unsafe fn security(process: &Process) { ptr::null_mut() )); + // 挂载 /proc 目录,有些语言(比如 rust)依赖此目录 + syscall_or_panic!(libc::mount( + c_str_ptr!("proc"), + c_str_ptr!("runtime/rootfs/proc"), + c_str_ptr!("proc"), + 0, + ptr::null_mut(), + )); + // 挂载运行文件夹,除此目录外程序没有其他目录的写权限 syscall_or_panic!(libc::mount( c_str_ptr!(process.workdir.to_str().unwrap()), @@ -534,4 +543,34 @@ mod tests { assert!(result.real_time_used >= 1000); assert_ne!(result.signal, 0); } + + #[tokio::test] + async fn test_ls() { + let pwd = tempdir_in("/tmp").unwrap(); + let process = Process::new( + String::from("/bin/ls -lah /dev"), + 1000, + 65535, + pwd.path().to_path_buf(), + ); + let result = Runner::from(process).unwrap().await.unwrap(); + assert_eq!(result.signal, 0); + let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap(); + println!("{}", out); + } + + #[tokio::test] + async fn test_dev_null() { + let pwd = tempdir_in("/tmp").unwrap(); + let process = Process::new( + String::from("/bin/cat /dev/null"), + 1000, + 65535, + pwd.path().to_path_buf(), + ); + let result = Runner::from(process).unwrap().await.unwrap(); + assert_eq!(result.signal, 0); + let out = std::fs::read_to_string(pwd.path().join(STDOUT_FILENAME)).unwrap(); + println!("{}", out); + } }