Merge pull request #1 from MeiK2333/next

Next
This commit is contained in:
MeiK 2020-04-26 16:24:21 +08:00 committed by GitHub
commit 0a931fbc27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 267 additions and 269 deletions

View File

@ -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(());
}
}

View File

@ -1,18 +1,11 @@
use std::fmt;
use std::fs;
use std::result;
use yaml_rust::{ScanError, Yaml, YamlLoader};
use std::io;
use yaml_rust::{Yaml, YamlLoader};
#[derive(Debug)]
pub enum Error {
YamlScanError(ScanError),
YamlParseError(String),
ReadFileError,
LanguageNotFound(String),
}
use super::error::{Error, Result};
pub type Result<T> = result::Result<T, Error>;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct LanguageConfig {
pub language: String,
pub version: String,
@ -20,6 +13,12 @@ pub struct LanguageConfig {
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 languages: Vec<LanguageConfig>,
}
@ -28,7 +27,7 @@ impl Config {
pub fn language_config_from_name(&self, name: &str) -> Result<LanguageConfig> {
for language in &self.languages {
if language.language == name {
return Ok(language.clone())
return Ok(language.clone());
}
}
Err(Error::LanguageNotFound(name.to_string()))
@ -101,7 +100,12 @@ impl Config {
pub fn load_from_file(filename: &str) -> Result<Config> {
let contents = match fs::read_to_string(filename) {
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)?;
Ok(config)

View File

@ -3,6 +3,14 @@ languages:
version: 7.5.0
compile_command: /usr/bin/gcc {{filename}} -o 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
version: 3.6.9
compile_command: /usr/bin/python3 -m compileall {{filename}}

53
src/error.rs Normal file
View 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),
}
}
}

View File

@ -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;
#[derive(Debug)]
pub enum Error {
StringToCStringError(NulError),
ReadFileError,
PathJoinError,
YamlScanError(ScanError),
YamlParseError(String),
UnknownJudgeType(String),
LanguageNotFound(String),
}
pub type Result<T> = result::Result<T, Error>;
use super::error::{Error, Result};
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use yaml_rust::{Yaml, YamlLoader};
pub struct TestCase {
pub index: u32,
pub input_file: String,
pub answer_file: String,
pub cpu_time_limit: u32,
pub real_time_limit: u32,
pub memory_limit: u32,
pub result: Option<TestCaseResult>,
}
#[derive(Debug)]
pub enum TestCaseResult {
Accepted,
CompileError(String),
WrongAnswer,
RuntimeError(String),
SystemError(String),
}
pub struct JudgeCode {
pub file: String,
pub language: config::LanguageConfig,
impl fmt::Display for TestCase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"index: {}
input_file: {}
answer_file: {}
time_limit: {}
memory_limit: {}
",
self.index, self.input_file, self.answer_file, self.cpu_time_limit, self.memory_limit
)
}
}
pub enum JudgeType {
@ -50,30 +35,20 @@ pub enum JudgeType {
Special,
}
pub struct TestConfig {
pub default_time_limit: u32,
pub default_real_time_limit: u32,
pub default_memory_limit: u32,
pub struct Code {
pub file: String,
pub language: config::LanguageConfig,
}
pub struct JudgeConfig {
pub tests: Vec<TestCase>,
pub judge_type: JudgeType,
pub extra_files: Vec<String>,
pub code: JudgeCode,
pub code: Code,
}
pub struct JudgeConfigs {
pub exec_file: String,
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> {
impl JudgeConfig {
fn load_yaml(global_config: &config::Config, yaml: &str) -> Result<JudgeConfig> {
let docs = match YamlLoader::load_from_str(yaml) {
Ok(value) => value,
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"] {
Yaml::Integer(value) => value.clone() as u32,
Yaml::BadValue => 65535,
@ -121,6 +87,8 @@ impl JudgeConfigs {
}
};
let mut tests: Vec<TestCase> = vec![];
// Yaml 解析库没有实现 enumerate 方法,因此此处使用 index 进行计数
let mut index = 1;
for case in test_cases {
let cpu_time_limit = match &case["time_limit"] {
Yaml::Integer(value) => value.clone() as u32,
@ -176,13 +144,14 @@ impl JudgeConfigs {
}
};
tests.push(TestCase {
index,
cpu_time_limit,
real_time_limit,
memory_limit,
result: None,
input_file,
answer_file,
});
index += 1;
}
let judge_type = match &doc["judge_type"] {
Yaml::String(value) => {
@ -269,7 +238,7 @@ impl JudgeConfigs {
))
}
};
JudgeCode { file, language }
Code { file, language }
}
Yaml::BadValue => {
return Err(Error::YamlParseError(
@ -283,20 +252,14 @@ impl JudgeConfigs {
}
};
Ok(TestConfig {
default_time_limit,
default_real_time_limit,
default_memory_limit,
Ok(JudgeConfig {
tests,
judge_type,
extra_files,
code,
})
}
/**
*
*/
pub fn load(global_config: &config::Config, path: &str) -> Result<JudgeConfigs> {
pub fn load(global_config: &config::Config, path: &str) -> Result<JudgeConfig> {
let dir = Path::new(&path);
let config = dir.join("config.yml");
@ -306,78 +269,13 @@ impl JudgeConfigs {
};
let config = match fs::read_to_string(&config) {
Ok(value) => value,
Err(_) => return Err(Error::ReadFileError),
};
let _config = JudgeConfigs::load_yaml(global_config, &config)?;
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);
Err(_) => {
return Err(Error::ReadFileError(
path.to_string(),
io::Error::last_os_error().raw_os_error(),
))
}
}
};
JudgeConfig::load_yaml(global_config, &config)
}
}

View File

@ -1,21 +1,12 @@
extern crate libc;
use handlebars::Handlebars;
use std::collections::BTreeMap;
use std::env;
mod config;
mod runner;
mod error;
mod judger;
mod process;
mod result;
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 config = if args.len() > 1 {
config::Config::load_from_file(&args[1])
@ -25,85 +16,21 @@ fn main() {
let config = match config {
Ok(value) => value,
Err(err) => {
eprintln!("{:?}", err);
return
eprintln!("{}", err);
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,
Err(err) => {
eprintln!("{:?}", err);
return
eprintln!("{}", err);
return;
}
};
println!("{}", judge_config.code.language);
process();
}
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!");
}
process::run(&judge_config);
}

110
src/process.rs Normal file
View 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
View 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)
}
}