From 33f93913d6739dd13b567fe93d45b9d3ec47d5a8 Mon Sep 17 00:00:00 2001 From: MeiK2333 Date: Thu, 14 Jan 2021 17:23:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=96=E8=AF=91=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.template.yaml | 1 + src/config.rs | 23 +++++++-------- src/error.rs | 5 +++- src/judger.rs | 51 +++++++++++++++++++++++++++++++-- src/main.rs | 67 ++++++++++++++++++++++++-------------------- src/process.rs | 46 +++++++++++++++--------------- src/result.rs | 38 +++++++++++++++++++++++-- 7 files changed, 162 insertions(+), 69 deletions(-) diff --git a/config.template.yaml b/config.template.yaml index 5c6c660..dae5493 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -1,4 +1,5 @@ data_dir: /data +judge_dir: /tmp languages: C: compile_cmd: /usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static diff --git a/src/config.rs b/src/config.rs index bb9331d..32118a9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,23 +8,24 @@ pub static STDOUT_FILENAME: &str = "stdout.txt"; pub static STDERR_FILENAME: &str = "stderr.txt"; lazy_static! { - pub static ref CONFIG: Config = { - let config = fs::read_to_string("config.yaml").unwrap(); - let cg: Config = serde_yaml::from_str(&config).unwrap(); - debug!("{:?}", cg); - cg - }; + pub static ref CONFIG: Config = { + let config = fs::read_to_string("config.yaml").unwrap(); + let cfg: Config = serde_yaml::from_str(&config).unwrap(); + debug!("{:?}", cfg); + cfg + }; } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct LanguageConf { - pub compile_cmd: String, - pub code_file: String, - pub run_cmd: String, + pub compile_cmd: String, + pub code_file: String, + pub run_cmd: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Config { - pub data_dir: String, - pub languages: HashMap, + pub data_dir: String, + pub judge_dir: String, + pub languages: HashMap, } diff --git a/src/error.rs b/src/error.rs index 672cf6c..97a930f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,7 @@ pub enum Error { ParseIntError(std::num::ParseIntError), CreateTempDirError(io::Error), CustomError(String), + LanguageNotFound(String), } pub type Result = result::Result; @@ -33,7 +34,9 @@ macro_rules! try_io { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::IOError(ref e) => write!(f, "IOError: {}", errno_str(e.raw_os_error())), + Error::IOError(ref e) => write!(f, "IOError: `{}`", errno_str(e.raw_os_error())), + Error::CustomError(ref e) => write!(f, "Internal Server Error: `{}`", e), + Error::LanguageNotFound(ref e) => write!(f, "Language Not Fount: `{}`", e), _ => write!(f, "{:?}", self), } } diff --git a/src/judger.rs b/src/judger.rs index c64cbfe..0ccff7a 100644 --- a/src/judger.rs +++ b/src/judger.rs @@ -1,12 +1,54 @@ +use crate::config::{CONFIG, STDERR_FILENAME, STDOUT_FILENAME}; +use crate::error::{Error, Result}; +use crate::process::{Process, Runner}; +use crate::result::{compile_error, compile_success}; +use crate::river; +use crate::river::JudgeResponse; +use std::fs; +use std::fs::read_to_string; use std::path::Path; #[cfg(test)] use std::println as debug; -pub async fn compile(language: &str, code: &str, path: &Path) { +pub async fn compile(language: &str, code: &str, path: &Path) -> Result { debug!("language: {}", language); debug!("code: {}", code); debug!("path: {:?}", path); + let lang = match CONFIG.languages.get(language) { + Some(val) => val, + None => return Err(Error::LanguageNotFound(String::from(language))), + }; + debug!("write file to {:?}", path.join(&lang.code_file)); + try_io!(fs::write(path.join(&lang.code_file), &code)); + + debug!("build command: {}", lang.compile_cmd); + let process = Process::new( + String::from(&lang.compile_cmd), + 10000, + 1024 * 1024, + path.to_path_buf(), + ); + let result = Runner::from(process)?; + let status = result.await?; + let mem_used = if status.memory_used < status.cgroup_memory_used { + status.memory_used + } else { + status.cgroup_memory_used + }; + if status.exit_code != 0 || status.signal != 0 { + // 合并 stdout 与 stderr 为 errmsg + // 因为不同的语言、不同的编译器,错误信息输出到了不同的地方 + let errmsg = format!( + "{}\n{}", + try_io!(read_to_string(path.join(STDOUT_FILENAME))), + try_io!(read_to_string(path.join(STDERR_FILENAME))), + ); + debug!("{}", errmsg); + return Ok(compile_error(status.time_used, mem_used, &errmsg)); + } else { + return Ok(compile_success(status.time_used, mem_used)); + } } pub async fn judge( @@ -17,7 +59,7 @@ pub async fn judge( memory_limit: i32, judge_type: i32, path: &Path, -) { +) -> Result { debug!("language: {}", language); debug!("in_file: {}", in_file); debug!("out_file: {}", out_file); @@ -25,4 +67,9 @@ pub async fn judge( debug!("memory_limit: {}", memory_limit); debug!("judge_type: {}", judge_type); debug!("path: {:?}", path); + Ok(JudgeResponse { + state: Some(river::judge_response::State::Status( + river::JudgeStatus::Pending as i32, + )), + }) } diff --git a/src/main.rs b/src/main.rs index 592a934..ee66fed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use futures::StreamExt; use futures_core::Stream; use river::judge_request::Data; use river::river_server::{River, RiverServer}; -use river::{JudgeRequest, JudgeResponse}; +use river::{JudgeRequest, JudgeResponse, JudgeResultEnum}; use std::pin::Pin; use tempfile::tempdir_in; use tonic::transport::Server; @@ -42,58 +42,63 @@ impl River for RiverService { let mut stream = request.into_inner(); let output = async_stream::try_stream! { - let pwd = match tempdir_in("/tmp") { + let pwd = match tempdir_in(&config::CONFIG.judge_dir) { Ok(val) => val, Err(e) => { yield result::system_error(error::Error::IOError(e)); return; } }; + // 是否通过编译 + let mut compile_success = false; while let Some(req) = stream.next().await { + yield result::pending(); + // TODO: 限制并发数量 + yield result::running(); let req = req?; let mut language = String::from(""); let result = match &req.data { Some(Data::CompileData(data)) => { debug!("compile request"); + // 因为评测时还需要 language 的信息,因此此处进行复制保存 language = String::from(&data.language); - let cfg = config::CONFIG.languages.get(&language); - debug!("{:?}", cfg); - let code = &data.code; - judger::compile(&language, &code, &pwd.path()).await; - JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) + let res = judger::compile(&language, &data.code, &pwd.path()).await; + // 判断编译结果 + if let Ok(ref val) = res { + if let Some(river::judge_response::State::Result(rst)) = &val.state { + if rst.result == JudgeResultEnum::CompileSuccess as i32 { + // 标记编译成功 + compile_success = true; + } + } } + res }, Some(Data::JudgeData(data)) => { debug!("judge request"); - let in_file = &data.in_file; - let out_file = &data.out_file; - let time_limit = data.time_limit; - let memory_limit = data.memory_limit; - let judge_type = data.judge_type; - if language == "" { - // error::Error::CustomError(String::from("not compiled")); - JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) - } + // 必须通过编译才能运行 + if language == "" || !compile_success { + Err(error::Error::CustomError(String::from("not compiled"))) } else { - judger::judge(&language, &in_file, &out_file, time_limit, memory_limit, judge_type, &pwd.path()).await; - JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) - } + judger::judge( + &language, + &data.in_file, + &data.out_file, + data.time_limit, + data.memory_limit, + data.judge_type, + &pwd.path() + ).await } }, - None => JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) - }, - _ => JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) - }, + None => Err(error::Error::CustomError(String::from("unrecognized request types"))), + _ => Err(error::Error::CustomError(String::from("unrecognized request types"))), }; - // TODO - yield JudgeResponse { - state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) + let res = match result { + Ok(res) => res, + Err(e) => result::system_error(e) }; + yield res; }; }; diff --git a/src/process.rs b/src/process.rs index 09b2669..b0bb963 100644 --- a/src/process.rs +++ b/src/process.rs @@ -70,34 +70,22 @@ impl Process { } } -struct Runner { +pub struct Runner { process: Process, pid: i32, tx: Arc>>, rx: Arc>>, - stack: *mut libc::c_void, cgroup_set: CGroupSet, } impl Runner { pub fn from(process: Process) -> Result { let (tx, rx) = mpsc::channel(); - let stack = unsafe { - libc::mmap( - ptr::null_mut(), - STACK_SIZE, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK, - -1, - 0, - ) - }; Ok(Runner { process: process, pid: -1, tx: Arc::new(Mutex::new(tx)), rx: Arc::new(Mutex::new(rx)), - stack: stack, cgroup_set: CGroupSet::new()?, }) } @@ -107,8 +95,6 @@ impl Drop for Runner { fn drop(&mut self) { debug!("dropping"); unsafe { - libc::munmap(self.stack, STACK_SIZE); - let mut status = 0; let pid = libc::waitpid(self.pid, &mut status, libc::WNOHANG); @@ -128,20 +114,31 @@ impl Future for Runner { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let runner = Pin::into_inner(self); + // 创建 clone 所需的栈空间 + let stack = unsafe { + libc::mmap( + ptr::null_mut(), + STACK_SIZE, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK, + -1, + 0, + ) + }; // 如果 pid == -1,则说明子进程还没有运行 if runner.pid == -1 { let waker = cx.waker().clone(); let pid = unsafe { libc::clone( runit, - (runner.stack as usize + STACK_SIZE) as *mut libc::c_void, + (stack as usize + STACK_SIZE) as *mut libc::c_void, libc::SIGCHLD - | libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等) - | libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网 - | libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间 - | libc::CLONE_NEWIPC // IPC 隔离 - | libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒 - | libc::CLONE_NEWPID, // 外部进程对沙盒不可见 + | libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等) + | libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网 + | libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间 + | libc::CLONE_NEWIPC // IPC 隔离 + | libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒 + | libc::CLONE_NEWPID, // 外部进程对沙盒不可见 &mut runner.process as *mut _ as *mut libc::c_void, ) }; @@ -166,6 +163,9 @@ impl Future for Runner { }); return Poll::Pending; } else { + unsafe { + libc::munmap(stack, STACK_SIZE); + } let mut status = runner.rx.lock().unwrap().recv().unwrap(); let mem_used = match runner .cgroup_set @@ -240,9 +240,11 @@ extern "C" fn runit(process: *mut libc::c_void) -> i32 { unsafe { security(&process); fd_dup(); + // TODO: 资源限制,超时 kill 等 syscall_or_panic!(libc::execve( exec_args.pathname, exec_args.argv, + // TODO: 传递自定义的环境变量 // 因为运行是在隔离的环境内,原有的环境变量已经没啥用了,因此这里直接传 null ptr::null_mut() )); diff --git a/src/result.rs b/src/result.rs index de7a9c5..1b2266d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,9 +1,9 @@ use crate::error::Error; use crate::river::judge_response::State; -use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum}; +use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus}; pub fn system_error(err: Error) -> JudgeResponse { - warn!("{:?}", err); + warn!("{}", err); JudgeResponse { state: Some(State::Result(JudgeResult { time_used: 0, @@ -13,3 +13,37 @@ pub fn system_error(err: Error) -> JudgeResponse { })), } } + +pub fn pending() -> JudgeResponse { + JudgeResponse { + state: Some(State::Status(JudgeStatus::Pending as i32)), + } +} + +pub fn running() -> JudgeResponse { + JudgeResponse { + state: Some(State::Status(JudgeStatus::Running as i32)), + } +} + +pub fn compile_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeResponse { + JudgeResponse { + state: Some(State::Result(JudgeResult { + time_used: time_used, + memory_used: memory_used, + result: JudgeResultEnum::CompileError as i32, + errmsg: String::from(errmsg), + })), + } +} + +pub fn compile_success(time_used: i64, memory_used: i64) -> JudgeResponse { + JudgeResponse { + state: Some(State::Result(JudgeResult { + time_used: time_used, + memory_used: memory_used, + result: JudgeResultEnum::CompileSuccess as i32, + errmsg: String::from(""), + })), + } +}