mirror of
https://github.com/MeiK2333/river.git
synced 2025-11-04 14:49:40 +08:00
添加编译流程
This commit is contained in:
parent
2fe45f3bfd
commit
33f93913d6
@ -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
|
||||||
|
|||||||
@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/main.rs
67
src/main.rs
@ -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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
));
|
));
|
||||||
|
|||||||
@ -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(""),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user