This commit is contained in:
MeiK 2020-10-22 10:39:29 +00:00
parent 8ba1eb2d90
commit c35eb6ae29
8 changed files with 79 additions and 245 deletions

View File

@ -68,7 +68,7 @@ message JudgeResponse {
} }
// int32 errno = 4; // int32 errno = 4;
// int32 exit_code = 5; // int32 exit_code = 5;
// string stdout = 6; string stdout = 6;
// string stderr = 7; string stderr = 7;
string errmsg = 8; string errmsg = 8;
} }

3
src/config.rs Normal file
View File

@ -0,0 +1,3 @@
pub static STDIN_FILENAME: &str = "stdin.txt";
pub static STDOUT_FILENAME: &str = "stdout.txt";
pub static STDERR_FILENAME: &str = "stderr.txt";

View File

@ -14,6 +14,7 @@ pub enum Error {
CreateTempDirError(io::Error), CreateTempDirError(io::Error),
LanguageNotFound(i32), LanguageNotFound(i32),
FileWriteError(io::Error), FileWriteError(io::Error),
FileReadError(io::Error),
ChannelRecvError, ChannelRecvError,
StringToCStringError(NulError), StringToCStringError(NulError),
OsStringToStringError(OsString), OsStringToStringError(OsString),
@ -57,6 +58,8 @@ pub fn system_error(err: Error) -> JudgeResponse {
time_used: 0, time_used: 0,
memory_used: 0, memory_used: 0,
state: Some(State::Result(JudgeResult::SystemError as i32)), state: Some(State::Result(JudgeResult::SystemError as i32)),
stdout: "".into(),
stderr: "".into(),
errmsg: format!("{}", err).into(), errmsg: format!("{}", err).into(),
} }
} }

View File

@ -1,3 +1,4 @@
use super::config::{STDERR_FILENAME, STDOUT_FILENAME};
use super::error::{Error, Result}; use super::error::{Error, Result};
use super::process::Process; use super::process::Process;
use super::runner::RunnerStatus; use super::runner::RunnerStatus;
@ -14,6 +15,8 @@ impl JudgeResponse {
time_used: 0, time_used: 0,
memory_used: 0, memory_used: 0,
errmsg: "".into(), errmsg: "".into(),
stdout: "".into(),
stderr: "".into(),
state: Some(State::Status(JudgeStatus::Running as i32)), state: Some(State::Status(JudgeStatus::Running as i32)),
} }
} }
@ -40,27 +43,17 @@ pub async fn judger(
Some(Language::Go) => "./a.out", Some(Language::Go) => "./a.out",
None => return Err(Error::LanguageNotFound(request.language)), None => return Err(Error::LanguageNotFound(request.language)),
}; };
let mut process = Process::new( let process = Process::new(
cmd.to_string(), cmd.to_string(),
path.to_path_buf(), path.to_path_buf(),
&data.in_data,
data.time_limit, data.time_limit,
data.memory_limit, data.memory_limit,
)?; )?;
// 设置输入数据
process.set_stdin(&data.in_data)?;
// 开始执行并等待返回结果 // 开始执行并等待返回结果
let runner = process.runner.clone(); let runner = process.runner.clone();
let status = runner.await?; let status = runner.await?;
// TODO: 对比答案
// 此处是为了消除 warning 的临时代码
let reader = process.stdout_reader()?;
let mut buf: [u8; 1024] = [0; 1024];
reader.readline(&mut buf)?;
let reader = process.stderr_reader()?;
let mut buf: [u8; 1024] = [0; 1024];
reader.readline(&mut buf)?;
// TODO: 根据返回值等判断 tle、mle、re 等状态 // TODO: 根据返回值等判断 tle、mle、re 等状态
// TODO: 对比答案,检查结果 // TODO: 对比答案,检查结果
@ -92,33 +85,45 @@ pub async fn compile(
return Err(Error::FileWriteError(e)); return Err(Error::FileWriteError(e));
}; };
// TODO: 使用配置文件
let cmd = match Language::from_i32(request.language) { let cmd = match Language::from_i32(request.language) {
Some(Language::C) => "/usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static", Some(Language::C) => "/usr/bin/gcc main.c -o a.out -Wall -O2 -std=c99 --static",
Some(Language::Cpp) => "/usr/bin/g++ main.cpp -O2 -Wall --static -o a.out --std=gnu++17", Some(Language::Cpp) => "/usr/bin/g++ main.cpp -O2 -Wall --static -o a.out --std=gnu++17",
Some(Language::Python) => "/usr/bin/python3 -m compileall main.py", Some(Language::Python) => "/usr/bin/python3 -m compileall main.py",
Some(Language::Rust) => "/usr/bin/rustc main.rs -o a.out -C opt-level=2", Some(Language::Rust) => "/usr/bin/rustc main.rs -o a.out -C opt-level=2",
// 无需编译的语言直接返回
// TODO: eslint...... // TODO: eslint......
Some(Language::Node) => return Ok(resp), Some(Language::Node) => "/bin/echo hello",
Some(Language::TypeScript) => "/usr/bin/tsc", Some(Language::TypeScript) => "/usr/bin/tsc",
Some(Language::Go) => "/usr/bin/go build -ldflags \"-s -w\" main.go", Some(Language::Go) => "/usr/bin/go build -ldflags \"-s -w\" main.go",
None => return Err(Error::LanguageNotFound(request.language)), None => return Err(Error::LanguageNotFound(request.language)),
}; };
// 编译的资源限制为固定的 debug!("build command: {}", cmd);
let process = Process::new(cmd.to_string(), path.to_path_buf(), 10000, 64 * 1024)?; let v = vec![];
let process = Process::new(
cmd.to_string(),
path.to_path_buf(),
&v,
// 编译的资源限制为固定的
10000,
64 * 1024,
)?;
let runner = process.runner.clone(); let runner = process.runner.clone();
let status = runner.await?; let status = runner.await?;
resp.set_process_status(&status); resp.set_process_status(&status);
if status.exit_code != 0 { if status.exit_code != 0 {
debug!("compile exit code: {}", status.exit_code); debug!("compile exit code: {}", status.exit_code);
let reader = process.stdout_reader()?; // 从 stdout 和 stderr 中获取错误信息
let stdout = reader.read()?; let stdout = match fs::read_to_string(path.join(STDOUT_FILENAME)).await {
debug!("stdout {}", stdout); Ok(val) => val,
let reader = process.stderr_reader()?; Err(e) => return Err(Error::FileReadError(e)),
let stderr = reader.read()?; };
debug!("stderr {}", stderr); let stderr = match fs::read_to_string(path.join(STDERR_FILENAME)).await {
resp.errmsg = stderr; Ok(val) => val,
Err(e) => return Err(Error::FileReadError(e)),
};
resp.stdout = stdout;
resp.stderr = stderr;
resp.state = Some(State::Result(JudgeResult::CompileError as i32)); resp.state = Some(State::Result(JudgeResult::CompileError as i32));
} else { } else {
resp.state = Some(State::Result(JudgeResult::Accepted as i32)); resp.state = Some(State::Result(JudgeResult::Accepted as i32));

View File

@ -13,11 +13,11 @@ use tempfile::tempdir_in;
use tonic::transport::Server; use tonic::transport::Server;
use tonic::{Request, Response, Status}; use tonic::{Request, Response, Status};
mod config;
mod error; mod error;
mod exec_args; mod exec_args;
mod judger; mod judger;
mod process; mod process;
mod reader;
mod runner; mod runner;
pub mod river { pub mod river {

View File

@ -1,10 +1,8 @@
use super::config::STDIN_FILENAME;
use super::error::{Error, Result}; use super::error::{Error, Result};
use super::reader::Reader;
use super::runner::Runner; use super::runner::Runner;
use libc; use libc;
use std::ffi::c_void; use std::fs;
use std::ffi::CString;
use std::os::raw::c_char;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -17,57 +15,27 @@ pub struct Process {
pub runner: Runner, pub runner: Runner,
} }
fn path_buf_str(path_buf: &PathBuf) -> Result<String> {
let file = match path_buf.file_stem() {
Some(stem) => match stem.to_str() {
Some(val) => val.to_string(),
None => return Err(Error::PathBufToStringError(path_buf.clone())),
},
None => return Err(Error::PathBufToStringError(path_buf.clone())),
};
Ok(file)
}
impl Process { impl Process {
pub fn new( pub fn new(
cmd: String, cmd: String,
workdir: PathBuf, workdir: PathBuf,
in_data: &Vec<u8>,
time_limit: i32, time_limit: i32,
memory_limit: i32, memory_limit: i32,
) -> Result<Process> { ) -> Result<Process> {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let memfile = path_buf_str(&workdir)?; debug!("writing input file");
let outfile = match CString::new(format!("{}{}", "stdout", memfile)) { // TODO: 此处同步写入文件,后续可以修改为异步写入,防止阻塞整体流程
Ok(val) => val, if let Err(e) = fs::write(workdir.join(STDIN_FILENAME), &in_data) {
Err(e) => return Err(Error::StringToCStringError(e)), return Err(Error::FileWriteError(e));
};
let errfile = match CString::new(format!("{}{}", "stderr", memfile)) {
Ok(val) => val,
Err(e) => return Err(Error::StringToCStringError(e)),
};
let stdout_fd = unsafe {
libc::shm_open(
outfile.as_ptr(),
libc::O_RDWR | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR,
)
};
let stderr_fd = unsafe {
libc::shm_open(
errfile.as_ptr(),
libc::O_RDWR | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR,
)
}; };
Ok(Process { Ok(Process {
runner: Runner { runner: Runner {
pid: -1, pid: -1,
time_limit: time_limit, time_limit: time_limit,
memory_limit: memory_limit, memory_limit: memory_limit,
stdin_fd: None,
stdout_fd: stdout_fd,
stderr_fd: stderr_fd,
cmd: cmd, cmd: cmd,
workdir: workdir, workdir: workdir,
tx: Arc::new(Mutex::new(tx)), tx: Arc::new(Mutex::new(tx)),
@ -75,60 +43,6 @@ impl Process {
}, },
}) })
} }
// 为进程设置 stdin 的数据
pub fn set_stdin(&mut self, in_data: &Vec<u8>) -> Result<()> {
if in_data.len() == 0 {
return Ok(())
}
let memfile = path_buf_str(&self.runner.workdir)?;
let memfile = format!("{}{}", "stdin", memfile);
// 打开内存文件
let fd = unsafe {
libc::shm_open(
CString::new(memfile).unwrap().as_ptr(),
libc::O_RDWR | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR,
)
};
if fd <= 0 {
return Err(Error::SyscallError("shm_open".to_string()));
}
self.runner.stdin_fd = Some(fd);
// 扩充内存到数据文件大小
if unsafe { libc::ftruncate(fd, in_data.len() as i64) } < 0 {
return Err(Error::SyscallError("ftruncate".to_string()));
}
// 复制数据到创建的内存中
unsafe {
let ptr = libc::mmap(
std::ptr::null_mut() as *mut c_void,
in_data.len(),
libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if ptr == libc::MAP_FAILED {
return Err(Error::SyscallError("mmap".to_string()));
}
libc::strcpy(ptr as *mut c_char, in_data.as_ptr() as *const i8);
// 复制完要记得 munmap否则会造成无法回收的内存泄露
if libc::munmap(ptr, in_data.len()) < 0 {
return Err(Error::SyscallError("munmap".to_string()));
}
}
Ok(())
}
pub fn stdout_reader(&self) -> Result<Reader> {
Reader::new(self.runner.stdout_fd)
}
pub fn stderr_reader(&self) -> Result<Reader> {
Reader::new(self.runner.stderr_fd)
}
} }
impl Drop for Process { impl Drop for Process {
@ -147,37 +61,11 @@ impl Drop for Process {
libc::waitpid(self.runner.pid, &mut status, 0); libc::waitpid(self.runner.pid, &mut status, 0);
} }
} }
// 如果设置了 stdin 数据,则需要释放对应的内存
if let Some(_) = self.runner.stdin_fd {
// 如果 stdin_fd 有值,则说明 pathbuf 的转换一定没问题,否则上面也不会转换成功
let memfile = self
.runner
.workdir
.clone()
.into_os_string()
.into_string()
.unwrap();
unsafe {
libc::shm_unlink(CString::new(memfile).unwrap().as_ptr());
}
}
// 清理 stdout 和 stderr 的空间
let memfile = path_buf_str(&self.runner.workdir).unwrap();
let outfile = CString::new(format!("{}{}", "stdout", memfile)).unwrap();
let errfile = CString::new(format!("{}{}", "stderr", memfile)).unwrap();
unsafe {
libc::shm_unlink(CString::new(outfile).unwrap().as_ptr());
libc::shm_unlink(CString::new(errfile).unwrap().as_ptr());
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::process::Process;
use std::fs;
use tempfile::tempdir_in;
#[test] #[test]
fn hello() { fn hello() {
let s = String::from("hello"); let s = String::from("hello");
@ -186,22 +74,5 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn run() { async fn run() {}
let cmd = String::from("/bin/echo hello");
let pwd = tempdir_in("./runner").unwrap().into_path();
let path = pwd.to_str().unwrap();
let process = Process::new(cmd, pwd.clone(), 1000, 65535).unwrap();
let runner = process.runner.clone();
let status = runner.await.unwrap();
fs::remove_dir_all(path).unwrap();
assert_eq!(status.exit_code, 0);
let mut buf: [u8; 1024] = [0; 1024];
let reader = process.stdout_reader().unwrap();
reader.readline(&mut buf).unwrap();
let s = String::from("hello\n");
let bytes = s.into_bytes();
assert_eq!(&buf[0..6], &bytes[..]);
assert_eq!(buf[6], 0);
}
} }

View File

@ -1,55 +0,0 @@
use super::error::{Error, Result};
use libc;
use std::ffi::CString;
use std::os::raw::c_char;
use std::str;
pub struct Reader {
fd: i32,
stream: *mut libc::FILE,
}
impl Reader {
pub fn new(fd: i32) -> Result<Reader> {
let mode = match CString::new("r") {
Ok(val) => val,
Err(e) => return Err(Error::StringToCStringError(e)),
};
let stream = unsafe { libc::fdopen(fd, mode.as_ptr()) };
Ok(Reader { fd, stream: stream })
}
pub fn readline(&self, buf: &mut [u8]) -> Result<()> {
let res = unsafe {
libc::fgets(
buf.as_mut_ptr() as *mut c_char,
buf.len() as i32,
self.stream,
)
};
if res.is_null() {
return Err(Error::SyscallError("fgets".to_string()));
}
Ok(())
}
pub fn read(&self) -> Result<String> {
let mut result: String = "".to_owned();
let mut buf: [u8; 1024] = [0; 1024];
while unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, 1024) } != 0 {
let s = match str::from_utf8(&buf) {
Ok(val) => (val),
Err(_) => return Err(Error::SyscallError("read".to_string())),
};
debug!("{}", s);
result.push_str(s);
}
Ok(result)
}
}
impl Drop for Reader {
fn drop(&mut self) {
unsafe {
libc::fclose(self.stream);
}
}
}

View File

@ -1,6 +1,8 @@
use super::config::{STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
use super::error::{errno_str, Error, Result}; use super::error::{errno_str, Error, Result};
use super::exec_args::ExecArgs; use super::exec_args::ExecArgs;
use std::env; use std::env;
use std::ffi::CString;
use std::future::Future; use std::future::Future;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
@ -18,9 +20,6 @@ pub struct Runner {
pub workdir: PathBuf, pub workdir: PathBuf,
pub time_limit: i32, pub time_limit: i32,
pub memory_limit: i32, pub memory_limit: i32,
pub stdin_fd: Option<i32>,
pub stdout_fd: i32,
pub stderr_fd: i32,
pub cmd: String, pub cmd: String,
pub tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>, pub tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
pub rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>, pub rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
@ -109,6 +108,24 @@ impl Future for Runner {
} }
} }
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 failure!");
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!(errno_str(err));
}
if libc::dup2(fd, to) < 0 {
let err = io::Error::last_os_error().raw_os_error();
eprintln!("dup2 failure!");
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!(errno_str(err));
}
}
impl Runner { impl Runner {
pub fn run(&self) { pub fn run(&self) {
// 子进程里崩溃也无法返回,崩溃就直接崩溃了 // 子进程里崩溃也无法返回,崩溃就直接崩溃了
@ -132,11 +149,19 @@ impl Runner {
}; };
unsafe { unsafe {
// 重定向文件描述符 // 重定向文件描述符
if let Some(fd) = self.stdin_fd { dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644);
dup(fd, libc::STDIN_FILENO); dup(
} STDOUT_FILENAME,
dup(self.stdout_fd, libc::STDOUT_FILENO); libc::STDOUT_FILENO,
dup(self.stderr_fd, libc::STDERR_FILENO); libc::O_CREAT | libc::O_RDWR,
0o644,
);
dup(
STDERR_FILENAME,
libc::STDERR_FILENO,
libc::O_CREAT | libc::O_RDWR,
0o644,
);
// 墙上时钟限制 // 墙上时钟限制
if setitimer(ITIMER_REAL, &rt, ptr::null_mut()) == -1 { if setitimer(ITIMER_REAL, &rt, ptr::null_mut()) == -1 {
eprintln!("setitimer failure!"); eprintln!("setitimer failure!");
@ -170,15 +195,6 @@ impl Runner {
} }
} }
unsafe fn dup(from: libc::c_int, to: libc::c_int) {
if libc::dup2(from, to) < 0 {
let err = io::Error::last_os_error().raw_os_error();
eprintln!("dup2 failure!");
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!(errno_str(err));
}
}
impl Runner { impl Runner {
pub fn wait(&self) -> RunnerStatus { pub fn wait(&self) -> RunnerStatus {
let pid = self.pid; let pid = self.pid;
@ -214,15 +230,6 @@ impl Runner {
eprintln!("{:?}", io::Error::last_os_error().raw_os_error()); eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!("How dare you!"); panic!("How dare you!");
} }
// 重置输出偏移量
if libc::lseek(self.stdout_fd, 0, libc::SEEK_SET) < 0 {
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!("How dare you!");
}
if libc::lseek(self.stderr_fd, 0, libc::SEEK_SET) < 0 {
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
panic!("How dare you!");
}
} }
let mut exit_code = 0; let mut exit_code = 0;