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
|
||||
judge_dir: /tmp
|
||||
languages:
|
||||
C:
|
||||
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";
|
||||
|
||||
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<String, LanguageConf>,
|
||||
pub data_dir: String,
|
||||
pub judge_dir: String,
|
||||
pub languages: HashMap<String, LanguageConf>,
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ pub enum Error {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
CreateTempDirError(io::Error),
|
||||
CustomError(String),
|
||||
LanguageNotFound(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<JudgeResponse> {
|
||||
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<JudgeResponse> {
|
||||
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,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
67
src/main.rs
67
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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -70,34 +70,22 @@ impl Process {
|
||||
}
|
||||
}
|
||||
|
||||
struct Runner {
|
||||
pub struct Runner {
|
||||
process: Process,
|
||||
pid: i32,
|
||||
tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
|
||||
rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
|
||||
stack: *mut libc::c_void,
|
||||
cgroup_set: CGroupSet,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn from(process: Process) -> Result<Runner> {
|
||||
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<Result<RunnerStatus>> {
|
||||
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()
|
||||
));
|
||||
|
||||
@ -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(""),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user