添加编译流程

This commit is contained in:
MeiK2333 2021-01-14 17:23:13 +08:00
parent 2fe45f3bfd
commit 33f93913d6
7 changed files with 162 additions and 69 deletions

View File

@ -1,4 +1,5 @@
data_dir: /data data_dir: /data
judge_dir: /tmp
languages: languages:
C: C:
compile_cmd: /usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static compile_cmd: /usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static

View File

@ -8,23 +8,24 @@ pub static STDOUT_FILENAME: &str = "stdout.txt";
pub static STDERR_FILENAME: &str = "stderr.txt"; pub static STDERR_FILENAME: &str = "stderr.txt";
lazy_static! { lazy_static! {
pub static ref CONFIG: Config = { pub static ref CONFIG: Config = {
let config = fs::read_to_string("config.yaml").unwrap(); let config = fs::read_to_string("config.yaml").unwrap();
let cg: Config = serde_yaml::from_str(&config).unwrap(); let cfg: Config = serde_yaml::from_str(&config).unwrap();
debug!("{:?}", cg); debug!("{:?}", cfg);
cg cfg
}; };
} }
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct LanguageConf { pub struct LanguageConf {
pub compile_cmd: String, pub compile_cmd: String,
pub code_file: String, pub code_file: String,
pub run_cmd: String, pub run_cmd: String,
} }
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub data_dir: String, pub data_dir: String,
pub languages: HashMap<String, LanguageConf>, pub judge_dir: String,
pub languages: HashMap<String, LanguageConf>,
} }

View File

@ -15,6 +15,7 @@ pub enum Error {
ParseIntError(std::num::ParseIntError), ParseIntError(std::num::ParseIntError),
CreateTempDirError(io::Error), CreateTempDirError(io::Error),
CustomError(String), CustomError(String),
LanguageNotFound(String),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -33,7 +34,9 @@ macro_rules! try_io {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { 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), _ => write!(f, "{:?}", self),
} }
} }

View File

@ -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; use std::path::Path;
#[cfg(test)] #[cfg(test)]
use std::println as debug; 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<JudgeResponse> {
debug!("language: {}", language); debug!("language: {}", language);
debug!("code: {}", code); debug!("code: {}", code);
debug!("path: {:?}", path); 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( pub async fn judge(
@ -17,7 +59,7 @@ pub async fn judge(
memory_limit: i32, memory_limit: i32,
judge_type: i32, judge_type: i32,
path: &Path, path: &Path,
) { ) -> Result<JudgeResponse> {
debug!("language: {}", language); debug!("language: {}", language);
debug!("in_file: {}", in_file); debug!("in_file: {}", in_file);
debug!("out_file: {}", out_file); debug!("out_file: {}", out_file);
@ -25,4 +67,9 @@ pub async fn judge(
debug!("memory_limit: {}", memory_limit); debug!("memory_limit: {}", memory_limit);
debug!("judge_type: {}", judge_type); debug!("judge_type: {}", judge_type);
debug!("path: {:?}", path); debug!("path: {:?}", path);
Ok(JudgeResponse {
state: Some(river::judge_response::State::Status(
river::JudgeStatus::Pending as i32,
)),
})
} }

View File

@ -7,7 +7,7 @@ use futures::StreamExt;
use futures_core::Stream; use futures_core::Stream;
use river::judge_request::Data; use river::judge_request::Data;
use river::river_server::{River, RiverServer}; use river::river_server::{River, RiverServer};
use river::{JudgeRequest, JudgeResponse}; use river::{JudgeRequest, JudgeResponse, JudgeResultEnum};
use std::pin::Pin; use std::pin::Pin;
use tempfile::tempdir_in; use tempfile::tempdir_in;
use tonic::transport::Server; use tonic::transport::Server;
@ -42,58 +42,63 @@ impl River for RiverService {
let mut stream = request.into_inner(); let mut stream = request.into_inner();
let output = async_stream::try_stream! { let output = async_stream::try_stream! {
let pwd = match tempdir_in("/tmp") { let pwd = match tempdir_in(&config::CONFIG.judge_dir) {
Ok(val) => val, Ok(val) => val,
Err(e) => { Err(e) => {
yield result::system_error(error::Error::IOError(e)); yield result::system_error(error::Error::IOError(e));
return; return;
} }
}; };
// 是否通过编译
let mut compile_success = false;
while let Some(req) = stream.next().await { while let Some(req) = stream.next().await {
yield result::pending();
// TODO: 限制并发数量
yield result::running();
let req = req?; let req = req?;
let mut language = String::from(""); let mut language = String::from("");
let result = match &req.data { let result = match &req.data {
Some(Data::CompileData(data)) => { Some(Data::CompileData(data)) => {
debug!("compile request"); debug!("compile request");
// 因为评测时还需要 language 的信息,因此此处进行复制保存
language = String::from(&data.language); language = String::from(&data.language);
let cfg = config::CONFIG.languages.get(&language); let res = judger::compile(&language, &data.code, &pwd.path()).await;
debug!("{:?}", cfg); // 判断编译结果
let code = &data.code; if let Ok(ref val) = res {
judger::compile(&language, &code, &pwd.path()).await; if let Some(river::judge_response::State::Result(rst)) = &val.state {
JudgeResponse { if rst.result == JudgeResultEnum::CompileSuccess as i32 {
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) // 标记编译成功
compile_success = true;
}
}
} }
res
}, },
Some(Data::JudgeData(data)) => { Some(Data::JudgeData(data)) => {
debug!("judge request"); debug!("judge request");
let in_file = &data.in_file; // 必须通过编译才能运行
let out_file = &data.out_file; if language == "" || !compile_success {
let time_limit = data.time_limit; Err(error::Error::CustomError(String::from("not compiled")))
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))
}
} else { } else {
judger::judge(&language, &in_file, &out_file, time_limit, memory_limit, judge_type, &pwd.path()).await; judger::judge(
JudgeResponse { &language,
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) &data.in_file,
} &data.out_file,
data.time_limit,
data.memory_limit,
data.judge_type,
&pwd.path()
).await
} }
}, },
None => JudgeResponse { None => Err(error::Error::CustomError(String::from("unrecognized request types"))),
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) _ => Err(error::Error::CustomError(String::from("unrecognized request types"))),
},
_ => JudgeResponse {
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32))
},
}; };
// TODO let res = match result {
yield JudgeResponse { Ok(res) => res,
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32)) Err(e) => result::system_error(e)
}; };
yield res;
}; };
}; };

View File

@ -70,34 +70,22 @@ impl Process {
} }
} }
struct Runner { pub struct Runner {
process: Process, process: Process,
pid: i32, pid: i32,
tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>, tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>, rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
stack: *mut libc::c_void,
cgroup_set: CGroupSet, cgroup_set: CGroupSet,
} }
impl Runner { impl Runner {
pub fn from(process: Process) -> Result<Runner> { pub fn from(process: Process) -> Result<Runner> {
let (tx, rx) = mpsc::channel(); 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 { Ok(Runner {
process: process, process: process,
pid: -1, pid: -1,
tx: Arc::new(Mutex::new(tx)), tx: Arc::new(Mutex::new(tx)),
rx: Arc::new(Mutex::new(rx)), rx: Arc::new(Mutex::new(rx)),
stack: stack,
cgroup_set: CGroupSet::new()?, cgroup_set: CGroupSet::new()?,
}) })
} }
@ -107,8 +95,6 @@ impl Drop for Runner {
fn drop(&mut self) { fn drop(&mut self) {
debug!("dropping"); debug!("dropping");
unsafe { unsafe {
libc::munmap(self.stack, STACK_SIZE);
let mut status = 0; let mut status = 0;
let pid = libc::waitpid(self.pid, &mut status, libc::WNOHANG); 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<Result<RunnerStatus>> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<RunnerStatus>> {
let runner = Pin::into_inner(self); 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则说明子进程还没有运行 // 如果 pid == -1则说明子进程还没有运行
if runner.pid == -1 { if runner.pid == -1 {
let waker = cx.waker().clone(); let waker = cx.waker().clone();
let pid = unsafe { let pid = unsafe {
libc::clone( libc::clone(
runit, 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::SIGCHLD
| libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等) | libc::CLONE_NEWUTS // 设置新的 UTS 名称空间(主机名、网络名等)
| libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网 | libc::CLONE_NEWNET // 设置新的网络空间,如果没有配置网络,则该沙盒内部将无法联网
| libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间 | libc::CLONE_NEWNS // 为沙盒内部设置新的 namespaces 空间
| libc::CLONE_NEWIPC // IPC 隔离 | libc::CLONE_NEWIPC // IPC 隔离
| libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒 | libc::CLONE_NEWCGROUP // 在新的 CGROUP 中创建沙盒
| libc::CLONE_NEWPID, // 外部进程对沙盒不可见 | libc::CLONE_NEWPID, // 外部进程对沙盒不可见
&mut runner.process as *mut _ as *mut libc::c_void, &mut runner.process as *mut _ as *mut libc::c_void,
) )
}; };
@ -166,6 +163,9 @@ impl Future for Runner {
}); });
return Poll::Pending; return Poll::Pending;
} else { } else {
unsafe {
libc::munmap(stack, STACK_SIZE);
}
let mut status = runner.rx.lock().unwrap().recv().unwrap(); let mut status = runner.rx.lock().unwrap().recv().unwrap();
let mem_used = match runner let mem_used = match runner
.cgroup_set .cgroup_set
@ -240,9 +240,11 @@ extern "C" fn runit(process: *mut libc::c_void) -> i32 {
unsafe { unsafe {
security(&process); security(&process);
fd_dup(); fd_dup();
// TODO: 资源限制,超时 kill 等
syscall_or_panic!(libc::execve( syscall_or_panic!(libc::execve(
exec_args.pathname, exec_args.pathname,
exec_args.argv, exec_args.argv,
// TODO: 传递自定义的环境变量
// 因为运行是在隔离的环境内,原有的环境变量已经没啥用了,因此这里直接传 null // 因为运行是在隔离的环境内,原有的环境变量已经没啥用了,因此这里直接传 null
ptr::null_mut() ptr::null_mut()
)); ));

View File

@ -1,9 +1,9 @@
use crate::error::Error; use crate::error::Error;
use crate::river::judge_response::State; 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 { pub fn system_error(err: Error) -> JudgeResponse {
warn!("{:?}", err); warn!("{}", err);
JudgeResponse { JudgeResponse {
state: Some(State::Result(JudgeResult { state: Some(State::Result(JudgeResult {
time_used: 0, 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(""),
})),
}
}