mirror of
https://github.com/MeiK2333/river.git
synced 2025-11-04 14:49:40 +08:00
全部替换为新沙盒
This commit is contained in:
parent
04566b44fb
commit
2deff9a019
@ -1,5 +1,7 @@
|
||||
data_dir: /root/river/runtime/data
|
||||
data_dir: /data
|
||||
judge_dir: /tmp
|
||||
rootfs: /root/river/newbie-sandbox/runtime/rootfs
|
||||
cgroup: 1
|
||||
languages:
|
||||
C:
|
||||
compile_cmd: /usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static
|
||||
|
||||
@ -7,9 +7,10 @@ use num_cpus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
pub static STDIN_FILENAME: &str = "stdin.txt";
|
||||
// pub static STDIN_FILENAME: &str = "stdin.txt";
|
||||
pub static STDOUT_FILENAME: &str = "stdout.txt";
|
||||
pub static STDERR_FILENAME: &str = "stderr.txt";
|
||||
pub static RESULT_FILENAME: &str = "result.txt";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = {
|
||||
@ -38,5 +39,7 @@ pub struct LanguageConf {
|
||||
pub struct Config {
|
||||
pub data_dir: String,
|
||||
pub judge_dir: String,
|
||||
pub cgroup: i32,
|
||||
pub rootfs: String,
|
||||
pub languages: HashMap<String, LanguageConf>,
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#![macro_use]
|
||||
|
||||
use std::ffi::{NulError, OsString};
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{NulError, OsString};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result;
|
||||
@ -19,6 +19,9 @@ pub enum Error {
|
||||
LanguageNotFound(String),
|
||||
SystemError(String),
|
||||
OsStringToStringError(OsString),
|
||||
PathToStringError(),
|
||||
StringSplitError(),
|
||||
StringToIntError(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@ -29,7 +32,7 @@ macro_rules! try_io {
|
||||
($expression:expr) => {
|
||||
match $expression {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::IOError(e)),
|
||||
Err(e) => return Err(crate::error::Error::IOError(e)),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
102
src/judger.rs
102
src/judger.rs
@ -1,15 +1,23 @@
|
||||
use std::path::Path;
|
||||
|
||||
use tokio::fs;
|
||||
use tokio::fs::read_to_string;
|
||||
use tokio::fs::{read_to_string, remove_file};
|
||||
|
||||
use crate::config::{CONFIG, CPU_SEMAPHORE, STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
|
||||
use crate::config::{CONFIG, CPU_SEMAPHORE, RESULT_FILENAME, STDERR_FILENAME, STDOUT_FILENAME};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::result::{
|
||||
accepted, compile_error, compile_success, memory_limit_exceeded, runtime_error,
|
||||
standard_result, time_limit_exceeded, wrong_answer,
|
||||
};
|
||||
use crate::river::{JudgeResponse, JudgeResultEnum, JudgeType};
|
||||
use crate::sandbox::Sandbox;
|
||||
|
||||
fn path_to_string(path: &Path) -> Result<String> {
|
||||
if let Some(s) = path.to_str() {
|
||||
return Ok(String::from(s));
|
||||
}
|
||||
Err(Error::PathToStringError())
|
||||
}
|
||||
|
||||
pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeResponse> {
|
||||
info!("compile: language = `{}`", language);
|
||||
@ -21,10 +29,36 @@ pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeRes
|
||||
|
||||
let semaphore = CPU_SEMAPHORE.clone();
|
||||
let permit = semaphore.acquire().await;
|
||||
// TODO: run process
|
||||
drop(permit);
|
||||
|
||||
Ok(compile_success(0, 0))
|
||||
let mut sandbox = Sandbox::new(
|
||||
&lang.compile_cmd,
|
||||
path_to_string(&path)?,
|
||||
String::from(&CONFIG.rootfs),
|
||||
path_to_string(&path.join(RESULT_FILENAME))?,
|
||||
String::from("/STDIN/"),
|
||||
path_to_string(&path.join(STDOUT_FILENAME))?,
|
||||
path_to_string(&path.join(STDERR_FILENAME))?,
|
||||
5000,
|
||||
655350,
|
||||
50 * 1024 * 1024,
|
||||
i32::from(CONFIG.cgroup),
|
||||
10,
|
||||
);
|
||||
let status = sandbox.spawn().await?;
|
||||
drop(permit);
|
||||
info!("status = {:?}", status);
|
||||
|
||||
if status.exit_code != 0 || status.signal != 0 {
|
||||
// 合并 stdout 与 stderr 为 errmsg
|
||||
// 因为不同的语言、不同的编译器,错误信息输出到了不同的地方
|
||||
let errmsg = format!(
|
||||
"{}\n{}",
|
||||
try_io!(read_to_string(path.join(STDOUT_FILENAME)).await),
|
||||
try_io!(read_to_string(path.join(STDERR_FILENAME)).await),
|
||||
);
|
||||
return Ok(compile_error(status.time_used, status.memory_used, &errmsg));
|
||||
}
|
||||
Ok(compile_success(status.time_used, status.memory_used))
|
||||
}
|
||||
|
||||
pub async fn judge(
|
||||
@ -38,8 +72,6 @@ pub async fn judge(
|
||||
) -> Result<JudgeResponse> {
|
||||
info!("judge: language = `{}`, in_file = `{}`, out_file = `{}`, time_limit = `{}`, memory_limit = `{}`, judge_type = `{}`", language, in_file, out_file, time_limit, memory_limit, judge_type);
|
||||
let data_dir = Path::new(&CONFIG.data_dir);
|
||||
// 复制输入文件
|
||||
try_io!(fs::copy(data_dir.join(&in_file), path.join(STDIN_FILENAME)).await);
|
||||
|
||||
let lang = match CONFIG.languages.get(language) {
|
||||
Some(val) => val,
|
||||
@ -48,7 +80,59 @@ pub async fn judge(
|
||||
// 信号量控制并发
|
||||
let semaphore = CPU_SEMAPHORE.clone();
|
||||
let permit = semaphore.acquire().await;
|
||||
// TODO: run process
|
||||
|
||||
try_io!(remove_file(&path.join(RESULT_FILENAME)).await);
|
||||
try_io!(remove_file(&path.join(STDOUT_FILENAME)).await);
|
||||
try_io!(remove_file(&path.join(STDERR_FILENAME)).await);
|
||||
let mut sandbox = Sandbox::new(
|
||||
&lang.run_cmd,
|
||||
path_to_string(&path)?,
|
||||
String::from(&CONFIG.rootfs),
|
||||
path_to_string(&path.join(RESULT_FILENAME))?,
|
||||
path_to_string(data_dir.join(&in_file).as_path())?,
|
||||
path_to_string(&path.join(STDOUT_FILENAME))?,
|
||||
path_to_string(&path.join(STDERR_FILENAME))?,
|
||||
time_limit,
|
||||
memory_limit,
|
||||
50 * 1024 * 1024,
|
||||
i32::from(CONFIG.cgroup),
|
||||
2,
|
||||
);
|
||||
let status = sandbox.spawn().await?;
|
||||
drop(permit);
|
||||
Ok(accepted(0, 0))
|
||||
|
||||
if status.time_used > time_limit.into() {
|
||||
// TLE
|
||||
return Ok(time_limit_exceeded(status.time_used, status.memory_used));
|
||||
} else if status.memory_used > memory_limit.into() {
|
||||
// MLE
|
||||
return Ok(memory_limit_exceeded(status.time_used, status.memory_used));
|
||||
} else if status.signal != 0 {
|
||||
// RE
|
||||
return Ok(runtime_error(
|
||||
status.time_used,
|
||||
status.memory_used,
|
||||
&format!("Program was interrupted by signal: `{}`", status.signal),
|
||||
));
|
||||
} else if status.exit_code != 0 {
|
||||
// RE
|
||||
// 就算是用户自己返回的非零,也算 RE
|
||||
return Ok(runtime_error(
|
||||
status.time_used,
|
||||
status.memory_used,
|
||||
&format!("Exceptional program return code: `{}`", status.exit_code),
|
||||
));
|
||||
} else if judge_type == JudgeType::Standard as i32 {
|
||||
// 答案对比
|
||||
let out = try_io!(fs::read(path.join(STDOUT_FILENAME)).await);
|
||||
let ans = try_io!(fs::read(data_dir.join(&out_file)).await);
|
||||
let res = standard_result(&out, &ans)?;
|
||||
return if res == JudgeResultEnum::Accepted {
|
||||
Ok(accepted(status.time_used, status.memory_used))
|
||||
} else {
|
||||
Ok(wrong_answer(status.time_used, status.memory_used))
|
||||
};
|
||||
}
|
||||
|
||||
Err(Error::SystemError(String::from(format!("Unknown Error!"))))
|
||||
}
|
||||
|
||||
16
src/main.rs
16
src/main.rs
@ -10,21 +10,22 @@ use futures_core::Stream;
|
||||
use log4rs;
|
||||
use tempfile::tempdir_in;
|
||||
use tokio::fs::read_dir;
|
||||
use tonic::{Request, Response, Status};
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use river::judge_request::Data;
|
||||
use river::river_server::{River, RiverServer};
|
||||
use river::{
|
||||
Empty, JudgeRequest, JudgeResponse, JudgeResultEnum, LanguageConfigResponse, LanguageItem,
|
||||
LsCase, LsRequest, LsResponse,
|
||||
};
|
||||
use river::judge_request::Data;
|
||||
use river::river_server::{River, RiverServer};
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
mod judger;
|
||||
mod result;
|
||||
mod sandbox;
|
||||
|
||||
pub mod river {
|
||||
tonic::include_proto!("river");
|
||||
@ -36,7 +37,7 @@ pub struct RiverService {}
|
||||
#[tonic::async_trait]
|
||||
impl River for RiverService {
|
||||
type JudgeStream =
|
||||
Pin<Box<dyn Stream<Item=Result<JudgeResponse, Status>> + Send + Sync + 'static>>;
|
||||
Pin<Box<dyn Stream<Item = Result<JudgeResponse, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn judge(
|
||||
&self,
|
||||
@ -122,9 +123,7 @@ impl River for RiverService {
|
||||
version: String::from(&value.version),
|
||||
});
|
||||
}
|
||||
let response = LanguageConfigResponse {
|
||||
languages,
|
||||
};
|
||||
let response = LanguageConfigResponse { languages };
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
@ -145,7 +144,7 @@ impl River for RiverService {
|
||||
loop {
|
||||
let in_file = format!("data{}.in", iter);
|
||||
let out_file = format!("data{}.out", iter);
|
||||
if files.contains(&in_file) && files.contains((&out_file)) {
|
||||
if files.contains(&in_file) && files.contains(&out_file) {
|
||||
response.cases.push(LsCase {
|
||||
r#in: in_file,
|
||||
out: out_file,
|
||||
@ -155,7 +154,6 @@ impl River for RiverService {
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!("ls: {:?}", response);
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus};
|
||||
use crate::river::judge_response::State;
|
||||
use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus};
|
||||
|
||||
pub fn system_error(err: Error) -> JudgeResponse {
|
||||
warn!("{}", err);
|
||||
|
||||
148
src/sandbox.rs
Normal file
148
src/sandbox.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use tokio::fs::read_to_string;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessExitStatus {
|
||||
pub time_used: i64,
|
||||
pub memory_used: i64,
|
||||
pub exit_code: i64,
|
||||
pub status: i64,
|
||||
pub signal: i64,
|
||||
}
|
||||
|
||||
pub struct Sandbox {
|
||||
inner_args: Vec<String>,
|
||||
workdir: String,
|
||||
rootfs: String,
|
||||
result: String,
|
||||
stdin: String,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
time_limit: i32,
|
||||
memory_limit: i32,
|
||||
file_size_limit: i32,
|
||||
cgroup: i32,
|
||||
pids: i32,
|
||||
}
|
||||
|
||||
impl Sandbox {
|
||||
pub fn new(
|
||||
cmd: &String,
|
||||
workdir: String,
|
||||
rootfs: String,
|
||||
result: String,
|
||||
stdin: String,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
time_limit: i32,
|
||||
memory_limit: i32,
|
||||
file_size_limit: i32,
|
||||
cgroup: i32,
|
||||
pids: i32,
|
||||
) -> Self {
|
||||
let inner_args = String::from(cmd)
|
||||
.split(" ")
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
Sandbox {
|
||||
inner_args,
|
||||
workdir,
|
||||
rootfs,
|
||||
result,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
time_limit,
|
||||
memory_limit,
|
||||
file_size_limit,
|
||||
cgroup,
|
||||
pids,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn(&mut self) -> Result<ProcessExitStatus> {
|
||||
let mut args = vec![
|
||||
String::from("./newbie-sandbox/target/x86_64-unknown-linux-gnu/release/newbie-sandbox"),
|
||||
String::from("-w"),
|
||||
String::from(&self.workdir),
|
||||
String::from("--rootfs"),
|
||||
String::from(&self.rootfs),
|
||||
String::from("-r"),
|
||||
String::from(&self.result),
|
||||
String::from("-i"),
|
||||
String::from(&self.stdin),
|
||||
String::from("-o"),
|
||||
String::from(&self.stdout),
|
||||
String::from("-e"),
|
||||
String::from(&self.stderr),
|
||||
String::from("-t"),
|
||||
self.time_limit.to_string(),
|
||||
String::from("-m"),
|
||||
self.memory_limit.to_string(),
|
||||
String::from("-f"),
|
||||
self.file_size_limit.to_string(),
|
||||
String::from("-c"),
|
||||
self.cgroup.to_string(),
|
||||
String::from("-p"),
|
||||
self.pids.to_string(),
|
||||
String::from("--"),
|
||||
];
|
||||
args.extend_from_slice(&mut self.inner_args);
|
||||
info!("args = {:?}", args.join(" "));
|
||||
let mut child = try_io!(Command::new(&args[0]).args(&args[1..]).spawn());
|
||||
let exit_status = try_io!(child.wait().await);
|
||||
if !exit_status.success() {
|
||||
return Err(Error::SystemError(String::from("run sandbox error!")));
|
||||
}
|
||||
|
||||
let mut time_used = 0;
|
||||
let mut memory_used = 0;
|
||||
let mut exit_code = 0;
|
||||
let mut status = 0;
|
||||
let mut signal = 0;
|
||||
|
||||
let text = try_io!(read_to_string(&self.result).await);
|
||||
for line in text.split("\n") {
|
||||
if !line.contains("=") {
|
||||
continue;
|
||||
}
|
||||
let mut splitter = line.splitn(2, " = ");
|
||||
let key = if let Some(s) = splitter.next() {
|
||||
s
|
||||
} else {
|
||||
return Err(Error::StringSplitError());
|
||||
};
|
||||
let value = if let Some(s) = splitter.next() {
|
||||
s
|
||||
} else {
|
||||
return Err(Error::StringSplitError());
|
||||
};
|
||||
match key {
|
||||
"time_used" => time_used = string_to_i64(value)?,
|
||||
"memory_used" => memory_used = string_to_i64(value)?,
|
||||
"exit_code" => exit_code = string_to_i64(value)?,
|
||||
"status" => status = string_to_i64(value)?,
|
||||
"signal" => signal = string_to_i64(value)?,
|
||||
_ => continue,
|
||||
}
|
||||
debug!("{}: {}", key, value);
|
||||
}
|
||||
|
||||
Ok(ProcessExitStatus {
|
||||
time_used,
|
||||
memory_used,
|
||||
exit_code,
|
||||
status,
|
||||
signal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_i64(value: &str) -> Result<i64> {
|
||||
if let Ok(res) = value.parse() {
|
||||
return Ok(res);
|
||||
}
|
||||
Err(Error::StringToIntError(String::from(value)))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user