mirror of
https://github.com/MeiK2333/river.git
synced 2025-11-04 14:49:40 +08:00
commit
0a931fbc27
@ -1,24 +0,0 @@
|
|||||||
use std::result;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Error(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub struct Cgroup {
|
|
||||||
pub pid: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cgroup {
|
|
||||||
pub fn new(pid: u32) -> Result<Self> {
|
|
||||||
return Ok(Cgroup { pid });
|
|
||||||
}
|
|
||||||
pub fn attach(&self) -> Result<()> {
|
|
||||||
if self.pid == 0 {
|
|
||||||
return Err(Error::Error("Hello World!".to_string()));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +1,11 @@
|
|||||||
|
use std::fmt;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::result;
|
use std::io;
|
||||||
use yaml_rust::{ScanError, Yaml, YamlLoader};
|
use yaml_rust::{Yaml, YamlLoader};
|
||||||
|
|
||||||
#[derive(Debug)]
|
use super::error::{Error, Result};
|
||||||
pub enum Error {
|
|
||||||
YamlScanError(ScanError),
|
|
||||||
YamlParseError(String),
|
|
||||||
ReadFileError,
|
|
||||||
LanguageNotFound(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
#[derive(Clone, Debug)]
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LanguageConfig {
|
pub struct LanguageConfig {
|
||||||
pub language: String,
|
pub language: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@ -20,6 +13,12 @@ pub struct LanguageConfig {
|
|||||||
pub run_command: String,
|
pub run_command: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LanguageConfig {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.language, self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub languages: Vec<LanguageConfig>,
|
pub languages: Vec<LanguageConfig>,
|
||||||
}
|
}
|
||||||
@ -28,7 +27,7 @@ impl Config {
|
|||||||
pub fn language_config_from_name(&self, name: &str) -> Result<LanguageConfig> {
|
pub fn language_config_from_name(&self, name: &str) -> Result<LanguageConfig> {
|
||||||
for language in &self.languages {
|
for language in &self.languages {
|
||||||
if language.language == name {
|
if language.language == name {
|
||||||
return Ok(language.clone())
|
return Ok(language.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::LanguageNotFound(name.to_string()))
|
Err(Error::LanguageNotFound(name.to_string()))
|
||||||
@ -101,7 +100,12 @@ impl Config {
|
|||||||
pub fn load_from_file(filename: &str) -> Result<Config> {
|
pub fn load_from_file(filename: &str) -> Result<Config> {
|
||||||
let contents = match fs::read_to_string(filename) {
|
let contents = match fs::read_to_string(filename) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(_) => return Err(Error::ReadFileError),
|
Err(_) => {
|
||||||
|
return Err(Error::ReadFileError(
|
||||||
|
filename.to_string(),
|
||||||
|
io::Error::last_os_error().raw_os_error(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let config = Config::load_yaml(&contents)?;
|
let config = Config::load_yaml(&contents)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
|
|||||||
@ -3,6 +3,14 @@ languages:
|
|||||||
version: 7.5.0
|
version: 7.5.0
|
||||||
compile_command: /usr/bin/gcc {{filename}} -o a.out
|
compile_command: /usr/bin/gcc {{filename}} -o a.out
|
||||||
run_command: ./a.out
|
run_command: ./a.out
|
||||||
|
- language: cpp
|
||||||
|
version: 7.5.0
|
||||||
|
compile_command: /usr/bin/g++ {{filename}} -o a.out
|
||||||
|
run_command: ./a.out
|
||||||
|
- language: bash
|
||||||
|
version: 4.4.20
|
||||||
|
compile_command: "/bin/echo Hello World!"
|
||||||
|
run_command: /bin/bash {{filename}}
|
||||||
- language: python
|
- language: python
|
||||||
version: 3.6.9
|
version: 3.6.9
|
||||||
compile_command: /usr/bin/python3 -m compileall {{filename}}
|
compile_command: /usr/bin/python3 -m compileall {{filename}}
|
||||||
|
|||||||
53
src/error.rs
Normal file
53
src/error.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use handlebars::RenderError;
|
||||||
|
use libc::strerror;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::ffi::NulError;
|
||||||
|
use std::fmt;
|
||||||
|
use std::result;
|
||||||
|
use yaml_rust::ScanError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
YamlScanError(ScanError),
|
||||||
|
YamlParseError(String),
|
||||||
|
LanguageNotFound(String),
|
||||||
|
ReadFileError(String, Option<i32>),
|
||||||
|
|
||||||
|
UnknownJudgeType(String),
|
||||||
|
PathJoinError,
|
||||||
|
|
||||||
|
StringToCStringError(NulError),
|
||||||
|
TemplateRenderError(RenderError),
|
||||||
|
LanguageConfigError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Error::YamlScanError(ref err) => {
|
||||||
|
let _ = write!(f, "YamlScanError: ");
|
||||||
|
err.fmt(f)
|
||||||
|
}
|
||||||
|
Error::YamlParseError(ref err) => write!(f, "YamlParseError: {}", err),
|
||||||
|
Error::LanguageNotFound(ref lang) => write!(f, "LanguageNotFound: {}", lang),
|
||||||
|
Error::ReadFileError(ref filename, errno) => {
|
||||||
|
let reason = match errno {
|
||||||
|
Some(no) => {
|
||||||
|
let stre = unsafe { strerror(no) };
|
||||||
|
let c_str: &CStr = unsafe { CStr::from_ptr(stre) };
|
||||||
|
c_str.to_str().unwrap()
|
||||||
|
}
|
||||||
|
_ => "Unknown Error!",
|
||||||
|
};
|
||||||
|
write!(f, "ReadFileError: `{}` {}", filename, reason)
|
||||||
|
}
|
||||||
|
Error::UnknownJudgeType(ref judge_type) => {
|
||||||
|
write!(f, "UnknownJudgeType: {}", judge_type)
|
||||||
|
}
|
||||||
|
Error::PathJoinError => write!(f, "PathJoinError"),
|
||||||
|
_ => write!(f, "{:?}", self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,48 +1,33 @@
|
|||||||
use std::ffi::CString;
|
|
||||||
use std::ffi::NulError;
|
|
||||||
use std::fs;
|
|
||||||
use std::mem;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::ptr;
|
|
||||||
use std::result;
|
|
||||||
use yaml_rust::{ScanError, Yaml, YamlLoader};
|
|
||||||
|
|
||||||
use super::config;
|
use super::config;
|
||||||
|
use super::error::{Error, Result};
|
||||||
#[derive(Debug)]
|
use std::fmt;
|
||||||
pub enum Error {
|
use std::fs;
|
||||||
StringToCStringError(NulError),
|
use std::io;
|
||||||
ReadFileError,
|
use std::path::Path;
|
||||||
PathJoinError,
|
use yaml_rust::{Yaml, YamlLoader};
|
||||||
YamlScanError(ScanError),
|
|
||||||
YamlParseError(String),
|
|
||||||
UnknownJudgeType(String),
|
|
||||||
LanguageNotFound(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub struct TestCase {
|
pub struct TestCase {
|
||||||
|
pub index: u32,
|
||||||
pub input_file: String,
|
pub input_file: String,
|
||||||
pub answer_file: String,
|
pub answer_file: String,
|
||||||
pub cpu_time_limit: u32,
|
pub cpu_time_limit: u32,
|
||||||
pub real_time_limit: u32,
|
pub real_time_limit: u32,
|
||||||
pub memory_limit: u32,
|
pub memory_limit: u32,
|
||||||
pub result: Option<TestCaseResult>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl fmt::Display for TestCase {
|
||||||
pub enum TestCaseResult {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
Accepted,
|
write!(
|
||||||
CompileError(String),
|
f,
|
||||||
WrongAnswer,
|
"index: {}
|
||||||
RuntimeError(String),
|
input_file: {}
|
||||||
SystemError(String),
|
answer_file: {}
|
||||||
}
|
time_limit: {}
|
||||||
|
memory_limit: {}
|
||||||
pub struct JudgeCode {
|
",
|
||||||
pub file: String,
|
self.index, self.input_file, self.answer_file, self.cpu_time_limit, self.memory_limit
|
||||||
pub language: config::LanguageConfig,
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum JudgeType {
|
pub enum JudgeType {
|
||||||
@ -50,30 +35,20 @@ pub enum JudgeType {
|
|||||||
Special,
|
Special,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestConfig {
|
pub struct Code {
|
||||||
pub default_time_limit: u32,
|
pub file: String,
|
||||||
pub default_real_time_limit: u32,
|
pub language: config::LanguageConfig,
|
||||||
pub default_memory_limit: u32,
|
}
|
||||||
|
|
||||||
|
pub struct JudgeConfig {
|
||||||
pub tests: Vec<TestCase>,
|
pub tests: Vec<TestCase>,
|
||||||
pub judge_type: JudgeType,
|
pub judge_type: JudgeType,
|
||||||
pub extra_files: Vec<String>,
|
pub extra_files: Vec<String>,
|
||||||
pub code: JudgeCode,
|
pub code: Code,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JudgeConfigs {
|
impl JudgeConfig {
|
||||||
pub exec_file: String,
|
fn load_yaml(global_config: &config::Config, yaml: &str) -> Result<JudgeConfig> {
|
||||||
pub exec_args: Vec<String>,
|
|
||||||
pub test_cases: Vec<TestCase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExecArgs {
|
|
||||||
pub pathname: *const libc::c_char,
|
|
||||||
pub argv: *const *const libc::c_char,
|
|
||||||
pub envp: *const *const libc::c_char,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JudgeConfigs {
|
|
||||||
fn load_yaml(global_config: &config::Config, yaml: &str) -> Result<TestConfig> {
|
|
||||||
let docs = match YamlLoader::load_from_str(yaml) {
|
let docs = match YamlLoader::load_from_str(yaml) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(err) => return Err(Error::YamlScanError(err)),
|
Err(err) => return Err(Error::YamlScanError(err)),
|
||||||
@ -89,15 +64,6 @@ impl JudgeConfigs {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let default_real_time_limit = match &doc["real_time_limit"] {
|
|
||||||
Yaml::Integer(value) => value.clone() as u32,
|
|
||||||
Yaml::BadValue => default_time_limit * 2 + 5000,
|
|
||||||
_ => {
|
|
||||||
return Err(Error::YamlParseError(
|
|
||||||
"解析错误,real_time_limit 字段的类型应该为 Integer".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let default_memory_limit = match &doc["memory_limit"] {
|
let default_memory_limit = match &doc["memory_limit"] {
|
||||||
Yaml::Integer(value) => value.clone() as u32,
|
Yaml::Integer(value) => value.clone() as u32,
|
||||||
Yaml::BadValue => 65535,
|
Yaml::BadValue => 65535,
|
||||||
@ -121,6 +87,8 @@ impl JudgeConfigs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut tests: Vec<TestCase> = vec![];
|
let mut tests: Vec<TestCase> = vec![];
|
||||||
|
// Yaml 解析库没有实现 enumerate 方法,因此此处使用 index 进行计数
|
||||||
|
let mut index = 1;
|
||||||
for case in test_cases {
|
for case in test_cases {
|
||||||
let cpu_time_limit = match &case["time_limit"] {
|
let cpu_time_limit = match &case["time_limit"] {
|
||||||
Yaml::Integer(value) => value.clone() as u32,
|
Yaml::Integer(value) => value.clone() as u32,
|
||||||
@ -176,13 +144,14 @@ impl JudgeConfigs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
tests.push(TestCase {
|
tests.push(TestCase {
|
||||||
|
index,
|
||||||
cpu_time_limit,
|
cpu_time_limit,
|
||||||
real_time_limit,
|
real_time_limit,
|
||||||
memory_limit,
|
memory_limit,
|
||||||
result: None,
|
|
||||||
input_file,
|
input_file,
|
||||||
answer_file,
|
answer_file,
|
||||||
});
|
});
|
||||||
|
index += 1;
|
||||||
}
|
}
|
||||||
let judge_type = match &doc["judge_type"] {
|
let judge_type = match &doc["judge_type"] {
|
||||||
Yaml::String(value) => {
|
Yaml::String(value) => {
|
||||||
@ -269,7 +238,7 @@ impl JudgeConfigs {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
JudgeCode { file, language }
|
Code { file, language }
|
||||||
}
|
}
|
||||||
Yaml::BadValue => {
|
Yaml::BadValue => {
|
||||||
return Err(Error::YamlParseError(
|
return Err(Error::YamlParseError(
|
||||||
@ -283,20 +252,14 @@ impl JudgeConfigs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TestConfig {
|
Ok(JudgeConfig {
|
||||||
default_time_limit,
|
|
||||||
default_real_time_limit,
|
|
||||||
default_memory_limit,
|
|
||||||
tests,
|
tests,
|
||||||
judge_type,
|
judge_type,
|
||||||
extra_files,
|
extra_files,
|
||||||
code,
|
code,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
pub fn load(global_config: &config::Config, path: &str) -> Result<JudgeConfig> {
|
||||||
* 读取评测文件夹
|
|
||||||
*/
|
|
||||||
pub fn load(global_config: &config::Config, path: &str) -> Result<JudgeConfigs> {
|
|
||||||
let dir = Path::new(&path);
|
let dir = Path::new(&path);
|
||||||
|
|
||||||
let config = dir.join("config.yml");
|
let config = dir.join("config.yml");
|
||||||
@ -306,78 +269,13 @@ impl JudgeConfigs {
|
|||||||
};
|
};
|
||||||
let config = match fs::read_to_string(&config) {
|
let config = match fs::read_to_string(&config) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(_) => return Err(Error::ReadFileError),
|
Err(_) => {
|
||||||
};
|
return Err(Error::ReadFileError(
|
||||||
let _config = JudgeConfigs::load_yaml(global_config, &config)?;
|
path.to_string(),
|
||||||
|
io::Error::last_os_error().raw_os_error(),
|
||||||
Ok(JudgeConfigs {
|
))
|
||||||
exec_file: "".to_string(),
|
|
||||||
exec_args: vec![],
|
|
||||||
test_cases: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 为 exec 函数生成参数
|
|
||||||
* 涉及到 Rust 到 C 的内存转换,此过程是内存不安全的
|
|
||||||
* 请务必手动清理内存,或者仅在马上要执行 exec 的位置执行此函数,以便由操作系统自动回收内存
|
|
||||||
*/
|
|
||||||
pub unsafe fn exec_args(&self) -> Result<ExecArgs> {
|
|
||||||
let exec_file = match CString::new(self.exec_file.clone()) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
|
||||||
};
|
|
||||||
let exec_file_ptr = exec_file.as_ptr();
|
|
||||||
let mut exec_args: Vec<*const libc::c_char> = vec![];
|
|
||||||
for item in self.exec_args.iter() {
|
|
||||||
let cstr = match CString::new(item.clone()) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => return Err(Error::StringToCStringError(err)),
|
|
||||||
};
|
|
||||||
let cptr = cstr.as_ptr();
|
|
||||||
// 需要使用 mem::forget 来标记
|
|
||||||
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
|
||||||
mem::forget(cstr);
|
|
||||||
exec_args.push(cptr);
|
|
||||||
}
|
|
||||||
// argv 与 envp 的参数需要使用 NULL 来标记结束
|
|
||||||
exec_args.push(ptr::null());
|
|
||||||
let exec_args_ptr: *const *const libc::c_char =
|
|
||||||
exec_args.as_ptr() as *const *const libc::c_char;
|
|
||||||
let env: Vec<*const libc::c_char> = vec![ptr::null()];
|
|
||||||
let env_ptr = env.as_ptr() as *const *const libc::c_char;
|
|
||||||
mem::forget(env);
|
|
||||||
mem::forget(exec_file);
|
|
||||||
mem::forget(exec_args);
|
|
||||||
|
|
||||||
Ok(ExecArgs {
|
|
||||||
pathname: exec_file_ptr,
|
|
||||||
argv: exec_args_ptr,
|
|
||||||
envp: env_ptr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_base() {
|
|
||||||
let run_args = JudgeConfigs {
|
|
||||||
exec_file: "/bin/echo".to_string(),
|
|
||||||
exec_args: vec![
|
|
||||||
"/bin/echo".to_string(),
|
|
||||||
"Hello".to_string(),
|
|
||||||
"World".to_string(),
|
|
||||||
],
|
|
||||||
test_cases: vec![],
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
let pid = libc::fork();
|
|
||||||
if pid == 0 {
|
|
||||||
let exec_args = run_args.exec_args().unwrap();
|
|
||||||
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
JudgeConfig::load_yaml(global_config, &config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
99
src/main.rs
99
src/main.rs
@ -1,21 +1,12 @@
|
|||||||
extern crate libc;
|
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod runner;
|
mod error;
|
||||||
|
mod judger;
|
||||||
|
mod process;
|
||||||
|
mod result;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut handlebars = Handlebars::new();
|
|
||||||
let source = "hello {{world}}";
|
|
||||||
let _ = handlebars.register_template_string("t1", source);
|
|
||||||
let mut data = BTreeMap::new();
|
|
||||||
data.insert("hello".to_string(), "你好".to_string());
|
|
||||||
data.insert("world".to_string(), "世界!".to_string());
|
|
||||||
println!("{}", handlebars.render("t1", &data).unwrap());
|
|
||||||
|
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let config = if args.len() > 1 {
|
let config = if args.len() > 1 {
|
||||||
config::Config::load_from_file(&args[1])
|
config::Config::load_from_file(&args[1])
|
||||||
@ -25,85 +16,21 @@ fn main() {
|
|||||||
let config = match config {
|
let config = match config {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{:?}", err);
|
eprintln!("{}", err);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let language = config.language_config_from_name("python");
|
||||||
|
println!("{}", language.unwrap());
|
||||||
|
|
||||||
let _ = match runner::JudgeConfigs::load(&config, "example") {
|
let judge_config = match judger::JudgeConfig::load(&config, "example") {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{:?}", err);
|
eprintln!("{}", err);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
println!("{}", judge_config.code.language);
|
||||||
|
|
||||||
process();
|
process::run(&judge_config);
|
||||||
}
|
|
||||||
|
|
||||||
fn process() {
|
|
||||||
let pid;
|
|
||||||
unsafe {
|
|
||||||
pid = libc::fork();
|
|
||||||
}
|
|
||||||
let mut run_configs = runner::JudgeConfigs {
|
|
||||||
exec_file: "/usr/bin/python3".to_string(),
|
|
||||||
exec_args: vec![
|
|
||||||
"/usr/bin/python3".to_string(),
|
|
||||||
"-c".to_string(),
|
|
||||||
"import requests; print(requests.get('https://httpbin.org/get').json())".to_string(),
|
|
||||||
],
|
|
||||||
test_cases: vec![],
|
|
||||||
};
|
|
||||||
if pid == 0 {
|
|
||||||
unsafe {
|
|
||||||
let exec_args = run_configs.exec_args().unwrap();
|
|
||||||
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
|
||||||
}
|
|
||||||
} else if pid > 0 {
|
|
||||||
println!("{:?}", pid);
|
|
||||||
run_configs.test_cases.push(runner::TestCase {
|
|
||||||
answer_file: "1.ans".to_string(),
|
|
||||||
input_file: "1.in".to_string(),
|
|
||||||
cpu_time_limit: 1000,
|
|
||||||
real_time_limit: 1000,
|
|
||||||
memory_limit: 65535,
|
|
||||||
result: Some(runner::TestCaseResult::Accepted),
|
|
||||||
});
|
|
||||||
run_configs.test_cases.push(runner::TestCase {
|
|
||||||
answer_file: "2.ans".to_string(),
|
|
||||||
input_file: "2.in".to_string(),
|
|
||||||
cpu_time_limit: 1000,
|
|
||||||
real_time_limit: 1000,
|
|
||||||
memory_limit: 65535,
|
|
||||||
result: Some(runner::TestCaseResult::CompileError("compile error".to_string())),
|
|
||||||
});
|
|
||||||
run_configs.test_cases.push(runner::TestCase {
|
|
||||||
answer_file: "3.ans".to_string(),
|
|
||||||
input_file: "3.in".to_string(),
|
|
||||||
cpu_time_limit: 1000,
|
|
||||||
real_time_limit: 1000,
|
|
||||||
memory_limit: 65535,
|
|
||||||
result: Some(runner::TestCaseResult::WrongAnswer),
|
|
||||||
});
|
|
||||||
run_configs.test_cases.push(runner::TestCase {
|
|
||||||
answer_file: "4.ans".to_string(),
|
|
||||||
input_file: "4.in".to_string(),
|
|
||||||
cpu_time_limit: 1000,
|
|
||||||
real_time_limit: 1000,
|
|
||||||
memory_limit: 65535,
|
|
||||||
result: Some(runner::TestCaseResult::RuntimeError("runtime error".to_string())),
|
|
||||||
});
|
|
||||||
run_configs.test_cases.push(runner::TestCase {
|
|
||||||
answer_file: "5.ans".to_string(),
|
|
||||||
input_file: "5.in".to_string(),
|
|
||||||
cpu_time_limit: 1000,
|
|
||||||
real_time_limit: 1000,
|
|
||||||
memory_limit: 65535,
|
|
||||||
result: Some(runner::TestCaseResult::SystemError("system error".to_string())),
|
|
||||||
});
|
|
||||||
println!("{:?}", run_configs.test_cases[0].result);
|
|
||||||
} else {
|
|
||||||
println!("fork failure!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/process.rs
Normal file
110
src/process.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use super::error::{Error, Result};
|
||||||
|
use super::judger::{JudgeConfig, TestCase};
|
||||||
|
use handlebars::Handlebars;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
pub struct ExecArgs {
|
||||||
|
pub pathname: *const libc::c_char,
|
||||||
|
pub argv: *const *const libc::c_char,
|
||||||
|
pub envp: *const *const libc::c_char,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecArgs {
|
||||||
|
fn build<'b>(cmd: &'b String, judge_config: &'b JudgeConfig) -> Result<ExecArgs> {
|
||||||
|
let mut handlebars = Handlebars::new();
|
||||||
|
let _ = handlebars.register_template_string("build", cmd);
|
||||||
|
let mut data = BTreeMap::new();
|
||||||
|
|
||||||
|
data.insert("filename", judge_config.code.file.clone());
|
||||||
|
|
||||||
|
let formatted = match handlebars.render("build", &data) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => return Err(Error::TemplateRenderError(err)),
|
||||||
|
};
|
||||||
|
let splited = formatted.split_whitespace();
|
||||||
|
let splited: Vec<&str> = splited.collect();
|
||||||
|
|
||||||
|
if splited.len() < 1 {
|
||||||
|
return Err(Error::LanguageConfigError(formatted));
|
||||||
|
}
|
||||||
|
let pathname = splited[0].clone();
|
||||||
|
let pathname_str = match CString::new(pathname) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||||
|
};
|
||||||
|
let pathname = pathname_str.as_ptr();
|
||||||
|
|
||||||
|
let mut argv_vec: Vec<*const libc::c_char> = vec![];
|
||||||
|
for item in splited.iter() {
|
||||||
|
let cstr = match CString::new(item.clone()) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => return Err(Error::StringToCStringError(err)),
|
||||||
|
};
|
||||||
|
let cptr = cstr.as_ptr();
|
||||||
|
// 需要使用 mem::forget 来标记
|
||||||
|
// 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容
|
||||||
|
mem::forget(cstr);
|
||||||
|
argv_vec.push(cptr);
|
||||||
|
}
|
||||||
|
// argv 与 envp 的参数需要使用 NULL 来标记结束
|
||||||
|
argv_vec.push(ptr::null());
|
||||||
|
let argv: *const *const libc::c_char = argv_vec.as_ptr() as *const *const libc::c_char;
|
||||||
|
|
||||||
|
// env 环境变量传 null,默认不传递任何环境变量,后续有需求可以修改此处
|
||||||
|
let envp_vec: Vec<*const libc::c_char> = vec![ptr::null()];
|
||||||
|
let envp = envp_vec.as_ptr() as *const *const libc::c_char;
|
||||||
|
|
||||||
|
mem::forget(pathname_str);
|
||||||
|
mem::forget(argv_vec);
|
||||||
|
mem::forget(envp_vec);
|
||||||
|
|
||||||
|
Ok(ExecArgs {
|
||||||
|
pathname,
|
||||||
|
argv,
|
||||||
|
envp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ExecArgs {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// TODO: 将不安全的指针类型转换回内置类型,以便由 Rust 自动回收资源
|
||||||
|
// 如果能保证资源能够正确 drop,不会出现泄漏,则 ExecArgs 相关的函数不必标注 unsafe
|
||||||
|
println!("Dropping!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(judge_config: &JudgeConfig) {
|
||||||
|
for test_case in &judge_config.tests {
|
||||||
|
run_one(judge_config, test_case);
|
||||||
|
println!("{}", test_case);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_one(judge_config: &JudgeConfig, test_case: &TestCase) {
|
||||||
|
println!("running test case: {}", test_case.index);
|
||||||
|
|
||||||
|
let pid;
|
||||||
|
unsafe {
|
||||||
|
pid = libc::fork();
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid == 0 { // 子进程
|
||||||
|
// 此处如果出现 Error,则直接程序崩溃,父进程可以收集异常的信息
|
||||||
|
let exec_args = ExecArgs::build(
|
||||||
|
&judge_config.code.language.compile_command.clone(),
|
||||||
|
&judge_config,
|
||||||
|
).unwrap();
|
||||||
|
unsafe {
|
||||||
|
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
||||||
|
}
|
||||||
|
} else if pid > 0 { // 父进程
|
||||||
|
println!("pid: {}", pid);
|
||||||
|
} else { // 异常
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/result.rs
Normal file
22
src/result.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ResourceUsed {
|
||||||
|
pub time_used: u32,
|
||||||
|
pub memory_used: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TestCaseResult {
|
||||||
|
Accepted(ResourceUsed),
|
||||||
|
CompileError(ResourceUsed, String),
|
||||||
|
WrongAnswer(ResourceUsed),
|
||||||
|
RuntimeError(ResourceUsed, String),
|
||||||
|
SystemError(ResourceUsed, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TestCaseResult {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user