This commit is contained in:
MeiK 2020-08-21 22:34:12 +08:00
parent d3ed13ea13
commit 525f566466
29 changed files with 1 additions and 1166 deletions

View File

@ -4,10 +4,4 @@ version = "0.1.0"
authors = ["MeiK <meik2333@gmail.com>"]
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"

View File

@ -1,6 +0,0 @@
# river
## TODO
- 资源限制
- 资源占用过多的错误

View File

@ -1 +0,0 @@
3

View File

@ -1 +0,0 @@
1 2

View File

@ -1 +0,0 @@
-3

View File

@ -1 +0,0 @@
-1 -2

View File

@ -1 +0,0 @@
2147483648

View File

@ -1 +0,0 @@
2147483647 1

View File

@ -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

View File

@ -1 +0,0 @@
Hello World!

View File

@ -1,8 +0,0 @@
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a + b);
return 0;
}

View File

@ -1,9 +0,0 @@
#include <iostream>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}

View File

@ -1,2 +0,0 @@
a, b = input().split()
print(int(a) + int(b))

View File

@ -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

View File

@ -1 +0,0 @@
Hello World!

View File

@ -1,6 +0,0 @@
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}

View File

@ -1,7 +0,0 @@
#include <iostream>
using namespace std;
int main() {
cout << "Hello World!" << endl;
return 0;
}

View File

@ -1,3 +0,0 @@
import time
time.sleep(3)
print('Hello World!')

View File

@ -1 +0,0 @@
echo "Hello World!"

View File

@ -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<LanguageConfig>,
}
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());
}
}
Err(Error::LanguageNotFound(name.to_string()))
}
fn load_yaml(yaml: &str) -> Result<Config> {
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<LanguageConfig> = 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<Config> {
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<Config> {
let config_yaml = include_str!("default.yml");
let config = Config::load_yaml(config_yaml)?;
Ok(config)
}
}

View File

@ -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}}

View File

@ -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<i32>),
UnknownJudgeType(String),
PathJoinError,
StringToCStringError(NulError),
TemplateRenderError(RenderError),
LanguageConfigError(String),
CreateTempDirError(io::Error),
// CloseTempDirError(io::Error),
CopyFileError(io::Error),
OsStringToStringError(OsString),
ForkError(Option<i32>),
WaitError(Option<i32>),
SystemTimeError(time::SystemTimeError),
RemoveFileError(String, Option<i32>),
OpenFileError(String, Option<i32>),
}
pub type Result<T> = result::Result<T, Error>;
pub fn errno_str(errno: Option<i32>) -> 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),
}
}
}

View File

@ -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<TestCase>,
pub judge_type: JudgeType,
pub extra_files: Vec<String>,
pub code: Code,
}
impl JudgeConfig {
fn load_yaml(global_config: &config::Config, yaml: &str, path: &str) -> Result<JudgeConfig> {
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<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,
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<Yaml> = 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<String> = 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<String>".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<JudgeConfig> {
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)
}
}

View File

@ -1,44 +1,3 @@
use std::env;
mod config;
mod error;
mod judger;
mod process;
mod result;
fn main() {
let args: Vec<String> = 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!");
}

View File

@ -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<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 环境变量传递当前进程环境变量
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<String> = 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 &copy_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<ExitStatus> {
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<ExitStatus> {
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<ExitStatus> {
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()));
}
}

View File

@ -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<P: AsRef<Path>>(
output_file: P,
answer_file: P,
exit_status: ExitStatus,
) -> Result<TestCaseResult> {
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)
}
}

View File

@ -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}}

View File

@ -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"
}
]
}