diff --git a/Cargo.toml b/Cargo.toml index 59cc60c..192c9a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,4 @@ version = "0.1.0" authors = ["MeiK "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -libc = "0.2" -handlebars = "3.0.1" -yaml-rust = "0.4" -tempfile = "3" diff --git a/README.md b/README.md index 19de6a2..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,6 +0,0 @@ -# river - -## TODO - -- 资源限制 -- 资源占用过多的错误 diff --git a/example/a_plus_b/1.ans b/example/a_plus_b/1.ans deleted file mode 100644 index e440e5c..0000000 --- a/example/a_plus_b/1.ans +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/example/a_plus_b/1.in b/example/a_plus_b/1.in deleted file mode 100644 index 1c6ae71..0000000 --- a/example/a_plus_b/1.in +++ /dev/null @@ -1 +0,0 @@ -1 2 \ No newline at end of file diff --git a/example/a_plus_b/2.ans b/example/a_plus_b/2.ans deleted file mode 100644 index 7d105a7..0000000 --- a/example/a_plus_b/2.ans +++ /dev/null @@ -1 +0,0 @@ --3 \ No newline at end of file diff --git a/example/a_plus_b/2.in b/example/a_plus_b/2.in deleted file mode 100644 index 6fa0970..0000000 --- a/example/a_plus_b/2.in +++ /dev/null @@ -1 +0,0 @@ --1 -2 \ No newline at end of file diff --git a/example/a_plus_b/3.ans b/example/a_plus_b/3.ans deleted file mode 100644 index 1ed5441..0000000 --- a/example/a_plus_b/3.ans +++ /dev/null @@ -1 +0,0 @@ -2147483648 \ No newline at end of file diff --git a/example/a_plus_b/3.in b/example/a_plus_b/3.in deleted file mode 100644 index 0d3660a..0000000 --- a/example/a_plus_b/3.in +++ /dev/null @@ -1 +0,0 @@ -2147483647 1 \ No newline at end of file diff --git a/example/a_plus_b/config.yml b/example/a_plus_b/config.yml deleted file mode 100644 index 8d200b7..0000000 --- a/example/a_plus_b/config.yml +++ /dev/null @@ -1,27 +0,0 @@ -time_limit: 1000 # ms -memory_limit: 65535 # kib -test_cases: - - in: 1.in - ans: 1.ans - - in: 2.in - ans: 2.ans - - in: 3.in - ans: 3.ans - time_limit: 300 - real_time_limit: 600 - memory_limit: 32767 -judge_type: standard # standard / special -# special_judge: -# file: special.cpp -# language: cpp -extra_files: - - hello.txt -code: - file: main.c - language: c -# code: -# file: main.cpp -# language: cpp -# code: -# file: main.py -# language: python diff --git a/example/a_plus_b/hello.txt b/example/a_plus_b/hello.txt deleted file mode 100644 index 980a0d5..0000000 --- a/example/a_plus_b/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World! diff --git a/example/a_plus_b/main.c b/example/a_plus_b/main.c deleted file mode 100644 index 4b0f4dc..0000000 --- a/example/a_plus_b/main.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main() { - int a, b; - scanf("%d %d", &a, &b); - printf("%d\n", a + b); - return 0; -} diff --git a/example/a_plus_b/main.cpp b/example/a_plus_b/main.cpp deleted file mode 100644 index aaa2314..0000000 --- a/example/a_plus_b/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -using namespace std; - -int main() { - int a, b; - cin >> a >> b; - cout << a + b << endl; - return 0; -} diff --git a/example/a_plus_b/main.py b/example/a_plus_b/main.py deleted file mode 100644 index 19ac7e3..0000000 --- a/example/a_plus_b/main.py +++ /dev/null @@ -1,2 +0,0 @@ -a, b = input().split() -print(int(a) + int(b)) diff --git a/example/hello_world/config.yml b/example/hello_world/config.yml deleted file mode 100644 index 6aa7726..0000000 --- a/example/hello_world/config.yml +++ /dev/null @@ -1,18 +0,0 @@ -time_limit: 1000 -memory_limit: 65535 -test_cases: - - in: data.in - ans: data.ans -judge_type: standard -# code: -# file: main.c -# language: c -# code: -# file: main.cpp -# language: cpp -code: - file: main.py - language: python -# code: -# file: main.sh -# language: bash diff --git a/example/hello_world/data.ans b/example/hello_world/data.ans deleted file mode 100644 index 980a0d5..0000000 --- a/example/hello_world/data.ans +++ /dev/null @@ -1 +0,0 @@ -Hello World! diff --git a/example/hello_world/data.in b/example/hello_world/data.in deleted file mode 100644 index e69de29..0000000 diff --git a/example/hello_world/main.c b/example/hello_world/main.c deleted file mode 100644 index 574e069..0000000 --- a/example/hello_world/main.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - printf("Hello World!\n"); - return 0; -} \ No newline at end of file diff --git a/example/hello_world/main.cpp b/example/hello_world/main.cpp deleted file mode 100644 index 90514e3..0000000 --- a/example/hello_world/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include -using namespace std; - -int main() { - cout << "Hello World!" << endl; - return 0; -} \ No newline at end of file diff --git a/example/hello_world/main.py b/example/hello_world/main.py deleted file mode 100644 index 3efc5c6..0000000 --- a/example/hello_world/main.py +++ /dev/null @@ -1,3 +0,0 @@ -import time -time.sleep(3) -print('Hello World!') \ No newline at end of file diff --git a/example/hello_world/main.sh b/example/hello_world/main.sh deleted file mode 100644 index 5543a6b..0000000 --- a/example/hello_world/main.sh +++ /dev/null @@ -1 +0,0 @@ -echo "Hello World!" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 8d5fe34..0000000 --- a/src/config.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::fmt; -use std::fs; -use std::io; -use yaml_rust::{Yaml, YamlLoader}; - -use super::error::{Error, Result}; - -#[derive(Clone, Debug)] -pub struct LanguageConfig { - pub language: String, - pub version: String, - pub compile_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 languages: Vec, -} - -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()); - } - } - Err(Error::LanguageNotFound(name.to_string())) - } - fn load_yaml(yaml: &str) -> Result { - let docs = match YamlLoader::load_from_str(yaml) { - Ok(value) => value, - Err(err) => return Err(Error::YamlScanError(err)), - }; - let doc = &docs[0]; - - // 读取语言配置 - let mut languages: Vec = vec![]; - let yaml_languages = match &doc["languages"] { - Yaml::Array(value) => value, - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 languages 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "解析错误,languages 字段的类型应该为 Array".to_string(), - )) - } - }; - for yaml_language in yaml_languages { - let language = match &yaml_language["language"] { - Yaml::String(value) => value.clone(), - _ => { - return Err(Error::YamlParseError( - "解析错误,language 字段的类型应该为 String".to_string(), - )) - } - }; - let version = match &yaml_language["version"] { - Yaml::String(value) => value.clone(), - _ => { - return Err(Error::YamlParseError( - "version 字段的类型应该为 String".to_string(), - )) - } - }; - let compile_command = match &yaml_language["compile_command"] { - Yaml::String(value) => value.clone(), - _ => { - return Err(Error::YamlParseError( - "compile_command 字段的类型应该为 String".to_string(), - )) - } - }; - let run_command = match &yaml_language["run_command"] { - Yaml::String(value) => value.clone(), - _ => { - return Err(Error::YamlParseError( - "run_command 字段的类型应该为 String".to_string(), - )) - } - }; - languages.push(LanguageConfig { - language, - version, - compile_command, - run_command, - }); - } - - Ok(Config { languages }) - } - pub fn load_from_file(filename: &str) -> Result { - let contents = match fs::read_to_string(filename) { - Ok(value) => value, - Err(_) => { - return Err(Error::ReadFileError( - filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - let config = Config::load_yaml(&contents)?; - Ok(config) - } - pub fn default() -> Result { - let config_yaml = include_str!("default.yml"); - let config = Config::load_yaml(config_yaml)?; - Ok(config) - } -} diff --git a/src/default.yml b/src/default.yml deleted file mode 100644 index c4c3165..0000000 --- a/src/default.yml +++ /dev/null @@ -1,17 +0,0 @@ -languages: - - language: c - version: 7.5.0 - compile_command: /usr/bin/gcc {{filename}} -o a.out -O2 - run_command: ./a.out - - language: cpp - version: 7.5.0 - compile_command: /usr/bin/g++ {{filename}} -o a.out -O2 - 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}} - run_command: /usr/bin/python3 {{filename}} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 2d28381..0000000 --- a/src/error.rs +++ /dev/null @@ -1,84 +0,0 @@ -use handlebars::RenderError; -use libc::strerror; -use std::ffi::{CStr, NulError, OsString}; -use std::fmt; -use std::io; -use std::result; -use std::time; -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), - CreateTempDirError(io::Error), - // CloseTempDirError(io::Error), - CopyFileError(io::Error), - OsStringToStringError(OsString), - ForkError(Option), - WaitError(Option), - SystemTimeError(time::SystemTimeError), - RemoveFileError(String, Option), - - OpenFileError(String, Option), -} - -pub type Result = result::Result; - -pub fn errno_str(errno: Option) -> String { - match errno { - Some(no) => { - let stre = unsafe { strerror(no) }; - let c_str: &CStr = unsafe { CStr::from_ptr(stre) }; - c_str.to_str().unwrap().to_string() - } - _ => "Unknown Error!".to_string(), - } -} - -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 = errno_str(errno); - write!(f, "ReadFileError: `{}` {}", filename, reason) - } - Error::OpenFileError(ref filename, errno) => { - let reason = errno_str(errno); - write!(f, "OpenFileError: `{}` {}", filename, reason) - } - Error::UnknownJudgeType(ref judge_type) => { - write!(f, "UnknownJudgeType: {}", judge_type) - } - Error::PathJoinError => write!(f, "PathJoinError"), - Error::ForkError(errno) => { - let reason = errno_str(errno); - write!(f, "ForkError: {}", reason) - } - Error::WaitError(errno) => { - let reason = errno_str(errno); - write!(f, "WaitError: {}", reason) - } - Error::RemoveFileError(ref filename, errno) => { - let reason = errno_str(errno); - write!(f, "RemoveFileError: `{}` {}", filename, reason) - } - _ => write!(f, "{:?}", self), - } - } -} diff --git a/src/judger.rs b/src/judger.rs deleted file mode 100644 index 6183423..0000000 --- a/src/judger.rs +++ /dev/null @@ -1,286 +0,0 @@ -use super::config; -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 output_file: String, - pub answer_file: String, - pub cpu_time_limit: u32, - pub real_time_limit: u32, - pub memory_limit: u32, -} - -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 { - Standard, - Special, -} - -pub struct Code { - pub file: String, - pub language: config::LanguageConfig, -} - -pub struct JudgeConfig { - pub config_dir: String, - pub tests: Vec, - pub judge_type: JudgeType, - pub extra_files: Vec, - pub code: Code, -} - -impl JudgeConfig { - fn load_yaml(global_config: &config::Config, yaml: &str, path: &str) -> Result { - let docs = match YamlLoader::load_from_str(yaml) { - Ok(value) => value, - Err(err) => return Err(Error::YamlScanError(err)), - }; - let doc = &docs[0]; - - let default_time_limit = match &doc["time_limit"] { - Yaml::Integer(value) => value.clone() as u32, - Yaml::BadValue => 1000, - _ => { - return Err(Error::YamlParseError( - "解析错误,time_limit 字段的类型应该为 Integer".to_string(), - )) - } - }; - let default_memory_limit = match &doc["memory_limit"] { - Yaml::Integer(value) => value.clone() as u32, - Yaml::BadValue => 65535, - _ => { - return Err(Error::YamlParseError( - "解析错误,memory_limit 字段的类型应该为 Integer".to_string(), - )) - } - }; - let test_cases = match &doc["test_cases"] { - Yaml::Array(value) => value, - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 test_cases 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "test_cases 字段的类型应该为 Array".to_string(), - )) - } - }; - 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, - Yaml::BadValue => default_time_limit, - _ => { - return Err(Error::YamlParseError( - "解析错误,time_limit 字段的类型应该为 Integer".to_string(), - )) - } - }; - let real_time_limit = match &case["real_time_limit"] { - Yaml::Integer(value) => value.clone() as u32, - Yaml::BadValue => cpu_time_limit * 2 + 5000, - _ => { - return Err(Error::YamlParseError( - "解析错误,real_time_limit 字段的类型应该为 Integer".to_string(), - )) - } - }; - let memory_limit = match &case["memory_limit"] { - Yaml::Integer(value) => value.clone() as u32, - Yaml::BadValue => default_memory_limit, - _ => { - return Err(Error::YamlParseError( - "解析错误,memory_limit 字段的类型应该为 Integer".to_string(), - )) - } - }; - let input_file = match &case["in"] { - Yaml::String(value) => value.clone(), - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 test_cases::in 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "test_cases::in 字段的类型应该为 String".to_string(), - )) - } - }; - let answer_file = match &case["ans"] { - Yaml::String(value) => value.clone(), - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 test_cases::ans 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "test_cases::ans 字段的类型应该为 String".to_string(), - )) - } - }; - let output_file = "output.txt".to_string(); - tests.push(TestCase { - index, - cpu_time_limit, - real_time_limit, - memory_limit, - input_file, - output_file, - answer_file, - }); - index += 1; - } - let judge_type = match &doc["judge_type"] { - Yaml::String(value) => { - if value == "standard" { - JudgeType::Standard - } else if value == "special" { - JudgeType::Special - } else { - return Err(Error::UnknownJudgeType(value.clone())); - } - } - Yaml::BadValue => JudgeType::Standard, - _ => { - return Err(Error::YamlParseError( - "judge_type 字段的类型应该为 String".to_string(), - )) - } - }; - let default_extra_files: Vec = vec![]; - let extra_files_yaml = match &doc["extra_files"] { - Yaml::Array(value) => value, - Yaml::BadValue => &default_extra_files, - _ => { - return Err(Error::YamlParseError( - "extra_files 字段的类型应该为 Array".to_string(), - )) - } - }; - let mut extra_files: Vec = vec![]; - for file in extra_files_yaml { - let name = match &file { - Yaml::String(value) => value.clone(), - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 extra_files 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "extra_files 字段的类型应该为 Array".to_string(), - )) - } - }; - extra_files.push(name); - } - let code = match &doc["code"] { - Yaml::Hash(value) => { - let file = match value.get(&Yaml::from_str("file")) { - Some(Yaml::String(val)) => val.clone(), - Some(Yaml::BadValue) => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 code::file 字段".to_string(), - )) - } - None => { - return Err(Error::YamlParseError( - "必须提供 code::file 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "code::file 字段的类型应该为 String".to_string(), - )) - } - }; - let language = match value.get(&Yaml::from_str("language")) { - Some(Yaml::String(val)) => match global_config.language_config_from_name(val) { - Ok(v) => v, - Err(_) => return Err(Error::LanguageNotFound(val.clone())), - }, - Some(Yaml::BadValue) => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 code::language 字段".to_string(), - )) - } - None => { - return Err(Error::YamlParseError( - "必须提供 code::language 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "code::language 字段的类型应该为 String".to_string(), - )) - } - }; - Code { file, language } - } - Yaml::BadValue => { - return Err(Error::YamlParseError( - "解析错误,无法解析到 code 字段".to_string(), - )) - } - _ => { - return Err(Error::YamlParseError( - "code 字段的类型应该为 Hash".to_string(), - )) - } - }; - - Ok(JudgeConfig { - config_dir: path.into(), - tests, - judge_type, - extra_files, - code, - }) - } - pub fn load(global_config: &config::Config, path: &str) -> Result { - let dir = Path::new(&path); - - let config = dir.join("config.yml"); - let config = match config.as_path().to_str() { - Some(value) => value, - None => return Err(Error::PathJoinError), - }; - let config = match fs::read_to_string(&config) { - Ok(value) => value, - Err(_) => { - return Err(Error::ReadFileError( - path.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - JudgeConfig::load_yaml(global_config, &config, &path) - } -} diff --git a/src/main.rs b/src/main.rs index 7228ab1..47ad8c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,44 +1,3 @@ -use std::env; - -mod config; -mod error; -mod judger; -mod process; -mod result; - fn main() { - let args: Vec = env::args().collect(); - let config = if args.len() > 1 { - config::Config::load_from_file(&args[1]) - } else { - config::Config::default() - }; - let config = match config { - Ok(value) => value, - Err(err) => { - eprintln!("{}", err); - return; - } - }; - let language = config.language_config_from_name("python"); - println!("{}", language.unwrap()); - - let judge_config = match judger::JudgeConfig::load(&config, "example/a_plus_b") { - Ok(value) => value, - Err(err) => { - eprintln!("{}", err); - return; - } - }; - println!("{}", judge_config.code.language); - - // TODO: 处理 SE,处理交互问题 - match process::run(&judge_config) { - Ok(_) => {} - Err(err) => { - // TODO: SE - eprintln!("{}", err); - return; - } - }; + println!("Hello World!"); } diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index 3190be6..0000000 --- a/src/process.rs +++ /dev/null @@ -1,323 +0,0 @@ -extern crate libc; - -use super::error::{errno_str, Error, Result}; -use super::judger::{JudgeConfig, TestCase}; -use super::result::{ExitStatus, TestCaseResult}; - -use handlebars::Handlebars; -use libc::{c_long, suseconds_t, time_t}; -use std::collections::BTreeMap; -use std::env; -use std::ffi::CString; -use std::fs; -use std::io; -use std::mem; -use std::path::Path; -use std::ptr; -use std::time::SystemTime; -use tempfile::tempdir; - -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(cmd: &String, judge_config: &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 环境变量传递当前进程环境变量 - let mut envp_vec: Vec<*const libc::c_char> = vec![]; - for (key, value) in env::vars_os() { - let mut key = match key.to_str() { - Some(val) => val.to_string(), - None => return Err(Error::OsStringToStringError(key)), - }; - let value = match value.to_str() { - Some(val) => val.to_string(), - None => return Err(Error::OsStringToStringError(value)), - }; - key.push_str("="); - key.push_str(&value); - let cstr = match CString::new(key) { - Ok(value) => value, - Err(err) => return Err(Error::StringToCStringError(err)), - }; - let cptr = cstr.as_ptr(); - // 需要使用 mem::forget 来标记 - // 否则在此次循环结束后,cstr 就会被回收,后续 exec 函数无法通过指针获取到字符串内容 - mem::forget(cstr); - envp_vec.push(cptr); - } - envp_vec.push(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) -> Result<()> { - let pwd = match tempdir() { - Ok(val) => val, - Err(err) => return Err(Error::CreateTempDirError(err)), - }; - let from_dir = Path::new(&judge_config.config_dir); - let to_dir = pwd.path(); - - let mut copy_files: Vec = vec![]; - copy_files.push(judge_config.code.file.clone()); - copy_files.extend_from_slice(&judge_config.extra_files); - for test_case in &judge_config.tests { - copy_files.push(test_case.input_file.clone()); - } - // copy file - for file in ©_files { - let from_file = from_dir.join(file); - let to_file = to_dir.join(file); - match fs::copy(from_file, to_file) { - Ok(_) => {} - Err(err) => return Err(Error::CopyFileError(err)), - }; - } - // TODO: 处理 CE 与 RE(编译期间程序运行出错为 CE,运行期间出错为 RE) - let compile_status = compile(judge_config, to_dir)?; - if !compile_status.exited { - // TODO: CE - } - - for test_case in &judge_config.tests { - let exit_status = run_one(judge_config, test_case, to_dir)?; - let output_file_path = to_dir.join(&test_case.output_file); - let answer_file_path = from_dir.join(&test_case.answer_file); - if !exit_status.exited { - // TODO: RE - } - let res = TestCaseResult::standard(&output_file_path, &answer_file_path, exit_status); - println!("{:?}", res); - match fs::remove_file(output_file_path) { - Ok(_) => {} - Err(_) => { - return Err(Error::RemoveFileError( - test_case.output_file.clone(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - } - println!("{:?}", pwd); - // match pwd.close() { - // Ok(_) => {} - // Err(err) => return Err(Error::CloseTempDirError(err)), - // }; - Ok(()) -} - -pub fn wait(pid: i32) -> Result { - let start = SystemTime::now(); - let mut status = 0; - let mut rusage = libc::rusage { - ru_utime: libc::timeval { - tv_sec: 0 as time_t, - tv_usec: 0 as suseconds_t, - }, - ru_stime: libc::timeval { - tv_sec: 0 as time_t, - tv_usec: 0 as suseconds_t, - }, - ru_maxrss: 0 as c_long, - ru_ixrss: 0 as c_long, - ru_idrss: 0 as c_long, - ru_isrss: 0 as c_long, - ru_minflt: 0 as c_long, - ru_majflt: 0 as c_long, - ru_nswap: 0 as c_long, - ru_inblock: 0 as c_long, - ru_oublock: 0 as c_long, - ru_msgsnd: 0 as c_long, - ru_msgrcv: 0 as c_long, - ru_nsignals: 0 as c_long, - ru_nvcsw: 0 as c_long, - ru_nivcsw: 0 as c_long, - }; - unsafe { - let val = libc::wait4(pid, &mut status, 0, &mut rusage); - if val < 0 { - return Err(Error::WaitError(io::Error::last_os_error().raw_os_error())); - } - } - let time_used = rusage.ru_utime.tv_sec * 1000 - + rusage.ru_utime.tv_usec / 1000 - + rusage.ru_stime.tv_sec * 1000 - + rusage.ru_stime.tv_usec / 1000; - let memory_used = rusage.ru_maxrss; - let real_time_used = match start.elapsed() { - Ok(elapsed) => elapsed.as_millis(), - Err(err) => return Err(Error::SystemTimeError(err)), - }; - let signal = unsafe { - if libc::WIFSIGNALED(status) { - libc::WTERMSIG(status) - } else if libc::WIFSTOPPED(status) { - libc::WSTOPSIG(status) - } else { - 0 - } - }; - let exited = unsafe { libc::WIFEXITED(status) }; - if exited { - status = unsafe { libc::WEXITSTATUS(status) }; - } - let coredump = unsafe { libc::WCOREDUMP(status) }; - Ok(ExitStatus { - rusage, - status, - signal, - exited, - coredump, - time_used, - real_time_used, - memory_used, - }) -} - -pub unsafe fn dup(filename: &str, to: libc::c_int, flag: libc::c_int, mode: libc::c_int) { - let filename_str = CString::new(filename).unwrap(); - let filename = filename_str.as_ptr(); - let fd = libc::open(filename, flag, mode); - if fd < 0 { - let err = io::Error::last_os_error().raw_os_error(); - eprintln!("open filure!"); - panic!(errno_str(err)); - } - if libc::dup2(fd, to) < 0 { - let err = io::Error::last_os_error().raw_os_error(); - eprintln!("dup2 filure!"); - panic!(errno_str(err)); - } -} - -pub fn run_one( - judge_config: &JudgeConfig, - test_case: &TestCase, - workdir: &Path, -) -> Result { - let pid; - - // 这个操作就需要 1-2ms 左右,此处预先完成,不占用子进程运行时间 - // 因此,父进程需要 drop 这个结构,需要保证没有内存泄漏 - let exec_args = ExecArgs::build( - &judge_config.code.language.run_command.clone(), - &judge_config, - )?; - unsafe { - pid = libc::fork(); - } - if pid == 0 { - // 子进程 - // 此处如果出现 Error,则直接程序崩溃,父进程可以收集异常的信息 - - // 修改工作目录 - env::set_current_dir(workdir).unwrap(); - unsafe { - dup( - &test_case.input_file, - libc::STDIN_FILENO, - libc::O_RDONLY, - 0o644, - ); - dup( - &test_case.output_file, - libc::STDOUT_FILENO, - libc::O_CREAT | libc::O_RDWR, - 0o644, - ); - libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp); - } - // 理论上,不会走到这里,在上一句 exec 后,程序就已经被替换为待执行程序了 - // 所以, How dare you! - panic!("How dare you!"); - } else if pid > 0 { - // 父进程 - return Ok(wait(pid)?); - } else { - // 异常 - return Err(Error::ForkError(io::Error::last_os_error().raw_os_error())); - } -} - -pub fn compile(judge_config: &JudgeConfig, workdir: &Path) -> Result { - let pid; - unsafe { - pid = libc::fork(); - } - if pid == 0 { - env::set_current_dir(workdir).unwrap(); - 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); - } - panic!("How dare you!"); - } else if pid > 0 { - return Ok(wait(pid)?); - } else { - return Err(Error::ForkError(io::Error::last_os_error().raw_os_error())); - } -} diff --git a/src/result.rs b/src/result.rs deleted file mode 100644 index 73ab3c0..0000000 --- a/src/result.rs +++ /dev/null @@ -1,142 +0,0 @@ -extern crate libc; - -use super::error::{Error, Result}; -use std::fmt; -use std::fs::File; -use std::io::{self, BufRead, BufReader}; -use std::path::Path; - -pub struct ExitStatus { - pub rusage: libc::rusage, - pub status: i32, - pub signal: i32, - pub exited: bool, - pub coredump: bool, - pub time_used: i64, - pub real_time_used: u128, - pub memory_used: i64, -} - -#[derive(Debug, Clone)] -pub enum TestCaseResult { - Accepted, - CompileError(String), - WrongAnswer, - RuntimeError(String), - SystemError(String), -} - -impl fmt::Display for TestCaseResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl TestCaseResult { - pub fn standard>( - output_file: P, - answer_file: P, - exit_status: ExitStatus, - ) -> Result { - println!("\n----------------------------------\n"); - println!("time used:\t{}", exit_status.time_used); - println!("real time used:\t{}", exit_status.real_time_used); - println!("memory used:\t{}", exit_status.rusage.ru_maxrss); - println!("status:\t{}", exit_status.status); - println!("signal:\t{}", exit_status.signal); - println!("exited:\t{}", exit_status.exited); - - let output_filename = match output_file.as_ref().to_str() { - Some(val) => val, - None => "", - }; - let answer_filename = match answer_file.as_ref().to_str() { - Some(val) => val, - None => "", - }; - - let output_file = match File::open(output_filename) { - Ok(val) => val, - Err(_) => { - return Err(Error::OpenFileError( - output_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - let mut output_reader = BufReader::new(output_file); - let mut output_buffer = String::new(); - let mut output_bytes; - - let answer_file = match File::open(answer_filename) { - Ok(val) => val, - Err(_) => { - return Err(Error::OpenFileError( - answer_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - let mut answer_reader = BufReader::new(answer_file); - let mut answer_buffer = String::new(); - let mut answer_bytes; - - loop { - output_bytes = match output_reader.read_line(&mut output_buffer) { - Ok(val) => val, - Err(_) => { - return Err(Error::ReadFileError( - output_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - answer_bytes = match answer_reader.read_line(&mut answer_buffer) { - Ok(val) => val, - Err(_) => { - return Err(Error::ReadFileError( - answer_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - if output_bytes == 0 || answer_bytes == 0 { - break; - } - if output_buffer.trim_end() != answer_buffer.trim_end() { - return Ok(TestCaseResult::WrongAnswer); - } - } - // 末尾的空白字符不影响结果 - while output_bytes != 0 { - if output_buffer.trim() != "" { - return Ok(TestCaseResult::WrongAnswer); - } - output_bytes = match output_reader.read_line(&mut output_buffer) { - Ok(val) => val, - Err(_) => { - return Err(Error::ReadFileError( - output_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - } - while answer_bytes != 0 { - if answer_buffer.trim() != "" { - return Ok(TestCaseResult::WrongAnswer); - } - answer_bytes = match answer_reader.read_line(&mut answer_buffer) { - Ok(val) => val, - Err(_) => { - return Err(Error::ReadFileError( - answer_filename.to_string(), - io::Error::last_os_error().raw_os_error(), - )) - } - }; - } - - Ok(TestCaseResult::Accepted) - } -} diff --git a/tests/config.yml b/tests/config.yml deleted file mode 100644 index 5a600a0..0000000 --- a/tests/config.yml +++ /dev/null @@ -1,9 +0,0 @@ -languages: - - language: c - version: 7.5.0 - compile_command: /usr/bin/gcc {{filename}} -o a.out - run_command: ./a.out - - language: python - version: 3.6.9 - compile_command: /usr/bin/python3 -m compileall {{filename}} - run_command: /use/bin/python3 {{filename}} diff --git a/tests/response.json b/tests/response.json deleted file mode 100644 index 7346996..0000000 --- a/tests/response.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "compile": { - "time": 120, - "memory": 4096, - "status": 0, - "signal": 0, - "exited": true, - "output": "main.c: In function ‘main’: \nmain.c:5:5: warning: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Wunused-result] \n scanf(\"%d %d\", &a, &b);" - }, - "tests": [ - { - "time": 0, - "memory": 1576, - "status": 0, - "signal": 0, - "exited": true, - "result": "Accepted" - }, - { - "time": 0, - "memory": 1576, - "status": 0, - "signal": 0, - "exited": true, - "result": "WrongAnswer" - }, - { - "time": 0, - "memory": 1576, - "status": 0, - "signal": 0, - "exited": true, - "result": "Accepted" - }, - { - "time": 0, - "memory": 1576, - "status": 139, - "signal": 11, - "exited": false, - "result": "RuntimeError" - } - ] -} \ No newline at end of file