mirror of
https://github.com/MeiK2333/river.git
synced 2025-11-04 14:49:40 +08:00
Update
This commit is contained in:
parent
8ba1eb2d90
commit
c35eb6ae29
@ -68,7 +68,7 @@ message JudgeResponse {
|
||||
}
|
||||
// int32 errno = 4;
|
||||
// int32 exit_code = 5;
|
||||
// string stdout = 6;
|
||||
// string stderr = 7;
|
||||
string stdout = 6;
|
||||
string stderr = 7;
|
||||
string errmsg = 8;
|
||||
}
|
||||
|
||||
3
src/config.rs
Normal file
3
src/config.rs
Normal 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";
|
||||
@ -14,6 +14,7 @@ pub enum Error {
|
||||
CreateTempDirError(io::Error),
|
||||
LanguageNotFound(i32),
|
||||
FileWriteError(io::Error),
|
||||
FileReadError(io::Error),
|
||||
ChannelRecvError,
|
||||
StringToCStringError(NulError),
|
||||
OsStringToStringError(OsString),
|
||||
@ -57,6 +58,8 @@ pub fn system_error(err: Error) -> JudgeResponse {
|
||||
time_used: 0,
|
||||
memory_used: 0,
|
||||
state: Some(State::Result(JudgeResult::SystemError as i32)),
|
||||
stdout: "".into(),
|
||||
stderr: "".into(),
|
||||
errmsg: format!("{}", err).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use super::config::{STDERR_FILENAME, STDOUT_FILENAME};
|
||||
use super::error::{Error, Result};
|
||||
use super::process::Process;
|
||||
use super::runner::RunnerStatus;
|
||||
@ -14,6 +15,8 @@ impl JudgeResponse {
|
||||
time_used: 0,
|
||||
memory_used: 0,
|
||||
errmsg: "".into(),
|
||||
stdout: "".into(),
|
||||
stderr: "".into(),
|
||||
state: Some(State::Status(JudgeStatus::Running as i32)),
|
||||
}
|
||||
}
|
||||
@ -40,27 +43,17 @@ pub async fn judger(
|
||||
Some(Language::Go) => "./a.out",
|
||||
None => return Err(Error::LanguageNotFound(request.language)),
|
||||
};
|
||||
let mut process = Process::new(
|
||||
let process = Process::new(
|
||||
cmd.to_string(),
|
||||
path.to_path_buf(),
|
||||
&data.in_data,
|
||||
data.time_limit,
|
||||
data.memory_limit,
|
||||
)?;
|
||||
|
||||
// 设置输入数据
|
||||
process.set_stdin(&data.in_data)?;
|
||||
|
||||
// 开始执行并等待返回结果
|
||||
let runner = process.runner.clone();
|
||||
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: 对比答案,检查结果
|
||||
@ -92,33 +85,45 @@ pub async fn compile(
|
||||
return Err(Error::FileWriteError(e));
|
||||
};
|
||||
|
||||
// TODO: 使用配置文件
|
||||
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::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::Rust) => "/usr/bin/rustc main.rs -o a.out -C opt-level=2",
|
||||
// 无需编译的语言直接返回
|
||||
// TODO: eslint......
|
||||
Some(Language::Node) => return Ok(resp),
|
||||
Some(Language::Node) => "/bin/echo hello",
|
||||
Some(Language::TypeScript) => "/usr/bin/tsc",
|
||||
Some(Language::Go) => "/usr/bin/go build -ldflags \"-s -w\" main.go",
|
||||
None => return Err(Error::LanguageNotFound(request.language)),
|
||||
};
|
||||
// 编译的资源限制为固定的
|
||||
let process = Process::new(cmd.to_string(), path.to_path_buf(), 10000, 64 * 1024)?;
|
||||
debug!("build command: {}", cmd);
|
||||
let v = vec![];
|
||||
let process = Process::new(
|
||||
cmd.to_string(),
|
||||
path.to_path_buf(),
|
||||
&v,
|
||||
// 编译的资源限制为固定的
|
||||
10000,
|
||||
64 * 1024,
|
||||
)?;
|
||||
|
||||
let runner = process.runner.clone();
|
||||
let status = runner.await?;
|
||||
resp.set_process_status(&status);
|
||||
if status.exit_code != 0 {
|
||||
debug!("compile exit code: {}", status.exit_code);
|
||||
let reader = process.stdout_reader()?;
|
||||
let stdout = reader.read()?;
|
||||
debug!("stdout {}", stdout);
|
||||
let reader = process.stderr_reader()?;
|
||||
let stderr = reader.read()?;
|
||||
debug!("stderr {}", stderr);
|
||||
resp.errmsg = stderr;
|
||||
// 从 stdout 和 stderr 中获取错误信息
|
||||
let stdout = match fs::read_to_string(path.join(STDOUT_FILENAME)).await {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::FileReadError(e)),
|
||||
};
|
||||
let stderr = match fs::read_to_string(path.join(STDERR_FILENAME)).await {
|
||||
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));
|
||||
} else {
|
||||
resp.state = Some(State::Result(JudgeResult::Accepted as i32));
|
||||
|
||||
@ -13,11 +13,11 @@ use tempfile::tempdir_in;
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod exec_args;
|
||||
mod judger;
|
||||
mod process;
|
||||
mod reader;
|
||||
mod runner;
|
||||
|
||||
pub mod river {
|
||||
|
||||
147
src/process.rs
147
src/process.rs
@ -1,10 +1,8 @@
|
||||
use super::config::STDIN_FILENAME;
|
||||
use super::error::{Error, Result};
|
||||
use super::reader::Reader;
|
||||
use super::runner::Runner;
|
||||
use libc;
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -17,57 +15,27 @@ pub struct Process {
|
||||
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 {
|
||||
pub fn new(
|
||||
cmd: String,
|
||||
workdir: PathBuf,
|
||||
in_data: &Vec<u8>,
|
||||
time_limit: i32,
|
||||
memory_limit: i32,
|
||||
) -> Result<Process> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let memfile = path_buf_str(&workdir)?;
|
||||
let outfile = match CString::new(format!("{}{}", "stdout", memfile)) {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::StringToCStringError(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,
|
||||
)
|
||||
debug!("writing input file");
|
||||
// TODO: 此处同步写入文件,后续可以修改为异步写入,防止阻塞整体流程
|
||||
if let Err(e) = fs::write(workdir.join(STDIN_FILENAME), &in_data) {
|
||||
return Err(Error::FileWriteError(e));
|
||||
};
|
||||
|
||||
Ok(Process {
|
||||
runner: Runner {
|
||||
pid: -1,
|
||||
time_limit: time_limit,
|
||||
memory_limit: memory_limit,
|
||||
stdin_fd: None,
|
||||
stdout_fd: stdout_fd,
|
||||
stderr_fd: stderr_fd,
|
||||
cmd: cmd,
|
||||
workdir: workdir,
|
||||
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 {
|
||||
@ -147,37 +61,11 @@ impl Drop for Process {
|
||||
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)]
|
||||
mod tests {
|
||||
use crate::process::Process;
|
||||
use std::fs;
|
||||
use tempfile::tempdir_in;
|
||||
|
||||
#[test]
|
||||
fn hello() {
|
||||
let s = String::from("hello");
|
||||
@ -186,22 +74,5 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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);
|
||||
}
|
||||
async fn run() {}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
use super::config::{STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
|
||||
use super::error::{errno_str, Error, Result};
|
||||
use super::exec_args::ExecArgs;
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
@ -18,9 +20,6 @@ pub struct Runner {
|
||||
pub workdir: PathBuf,
|
||||
pub time_limit: i32,
|
||||
pub memory_limit: i32,
|
||||
pub stdin_fd: Option<i32>,
|
||||
pub stdout_fd: i32,
|
||||
pub stderr_fd: i32,
|
||||
pub cmd: String,
|
||||
pub tx: Arc<Mutex<mpsc::Sender<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 {
|
||||
pub fn run(&self) {
|
||||
// 子进程里崩溃也无法返回,崩溃就直接崩溃了
|
||||
@ -132,11 +149,19 @@ impl Runner {
|
||||
};
|
||||
unsafe {
|
||||
// 重定向文件描述符
|
||||
if let Some(fd) = self.stdin_fd {
|
||||
dup(fd, libc::STDIN_FILENO);
|
||||
}
|
||||
dup(self.stdout_fd, libc::STDOUT_FILENO);
|
||||
dup(self.stderr_fd, libc::STDERR_FILENO);
|
||||
dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644);
|
||||
dup(
|
||||
STDOUT_FILENAME,
|
||||
libc::STDOUT_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 {
|
||||
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 {
|
||||
pub fn wait(&self) -> RunnerStatus {
|
||||
let pid = self.pid;
|
||||
@ -214,15 +230,6 @@ impl Runner {
|
||||
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
|
||||
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user