diff --git a/src/cgroup.rs b/src/cgroup.rs deleted file mode 100644 index 30de676..0000000 --- a/src/cgroup.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::result; - -#[derive(Debug)] -pub enum Error { - Error(String), -} - -pub type Result = result::Result; - -pub struct Cgroup { - pub pid: u32, -} - -impl Cgroup { - pub fn new(pid: u32) -> Result { - return Ok(Cgroup { pid }); - } - pub fn attach(&self) -> Result<()> { - if self.pid == 0 { - return Err(Error::Error("Hello World!".to_string())); - } - return Ok(()); - } -} diff --git a/src/config.rs b/src/config.rs index d0718a5..8d5fe34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 = result::Result; - -#[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, } @@ -28,7 +27,7 @@ impl Config { pub fn language_config_from_name(&self, name: &str) -> Result { 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 { 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) diff --git a/src/default.yml b/src/default.yml index 5a600a0..c5f59ea 100644 --- a/src/default.yml +++ b/src/default.yml @@ -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}} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..2d984a1 --- /dev/null +++ b/src/error.rs @@ -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), + + UnknownJudgeType(String), + PathJoinError, + + StringToCStringError(NulError), + TemplateRenderError(RenderError), + LanguageConfigError(String), +} + +pub type Result = result::Result; + +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), + } + } +} diff --git a/src/runner.rs b/src/judger.rs similarity index 67% rename from src/runner.rs rename to src/judger.rs index dc13d41..ca74812 100644 --- a/src/runner.rs +++ b/src/judger.rs @@ -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 = result::Result; +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, } -#[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, pub judge_type: JudgeType, pub extra_files: Vec, - pub code: JudgeCode, + pub code: Code, } -pub struct JudgeConfigs { - pub exec_file: String, - pub exec_args: Vec, - pub test_cases: Vec, -} - -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 { +impl JudgeConfig { + fn load_yaml(global_config: &config::Config, yaml: &str) -> Result { 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 = 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 { + pub fn load(global_config: &config::Config, path: &str) -> Result { 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 { - 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) } } diff --git a/src/main.rs b/src/main.rs index 72beb6f..cd790f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 = 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); } diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..847a7aa --- /dev/null +++ b/src/process.rs @@ -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 { + 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 { // 异常 + } +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..ade2820 --- /dev/null +++ b/src/result.rs @@ -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) + } +}