From 26d8727db7c18997a2133b574eb955652472b196 Mon Sep 17 00:00:00 2001 From: MeiK Date: Thu, 5 Nov 2020 16:02:31 +0800 Subject: [PATCH] add language: java --- Cargo.toml | 2 + build.rs | 1 + example/java/1000/Main.java | 5 ++ example/java/1000/in.txt | 0 example/java/1000/out.txt | 1 + example/java/1001/Main.java | 12 +++ example/java/1001/in.txt | 1 + example/java/1001/out.txt | 1 + example/main.py | 15 +++- src/allow.rs | 16 ++++ src/judger.rs | 16 +++- src/memory.c | 160 ++++++++++++++++++++++++++++++++++++ src/result.rs | 23 +++++- src/runner.rs | 138 +++++++++++++++---------------- 14 files changed, 313 insertions(+), 78 deletions(-) create mode 100644 example/java/1000/Main.java create mode 100644 example/java/1000/in.txt create mode 100644 example/java/1000/out.txt create mode 100644 example/java/1001/Main.java create mode 100644 example/java/1001/in.txt create mode 100644 example/java/1001/out.txt create mode 100644 src/memory.c diff --git a/Cargo.toml b/Cargo.toml index 82040de..1bc54e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ tempfile = "3" libc = "0.2.76" log = "0.4.0" env_logger = "0.8.1" +nix = "0.19.0" [build-dependencies] tonic-build ={ version = "0.3" } +cc = "1.0" \ No newline at end of file diff --git a/build.rs b/build.rs index fd7220e..267430b 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ fn main() -> Result<(), Box> { tonic_build::compile_protos("proto/river.proto")?; + cc::Build::new().file("src/memory.c").compile("memory"); Ok(()) } diff --git a/example/java/1000/Main.java b/example/java/1000/Main.java new file mode 100644 index 0000000..503e72c --- /dev/null +++ b/example/java/1000/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} \ No newline at end of file diff --git a/example/java/1000/in.txt b/example/java/1000/in.txt new file mode 100644 index 0000000..e69de29 diff --git a/example/java/1000/out.txt b/example/java/1000/out.txt new file mode 100644 index 0000000..c57eff5 --- /dev/null +++ b/example/java/1000/out.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/example/java/1001/Main.java b/example/java/1001/Main.java new file mode 100644 index 0000000..3ac0c0b --- /dev/null +++ b/example/java/1001/Main.java @@ -0,0 +1,12 @@ +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner reader = new Scanner(System.in); + int a, b; + a = reader.nextInt(); + b = reader.nextInt(); + System.out.println(a + b); + reader.close(); + } +} \ No newline at end of file diff --git a/example/java/1001/in.txt b/example/java/1001/in.txt new file mode 100644 index 0000000..1ed0fb7 --- /dev/null +++ b/example/java/1001/in.txt @@ -0,0 +1 @@ +2 3 \ No newline at end of file diff --git a/example/java/1001/out.txt b/example/java/1001/out.txt new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/example/java/1001/out.txt @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/example/main.py b/example/main.py index 62f184a..9fa84dc 100644 --- a/example/main.py +++ b/example/main.py @@ -13,6 +13,8 @@ def judge(path, language): filename = "main.cpp" elif language == river_pb2.Python: filename = "main.py" + elif language == river_pb2.Java: + filename = "Main.java" with open(path.joinpath(filename), "rb") as fr: code = fr.read() with open(path.joinpath("in.txt"), "rb") as fr: @@ -25,19 +27,30 @@ def judge(path, language): judge_type=river_pb2.Standard, compile_data=river_pb2.CompileData(code=code), ) + # if language == river_pb2.Java: + # import time + # time.sleep(1000) # judge yield river_pb2.JudgeRequest( language=language, judge_type=river_pb2.Standard, judge_data=river_pb2.JudgeData( - in_data=in_data, out_data=out_data, time_limit=1000, memory_limit=65535 + in_data=in_data, out_data=out_data, time_limit=10000, memory_limit=65535 ), ) + # if language == river_pb2.Java: + # import time + # time.sleep(1000) def run(): with grpc.insecure_channel("localhost:4003") as channel: stub = river_pb2_grpc.RiverStub(channel) + for path in Path("java").iterdir(): + print(f"开始评测 {path}") + for item in stub.Judge(judge(path, river_pb2.Java)): + print(item) + print(f"{path} 评测完成") for path in Path("c").iterdir(): print(f"开始评测 {path}") for item in stub.Judge(judge(path, river_pb2.C)): diff --git a/src/allow.rs b/src/allow.rs index c9896d2..ff49fb7 100644 --- a/src/allow.rs +++ b/src/allow.rs @@ -7,17 +7,22 @@ pub fn gen_rules() -> Vec { allow_syscall(libc::SYS_arch_prctl), // allow_syscall(libc::SYS_brk), allow_syscall(libc::SYS_chmod), + allow_syscall(libc::SYS_clock_adjtime), + allow_syscall(libc::SYS_clock_getres), allow_syscall(libc::SYS_clock_gettime), allow_syscall(libc::SYS_clone), allow_syscall(libc::SYS_close), + allow_syscall(libc::SYS_connect), allow_syscall(libc::SYS_dup), allow_syscall(libc::SYS_dup2), allow_syscall(libc::SYS_execve), allow_syscall(libc::SYS_exit), allow_syscall(libc::SYS_exit_group), + allow_syscall(libc::SYS_fchdir), allow_syscall(libc::SYS_fcntl), allow_syscall(libc::SYS_fork), allow_syscall(libc::SYS_fstat), + allow_syscall(libc::SYS_ftruncate), allow_syscall(libc::SYS_futex), allow_syscall(libc::SYS_getcwd), allow_syscall(libc::SYS_getdents), @@ -25,6 +30,8 @@ pub fn gen_rules() -> Vec { allow_syscall(libc::SYS_geteuid), allow_syscall(libc::SYS_getgid), allow_syscall(libc::SYS_getpid), + allow_syscall(libc::SYS_getsockname), + allow_syscall(libc::SYS_getsockopt), allow_syscall(libc::SYS_gettid), allow_syscall(libc::SYS_getrandom), allow_syscall(libc::SYS_getrlimit), @@ -33,12 +40,16 @@ pub fn gen_rules() -> Vec { allow_syscall(libc::SYS_ioctl), allow_syscall(libc::SYS_lseek), allow_syscall(libc::SYS_lstat), + allow_syscall(libc::SYS_madvise), // allow_syscall(libc::SYS_mmap), allow_syscall(libc::SYS_mkdir), allow_syscall(libc::SYS_mprotect), // allow_syscall(libc::SYS_munmap), + allow_syscall(libc::SYS_nanosleep), allow_syscall(libc::SYS_open), allow_syscall(libc::SYS_openat), + allow_syscall(libc::SYS_prctl), + allow_syscall(libc::SYS_pread64), allow_syscall(libc::SYS_prlimit64), allow_syscall(libc::SYS_read), allow_syscall(libc::SYS_readlink), @@ -46,10 +57,15 @@ pub fn gen_rules() -> Vec { allow_syscall(libc::SYS_rt_sigaction), allow_syscall(libc::SYS_rt_sigprocmask), allow_syscall(libc::SYS_rt_sigreturn), + allow_syscall(libc::SYS_sched_getaffinity), + allow_syscall(libc::SYS_sched_yield), allow_syscall(libc::SYS_select), allow_syscall(libc::SYS_set_robust_list), allow_syscall(libc::SYS_set_tid_address), + allow_syscall(libc::SYS_setsockopt), allow_syscall(libc::SYS_sigaltstack), + allow_syscall(libc::SYS_socket), + allow_syscall(libc::SYS_socketpair), allow_syscall(libc::SYS_stat), allow_syscall(libc::SYS_sysinfo), allow_syscall(libc::SYS_umask), diff --git a/src/judger.rs b/src/judger.rs index 5706824..6c7e8e9 100644 --- a/src/judger.rs +++ b/src/judger.rs @@ -44,7 +44,7 @@ pub async fn judger( Some(Language::Node) => "node main.js", Some(Language::TypeScript) => "node main.js", Some(Language::Go) => "./a.out", - Some(Language::Java) => "Main.java", + Some(Language::Java) => "/usr/bin/java -cp . Main", None => return Err(Error::LanguageNotFound(request.language)), }; let process = Process::new( @@ -54,9 +54,15 @@ pub async fn judger( data.time_limit, data.memory_limit, )?; + debug!("run command: {}", cmd); // 开始执行并等待返回结果 - let runner = process.runner.clone(); + let mut runner = process.runner.clone(); + // 为 Java 虚拟机取消内存限制和 trace(万恶的 JVM) + if request.language == Language::Java as i32 { + runner.memory_limit = -1; + runner.traceme = false; + } let status = runner.await?; resp.set_process_status(&status); @@ -126,7 +132,7 @@ pub async fn compile( Some(Language::Node) => "/usr/bin/node /plugins/js/validate.js main.js", Some(Language::TypeScript) => "/usr/bin/tsc main.ts", Some(Language::Go) => "/usr/bin/go build -ldflags \"-s -w\" main.go", - Some(Language::Java) => "/usr/bin/java -cp . Main", + Some(Language::Java) => "/usr/bin/javac Main.java", None => return Err(Error::LanguageNotFound(request.language)), }; debug!("build command: {}", cmd); @@ -142,6 +148,10 @@ pub async fn compile( let mut runner = process.runner.clone(); runner.traceme = false; + // 为 Java 虚拟机取消内存限制(万恶的 JVM) + if request.language == Language::Java as i32 { + runner.memory_limit = -1; + } let status = runner.await?; resp.set_process_status(&status); if status.exit_code != 0 || status.signal != 0 { diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..822076d --- /dev/null +++ b/src/memory.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include + +/** + * in: ' 42964 kB' + * out: 42964 + * */ +int GetNumByVmLine(char body[]) +{ + int offset, ans, start; + offset = ans = 0; + start = 0; // FALSE on C + while (1) + { + if (!start) + { + if (isdigit(body[offset])) + { + start = 1; // TRUE on C + } + else + { + offset++; + continue; + } + } + if (start) + { + if (isdigit(body[offset])) + { + ans *= 10; + ans += (int)(body[offset] - '0'); + offset++; + } + else + { + break; + } + } + } + return ans; +} + +long MemoryUsage(int fd) +{ + int i; + ssize_t len; + char body[4096]; + long vm_data = 0, vm_stk = 0; + + if ((len = pread(fd, body, 4096, 0)) == -1) + { + return -1; + } + + for (i = 0; i < len; i++) + { + switch (body[i]) + { + case 'V': + goto V; + default: + goto NEXTLINE; + } + V: + i++; + switch (body[i]) + { + case 'm': + goto Vm; + default: + goto NEXTLINE; + } + Vm: + i++; + switch (body[i]) + { + case 'R': + i += 2; + goto VmRSS; + case 'D': + i += 3; + goto VmData; + case 'S': + goto VmS; + case 'E': + i += 2; + goto VmExe; + case 'L': + goto VmL; + default: + goto NEXTLINE; + } + + VmRSS: + i += 2; + // vm_rss = GetNumByVmLine(body + i); + goto NEXTLINE; + + VmData: + i += 2; + vm_data = GetNumByVmLine(body + i); + goto NEXTLINE; + + VmS: + i++; + switch (body[i]) + { + case 't': + i++; + goto VmStk; + case 'i': + i += 2; + goto VmSize; + default: + goto NEXTLINE; + } + + VmStk: + i += 2; + vm_stk = GetNumByVmLine(body + i); + goto NEXTLINE; + + VmSize: + i += 2; + // vm_size = GetNumByVmLine(body + i); + goto NEXTLINE; + + VmExe: + i += 2; + // vm_exe = GetNumByVmLine(body + i); + goto NEXTLINE; + + VmL: + i++; + switch (body[i]) + { + case 'i': + i++; + goto VmLib; + } + + VmLib: + i += 2; + // vm_lib = GetNumByVmLine(body + i); + goto NEXTLINE; + + NEXTLINE: + while (body[i] != '\n') + { + i++; + } + } + + return vm_data + vm_stk; +} diff --git a/src/result.rs b/src/result.rs index 050438a..3021912 100644 --- a/src/result.rs +++ b/src/result.rs @@ -8,7 +8,7 @@ pub fn standard_result(out: &[u8], ans: &[u8]) -> Result { let mut ans_offset = 0; // 没有 PE,PE 直接 WA let mut r = JudgeResult::Accepted; - while out_offset < out_len && ans_offset < ans_len { + while out_offset <= out_len && ans_offset <= ans_len { let (out_start, out_end, out_exists) = next_line(&out, out_offset, out_len); let (ans_start, ans_end, ans_exists) = next_line(&ans, ans_offset, ans_len); if !out_exists || !ans_exists { @@ -159,4 +159,25 @@ mod tests { let out: &[u8] = "Hello World!\t\t\t\t\n\n\n\n \n\n\n\n\t\t\t\t".as_bytes(); assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted); } + + #[test] + fn test8() { + let ans: &[u8] = "Hello World!".as_bytes(); + let out: &[u8] = "".as_bytes(); + assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::WrongAnswer); + } + + #[test] + fn test9() { + let ans: &[u8] = "".as_bytes(); + let out: &[u8] = "".as_bytes(); + assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted); + } + + #[test] + fn test10() { + let ans: &[u8] = "Hello World!".as_bytes(); + let out: &[u8] = "Hello World!\n".as_bytes(); + assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted); + } } diff --git a/src/runner.rs b/src/runner.rs index bdf36f1..f99e4f4 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,23 +1,30 @@ +extern crate nix; + use super::allow::{gen_rules, trace_syscall}; use super::config::{STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME}; use super::error::{errno_str, Error, Result}; use super::exec_args::ExecArgs; use super::seccomp::*; +use nix::unistd::close; use std::convert::TryInto; use std::env; use std::ffi::CString; -use std::fs::File; +use std::fs::{remove_file, File}; use std::future::Future; -use std::io::{self, prelude::*, BufReader}; -use std::path::PathBuf; +use std::io; +use std::os::unix::io::IntoRawFd; +use std::path::{Path, PathBuf}; use std::pin::Pin; use std::ptr; -use std::sync::mpsc; -use std::sync::{Arc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::task::{Context, Poll}; use std::thread; use std::time::SystemTime; +extern "C" { + fn MemoryUsage(fd: i32) -> i64; +} + #[derive(Clone)] pub struct Runner { pub pid: i32, @@ -161,12 +168,18 @@ impl Runner { unsafe { // 重定向文件描述符 dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644); + if Path::new(STDOUT_FILENAME).exists() { + remove_file(STDOUT_FILENAME).unwrap(); + } dup( STDOUT_FILENAME, libc::STDOUT_FILENO, libc::O_CREAT | libc::O_RDWR, 0o644, ); + if Path::new(STDERR_FILENAME).exists() { + remove_file(STDERR_FILENAME).unwrap(); + } dup( STDERR_FILENAME, libc::STDERR_FILENO, @@ -180,7 +193,7 @@ impl Runner { panic!("How dare you!"); } // CPU 时间限制,粒度为 S - rl.rlim_cur = (self.time_limit / 1000 + 1) as u64; + rl.rlim_cur = (self.time_limit as u64) / 1000 + 1; rl.rlim_max = rl.rlim_cur + 1; if libc::setrlimit(libc::RLIMIT_CPU, &rl) != 0 { eprintln!("setrlimit failure!"); @@ -188,17 +201,19 @@ impl Runner { panic!("How dare you!"); } // 设置内存限制 - rl.rlim_cur = (self.memory_limit * 1024) as u64; - rl.rlim_max = rl.rlim_cur + 1024; - if libc::setrlimit(libc::RLIMIT_DATA, &rl) != 0 { - eprintln!("setrlimit failure!"); - eprintln!("{:?}", io::Error::last_os_error().raw_os_error()); - panic!("How dare you!"); - } - if libc::setrlimit(libc::RLIMIT_AS, &rl) != 0 { - eprintln!("setrlimit failure!"); - eprintln!("{:?}", io::Error::last_os_error().raw_os_error()); - panic!("How dare you!"); + if self.memory_limit > 0 { + rl.rlim_cur = (self.memory_limit as u64) * 1024; + rl.rlim_max = rl.rlim_cur + 1024; + if libc::setrlimit(libc::RLIMIT_DATA, &rl) != 0 { + eprintln!("setrlimit failure!"); + eprintln!("{:?}", io::Error::last_os_error().raw_os_error()); + panic!("How dare you!"); + } + if libc::setrlimit(libc::RLIMIT_AS, &rl) != 0 { + eprintln!("setrlimit failure!"); + eprintln!("{:?}", io::Error::last_os_error().raw_os_error()); + panic!("How dare you!"); + } } if self.traceme { // 设置 trace 模式 @@ -229,6 +244,7 @@ impl Runner { } SeccompFilter::apply(filter.try_into().unwrap()).unwrap(); libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp); + libc::kill(libc::getpid(), libc::SIGKILL); } panic!("How dare you!"); } @@ -267,6 +283,17 @@ impl Runner { // from: https://www.hackerearth.com/practice/notes/vivekprakash/technical-diving-into-memory-used-by-a-program-in-online-judges/ // VmRSS 为程序当前驻留在物理内存中的大小,对虚拟内存等无效 let mut vm_mem = 0; + let status_file = format!("/proc/{}/status", pid); + let file = match File::open(status_file) { + Ok(val) => val, + Err(_) => { + return judge_error(format!( + "open file `{}` failure!", + format!("/proc/{}/status", pid) + )) + } + }; + let status_fd = file.into_raw_fd(); unsafe { if self.traceme { @@ -283,52 +310,15 @@ impl Runner { libc::ptrace(libc::PTRACE_SYSCALL, pid, 0, 0); libc::waitpid(pid, &mut status, 0); - // 通过 /proc/pid/status 读取内存占用 - let status_file = format!("/proc/{}/status", pid); - debug!("{}", status_file); - let file = match File::open(status_file) { - Ok(val) => val, - Err(_) => { - return judge_error(format!( - "open file `{}` failure!", - format!("/proc/{}/status", pid) - )) - } - }; - let reader = BufReader::new(file); - let mut vm_data = 0; - let mut vm_stk = 0; - for line in reader.lines() { - let s = match line { - Ok(val) => val, - Err(_) => return judge_error("readline failure!".to_string()), - }; - if s.starts_with("VmData") { - let len = s.len() - 2; - let mem = &s[7..len].trim(); - let mem = match mem.parse::() { - Ok(val) => val, - Err(_) => return judge_error(format!("parse {} failure!", mem)), - }; - if vm_data < mem { - vm_data = mem; - } - debug!("vm_data: {}", vm_data); - } else if s.starts_with("VmStk") { - let len = s.len() - 2; - let mem = &s[6..len].trim(); - let mem = match mem.parse::() { - Ok(val) => val, - Err(_) => return judge_error(format!("parse {} failure!", mem)), - }; - if vm_stk < mem { - vm_stk = mem; - } - debug!("vm_stk: {}", vm_stk); - } + let vmem = MemoryUsage(status_fd); + if vm_mem < vmem { + vm_mem = vmem; } - if vm_mem < vm_stk + vm_data { - vm_mem = vm_stk + vm_data; + // trace 模式下,如果检测到内存已经超出限制,则直接 kill & break + if vm_mem > self.memory_limit.into() { + debug!("MemoryLimitExceeded! break"); + libc::kill(pid, libc::SIGKILL); + break; } debug!("vm_mem: {}", vm_mem); @@ -341,23 +331,25 @@ impl Runner { debug!("exited: {}", libc::WIFEXITED(status)); break; } - debug!("exited: {}", libc::WIFEXITED(status)); + // debug!("exited: {}", libc::WIFEXITED(status)); } } + match close(status_fd) { + Ok(_) => {} + Err(_) => return judge_error("close status file failure!".to_string()), + }; let mut exit_code = 0; - let exited = unsafe { libc::WIFEXITED(status) }; + let exited = libc::WIFEXITED(status); if exited { - exit_code = unsafe { libc::WEXITSTATUS(status) }; + exit_code = libc::WEXITSTATUS(status); } - let signal = unsafe { - if libc::WIFSIGNALED(status) { - libc::WTERMSIG(status) - } else if libc::WIFSTOPPED(status) { - libc::WSTOPSIG(status) - } else { - 0 - } + let signal = if libc::WIFSIGNALED(status) { + libc::WTERMSIG(status) + } else if libc::WIFSTOPPED(status) { + libc::WSTOPSIG(status) + } else { + 0 }; // TODO: 添加 CGroup 的量度 let time_used = rusage.ru_utime.tv_sec * 1000