mirror of
https://github.com/MeiK2333/river.git
synced 2025-09-26 22:49:11 +08:00
重构代码结构
This commit is contained in:
parent
a17d1a7f25
commit
664564e79b
20
README.md
20
README.md
@ -3,23 +3,3 @@
|
||||
## 环境要求
|
||||
|
||||
- linux
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
cd example
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
## TODOs
|
||||
|
||||
已经完成基本功能,后续需要优化
|
||||
|
||||
- 基于 cgroups 的资源控制
|
||||
- 用户、组限制
|
||||
- 安全测试
|
||||
- 优化 args 生成代码,减少测量出的用户代码执行时间
|
||||
- special judge
|
||||
- docker 部署
|
||||
- 更多语言支持
|
||||
- 使用环境变量等机制自定义评测目录
|
||||
|
2
build.rs
2
build.rs
@ -1,5 +1,5 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::compile_protos("proto/river.proto")?;
|
||||
cc::Build::new().file("src/memory.c").compile("memory");
|
||||
// cc::Build::new().file("src/memory.c").compile("memory");
|
||||
Ok(())
|
||||
}
|
||||
|
8
example/.gitignore
vendored
8
example/.gitignore
vendored
@ -1,8 +0,0 @@
|
||||
*.pyc
|
||||
venv/
|
||||
__pycache__/
|
||||
*.out
|
||||
*.class
|
||||
package.json
|
||||
package-lock.json
|
||||
node_modules
|
@ -1,5 +0,0 @@
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello World!\n");
|
||||
return 0;
|
||||
}
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,7 +0,0 @@
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
int a, b;
|
||||
scanf("%d %d", &a, &b);
|
||||
printf("%d\n", a + b);
|
||||
return 0;
|
||||
}
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,6 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
int main() {
|
||||
cout << "Hello World!" << endl;
|
||||
return 0;
|
||||
}
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,8 +0,0 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
int main() {
|
||||
int a, b;
|
||||
cin >> a >> b;
|
||||
cout << a + b << endl;
|
||||
return 0;
|
||||
}
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,6 +0,0 @@
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello World!")
|
||||
}
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,9 +0,0 @@
|
||||
package main
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
var a, b int
|
||||
fmt.Scan(&a)
|
||||
fmt.Scan(&b)
|
||||
fmt.Println(a + b)
|
||||
}
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,5 +0,0 @@
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1,12 +0,0 @@
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
Scanner reader = new Scanner(System.in);
|
||||
int a, b;
|
||||
a = reader.nextInt();
|
||||
b = reader.nextInt();
|
||||
System.out.println(a + b);
|
||||
reader.close();
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,94 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import grpc
|
||||
|
||||
import river_pb2
|
||||
import river_pb2_grpc
|
||||
|
||||
|
||||
def judge(path, language):
|
||||
if language == river_pb2.C:
|
||||
filename = "main.c"
|
||||
elif language == river_pb2.Cpp:
|
||||
filename = "main.cpp"
|
||||
elif language == river_pb2.Python:
|
||||
filename = "main.py"
|
||||
elif language == river_pb2.Java:
|
||||
filename = "Main.java"
|
||||
elif language == river_pb2.Rust:
|
||||
filename = "main.rs"
|
||||
elif language == river_pb2.Go:
|
||||
filename = "main.go"
|
||||
elif language == river_pb2.Node:
|
||||
filename = "main.js"
|
||||
elif language == river_pb2.TypeScript:
|
||||
filename = "main.ts"
|
||||
with open(path.joinpath(filename), "rb") as fr:
|
||||
code = fr.read()
|
||||
with open(path.joinpath("in.txt"), "rb") as fr:
|
||||
in_data = fr.read()
|
||||
with open(path.joinpath("out.txt"), "rb") as fr:
|
||||
out_data = fr.read()
|
||||
# compile
|
||||
yield river_pb2.JudgeRequest(
|
||||
language=language,
|
||||
judge_type=river_pb2.Standard,
|
||||
compile_data=river_pb2.CompileData(code=code),
|
||||
)
|
||||
# judge
|
||||
yield river_pb2.JudgeRequest(
|
||||
language=language,
|
||||
judge_type=river_pb2.Standard,
|
||||
judge_data=river_pb2.JudgeData(
|
||||
in_data=in_data, out_data=out_data, time_limit=10000, memory_limit=65535
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def run():
|
||||
with grpc.insecure_channel("localhost:4003") as channel:
|
||||
stub = river_pb2_grpc.RiverStub(channel)
|
||||
for path in Path("java").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Java)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("c").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.C)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("cpp").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Cpp)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("py").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Python)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("rust").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Rust)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("go").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Go)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("node").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.Node)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
for path in Path("ts").iterdir():
|
||||
print(f"开始评测 {path}")
|
||||
for item in stub.Judge(judge(path, river_pb2.TypeScript)):
|
||||
print(item)
|
||||
print(f"{path} 评测完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
@ -1 +0,0 @@
|
||||
console.log("Hello World!");
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,15 +0,0 @@
|
||||
const readline = require('readline');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on('line', (line) => {
|
||||
var nums = line.split(' ');
|
||||
var a = parseInt(nums[0]);
|
||||
var b = parseInt(nums[1]);
|
||||
var res = a + b;
|
||||
console.log(res);
|
||||
process.exit(0);
|
||||
});
|
@ -1 +0,0 @@
|
||||
5
|
@ -1 +0,0 @@
|
||||
print("Hello World!")
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,2 +0,0 @@
|
||||
a, b = input().split()
|
||||
print(int(a) + int(b))
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,4 +0,0 @@
|
||||
grpcio==1.33.1
|
||||
grpcio-tools==1.33.1
|
||||
protobuf==3.13.0
|
||||
six==1.15.0
|
@ -1,529 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: river.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import enum_type_wrapper
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='river.proto',
|
||||
package='river',
|
||||
syntax='proto3',
|
||||
serialized_options=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
serialized_pb=b'\n\x0briver.proto\x12\x05river\"\x1b\n\x0b\x43ompileData\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x0c\"X\n\tJudgeData\x12\x0f\n\x07in_data\x18\x01 \x01(\x0c\x12\x10\n\x08out_data\x18\x02 \x01(\x0c\x12\x12\n\ntime_limit\x18\x03 \x01(\x05\x12\x14\n\x0cmemory_limit\x18\x04 \x01(\x05\"\xb3\x01\n\x0cJudgeRequest\x12!\n\x08language\x18\x01 \x01(\x0e\x32\x0f.river.Language\x12$\n\njudge_type\x18\x02 \x01(\x0e\x32\x10.river.JudgeType\x12*\n\x0c\x63ompile_data\x18\x03 \x01(\x0b\x32\x12.river.CompileDataH\x00\x12&\n\njudge_data\x18\x04 \x01(\x0b\x32\x10.river.JudgeDataH\x00\x42\x06\n\x04\x64\x61ta\"\xbc\x01\n\rJudgeResponse\x12\x11\n\ttime_used\x18\x01 \x01(\x03\x12\x13\n\x0bmemory_used\x18\x02 \x01(\x03\x12$\n\x06result\x18\x03 \x01(\x0e\x32\x12.river.JudgeResultH\x00\x12$\n\x06status\x18\t \x01(\x0e\x32\x12.river.JudgeStatusH\x00\x12\x0e\n\x06stdout\x18\x06 \x01(\t\x12\x0e\n\x06stderr\x18\x07 \x01(\t\x12\x0e\n\x06\x65rrmsg\x18\x08 \x01(\tB\x07\n\x05state*\\\n\x08Language\x12\x05\n\x01\x43\x10\x00\x12\x07\n\x03\x43pp\x10\x01\x12\n\n\x06Python\x10\x02\x12\x08\n\x04Rust\x10\x03\x12\x08\n\x04Node\x10\x04\x12\x0e\n\nTypeScript\x10\x05\x12\x06\n\x02Go\x10\x06\x12\x08\n\x04Java\x10\x07*\x19\n\tJudgeType\x12\x0c\n\x08Standard\x10\x00*\xd5\x01\n\x0bJudgeResult\x12\x0c\n\x08\x41\x63\x63\x65pted\x10\x00\x12\x0f\n\x0bWrongAnswer\x10\x01\x12\x15\n\x11TimeLimitExceeded\x10\x02\x12\x17\n\x13MemoryLimitExceeded\x10\x03\x12\x10\n\x0cRuntimeError\x10\x04\x12\x17\n\x13OutputLimitExceeded\x10\x05\x12\x10\n\x0c\x43ompileError\x10\x06\x12\x15\n\x11PresentationError\x10\x07\x12\x0f\n\x0bSystemError\x10\x08\x12\x12\n\x0e\x43ompileSuccess\x10\t*2\n\x0bJudgeStatus\x12\x0b\n\x07Pending\x10\x00\x12\x0b\n\x07Running\x10\x01\x12\t\n\x05\x45nded\x10\x02\x32\x41\n\x05River\x12\x38\n\x05Judge\x12\x13.river.JudgeRequest\x1a\x14.river.JudgeResponse\"\x00(\x01\x30\x01\x62\x06proto3'
|
||||
)
|
||||
|
||||
_LANGUAGE = _descriptor.EnumDescriptor(
|
||||
name='Language',
|
||||
full_name='river.Language',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='C', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Cpp', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Python', index=2, number=2,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Rust', index=3, number=3,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Node', index=4, number=4,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='TypeScript', index=5, number=5,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Go', index=6, number=6,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Java', index=7, number=7,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=514,
|
||||
serialized_end=606,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_LANGUAGE)
|
||||
|
||||
Language = enum_type_wrapper.EnumTypeWrapper(_LANGUAGE)
|
||||
_JUDGETYPE = _descriptor.EnumDescriptor(
|
||||
name='JudgeType',
|
||||
full_name='river.JudgeType',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Standard', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=608,
|
||||
serialized_end=633,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_JUDGETYPE)
|
||||
|
||||
JudgeType = enum_type_wrapper.EnumTypeWrapper(_JUDGETYPE)
|
||||
_JUDGERESULT = _descriptor.EnumDescriptor(
|
||||
name='JudgeResult',
|
||||
full_name='river.JudgeResult',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Accepted', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='WrongAnswer', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='TimeLimitExceeded', index=2, number=2,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='MemoryLimitExceeded', index=3, number=3,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='RuntimeError', index=4, number=4,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='OutputLimitExceeded', index=5, number=5,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='CompileError', index=6, number=6,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='PresentationError', index=7, number=7,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='SystemError', index=8, number=8,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='CompileSuccess', index=9, number=9,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=636,
|
||||
serialized_end=849,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_JUDGERESULT)
|
||||
|
||||
JudgeResult = enum_type_wrapper.EnumTypeWrapper(_JUDGERESULT)
|
||||
_JUDGESTATUS = _descriptor.EnumDescriptor(
|
||||
name='JudgeStatus',
|
||||
full_name='river.JudgeStatus',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
values=[
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Pending', index=0, number=0,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Running', index=1, number=1,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
_descriptor.EnumValueDescriptor(
|
||||
name='Ended', index=2, number=2,
|
||||
serialized_options=None,
|
||||
type=None,
|
||||
create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
containing_type=None,
|
||||
serialized_options=None,
|
||||
serialized_start=851,
|
||||
serialized_end=901,
|
||||
)
|
||||
_sym_db.RegisterEnumDescriptor(_JUDGESTATUS)
|
||||
|
||||
JudgeStatus = enum_type_wrapper.EnumTypeWrapper(_JUDGESTATUS)
|
||||
C = 0
|
||||
Cpp = 1
|
||||
Python = 2
|
||||
Rust = 3
|
||||
Node = 4
|
||||
TypeScript = 5
|
||||
Go = 6
|
||||
Java = 7
|
||||
Standard = 0
|
||||
Accepted = 0
|
||||
WrongAnswer = 1
|
||||
TimeLimitExceeded = 2
|
||||
MemoryLimitExceeded = 3
|
||||
RuntimeError = 4
|
||||
OutputLimitExceeded = 5
|
||||
CompileError = 6
|
||||
PresentationError = 7
|
||||
SystemError = 8
|
||||
CompileSuccess = 9
|
||||
Pending = 0
|
||||
Running = 1
|
||||
Ended = 2
|
||||
|
||||
|
||||
|
||||
_COMPILEDATA = _descriptor.Descriptor(
|
||||
name='CompileData',
|
||||
full_name='river.CompileData',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='code', full_name='river.CompileData.code', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=22,
|
||||
serialized_end=49,
|
||||
)
|
||||
|
||||
|
||||
_JUDGEDATA = _descriptor.Descriptor(
|
||||
name='JudgeData',
|
||||
full_name='river.JudgeData',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='in_data', full_name='river.JudgeData.in_data', index=0,
|
||||
number=1, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='out_data', full_name='river.JudgeData.out_data', index=1,
|
||||
number=2, type=12, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"",
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='time_limit', full_name='river.JudgeData.time_limit', index=2,
|
||||
number=3, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='memory_limit', full_name='river.JudgeData.memory_limit', index=3,
|
||||
number=4, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=51,
|
||||
serialized_end=139,
|
||||
)
|
||||
|
||||
|
||||
_JUDGEREQUEST = _descriptor.Descriptor(
|
||||
name='JudgeRequest',
|
||||
full_name='river.JudgeRequest',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='language', full_name='river.JudgeRequest.language', index=0,
|
||||
number=1, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='judge_type', full_name='river.JudgeRequest.judge_type', index=1,
|
||||
number=2, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='compile_data', full_name='river.JudgeRequest.compile_data', index=2,
|
||||
number=3, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='judge_data', full_name='river.JudgeRequest.judge_data', index=3,
|
||||
number=4, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
_descriptor.OneofDescriptor(
|
||||
name='data', full_name='river.JudgeRequest.data',
|
||||
index=0, containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[]),
|
||||
],
|
||||
serialized_start=142,
|
||||
serialized_end=321,
|
||||
)
|
||||
|
||||
|
||||
_JUDGERESPONSE = _descriptor.Descriptor(
|
||||
name='JudgeResponse',
|
||||
full_name='river.JudgeResponse',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='time_used', full_name='river.JudgeResponse.time_used', index=0,
|
||||
number=1, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='memory_used', full_name='river.JudgeResponse.memory_used', index=1,
|
||||
number=2, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='result', full_name='river.JudgeResponse.result', index=2,
|
||||
number=3, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='status', full_name='river.JudgeResponse.status', index=3,
|
||||
number=9, type=14, cpp_type=8, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='stdout', full_name='river.JudgeResponse.stdout', index=4,
|
||||
number=6, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"".decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='stderr', full_name='river.JudgeResponse.stderr', index=5,
|
||||
number=7, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"".decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='errmsg', full_name='river.JudgeResponse.errmsg', index=6,
|
||||
number=8, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=b"".decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
_descriptor.OneofDescriptor(
|
||||
name='state', full_name='river.JudgeResponse.state',
|
||||
index=0, containing_type=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
fields=[]),
|
||||
],
|
||||
serialized_start=324,
|
||||
serialized_end=512,
|
||||
)
|
||||
|
||||
_JUDGEREQUEST.fields_by_name['language'].enum_type = _LANGUAGE
|
||||
_JUDGEREQUEST.fields_by_name['judge_type'].enum_type = _JUDGETYPE
|
||||
_JUDGEREQUEST.fields_by_name['compile_data'].message_type = _COMPILEDATA
|
||||
_JUDGEREQUEST.fields_by_name['judge_data'].message_type = _JUDGEDATA
|
||||
_JUDGEREQUEST.oneofs_by_name['data'].fields.append(
|
||||
_JUDGEREQUEST.fields_by_name['compile_data'])
|
||||
_JUDGEREQUEST.fields_by_name['compile_data'].containing_oneof = _JUDGEREQUEST.oneofs_by_name['data']
|
||||
_JUDGEREQUEST.oneofs_by_name['data'].fields.append(
|
||||
_JUDGEREQUEST.fields_by_name['judge_data'])
|
||||
_JUDGEREQUEST.fields_by_name['judge_data'].containing_oneof = _JUDGEREQUEST.oneofs_by_name['data']
|
||||
_JUDGERESPONSE.fields_by_name['result'].enum_type = _JUDGERESULT
|
||||
_JUDGERESPONSE.fields_by_name['status'].enum_type = _JUDGESTATUS
|
||||
_JUDGERESPONSE.oneofs_by_name['state'].fields.append(
|
||||
_JUDGERESPONSE.fields_by_name['result'])
|
||||
_JUDGERESPONSE.fields_by_name['result'].containing_oneof = _JUDGERESPONSE.oneofs_by_name['state']
|
||||
_JUDGERESPONSE.oneofs_by_name['state'].fields.append(
|
||||
_JUDGERESPONSE.fields_by_name['status'])
|
||||
_JUDGERESPONSE.fields_by_name['status'].containing_oneof = _JUDGERESPONSE.oneofs_by_name['state']
|
||||
DESCRIPTOR.message_types_by_name['CompileData'] = _COMPILEDATA
|
||||
DESCRIPTOR.message_types_by_name['JudgeData'] = _JUDGEDATA
|
||||
DESCRIPTOR.message_types_by_name['JudgeRequest'] = _JUDGEREQUEST
|
||||
DESCRIPTOR.message_types_by_name['JudgeResponse'] = _JUDGERESPONSE
|
||||
DESCRIPTOR.enum_types_by_name['Language'] = _LANGUAGE
|
||||
DESCRIPTOR.enum_types_by_name['JudgeType'] = _JUDGETYPE
|
||||
DESCRIPTOR.enum_types_by_name['JudgeResult'] = _JUDGERESULT
|
||||
DESCRIPTOR.enum_types_by_name['JudgeStatus'] = _JUDGESTATUS
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
CompileData = _reflection.GeneratedProtocolMessageType('CompileData', (_message.Message,), {
|
||||
'DESCRIPTOR' : _COMPILEDATA,
|
||||
'__module__' : 'river_pb2'
|
||||
# @@protoc_insertion_point(class_scope:river.CompileData)
|
||||
})
|
||||
_sym_db.RegisterMessage(CompileData)
|
||||
|
||||
JudgeData = _reflection.GeneratedProtocolMessageType('JudgeData', (_message.Message,), {
|
||||
'DESCRIPTOR' : _JUDGEDATA,
|
||||
'__module__' : 'river_pb2'
|
||||
# @@protoc_insertion_point(class_scope:river.JudgeData)
|
||||
})
|
||||
_sym_db.RegisterMessage(JudgeData)
|
||||
|
||||
JudgeRequest = _reflection.GeneratedProtocolMessageType('JudgeRequest', (_message.Message,), {
|
||||
'DESCRIPTOR' : _JUDGEREQUEST,
|
||||
'__module__' : 'river_pb2'
|
||||
# @@protoc_insertion_point(class_scope:river.JudgeRequest)
|
||||
})
|
||||
_sym_db.RegisterMessage(JudgeRequest)
|
||||
|
||||
JudgeResponse = _reflection.GeneratedProtocolMessageType('JudgeResponse', (_message.Message,), {
|
||||
'DESCRIPTOR' : _JUDGERESPONSE,
|
||||
'__module__' : 'river_pb2'
|
||||
# @@protoc_insertion_point(class_scope:river.JudgeResponse)
|
||||
})
|
||||
_sym_db.RegisterMessage(JudgeResponse)
|
||||
|
||||
|
||||
|
||||
_RIVER = _descriptor.ServiceDescriptor(
|
||||
name='River',
|
||||
full_name='river.River',
|
||||
file=DESCRIPTOR,
|
||||
index=0,
|
||||
serialized_options=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
serialized_start=903,
|
||||
serialized_end=968,
|
||||
methods=[
|
||||
_descriptor.MethodDescriptor(
|
||||
name='Judge',
|
||||
full_name='river.River.Judge',
|
||||
index=0,
|
||||
containing_service=None,
|
||||
input_type=_JUDGEREQUEST,
|
||||
output_type=_JUDGERESPONSE,
|
||||
serialized_options=None,
|
||||
create_key=_descriptor._internal_create_key,
|
||||
),
|
||||
])
|
||||
_sym_db.RegisterServiceDescriptor(_RIVER)
|
||||
|
||||
DESCRIPTOR.services_by_name['River'] = _RIVER
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
@ -1,66 +0,0 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
|
||||
import river_pb2 as river__pb2
|
||||
|
||||
|
||||
class RiverStub(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.Judge = channel.stream_stream(
|
||||
'/river.River/Judge',
|
||||
request_serializer=river__pb2.JudgeRequest.SerializeToString,
|
||||
response_deserializer=river__pb2.JudgeResponse.FromString,
|
||||
)
|
||||
|
||||
|
||||
class RiverServicer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def Judge(self, request_iterator, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
|
||||
def add_RiverServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
'Judge': grpc.stream_stream_rpc_method_handler(
|
||||
servicer.Judge,
|
||||
request_deserializer=river__pb2.JudgeRequest.FromString,
|
||||
response_serializer=river__pb2.JudgeResponse.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
'river.River', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class River(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def Judge(request_iterator,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.stream_stream(request_iterator, target, '/river.River/Judge',
|
||||
river__pb2.JudgeRequest.SerializeToString,
|
||||
river__pb2.JudgeResponse.FromString,
|
||||
options, channel_credentials,
|
||||
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
}
|
@ -1 +0,0 @@
|
||||
Hello World!
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,15 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
let mut input = String::new();
|
||||
|
||||
io::stdin().read_line(&mut input).expect("correct input");
|
||||
let res = input
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map(|a| a.parse::<i32>())
|
||||
.map(|a| a.expect("parsed integer"))
|
||||
.fold(0i32, |sum, a| sum + a);
|
||||
|
||||
println!("{}", res);
|
||||
}
|
@ -1 +0,0 @@
|
||||
5
|
1
example/ts/1000/.gitignore
vendored
1
example/ts/1000/.gitignore
vendored
@ -1 +0,0 @@
|
||||
main.js
|
@ -1 +0,0 @@
|
||||
console.log("Hello World!");
|
@ -1 +0,0 @@
|
||||
Hello World!
|
1
example/ts/1001/.gitignore
vendored
1
example/ts/1001/.gitignore
vendored
@ -1 +0,0 @@
|
||||
main.js
|
@ -1 +0,0 @@
|
||||
2 3
|
@ -1,15 +0,0 @@
|
||||
import * as readline from 'readline';
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on('line', (line: string) => {
|
||||
var nums = line.split(' ');
|
||||
var a = parseInt(nums[0]);
|
||||
var b = parseInt(nums[1]);
|
||||
var res = a + b;
|
||||
console.log(res);
|
||||
process.exit(0);
|
||||
});
|
@ -1 +0,0 @@
|
||||
5
|
@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Hello World!"
|
||||
# create judge dir
|
||||
mkdir -p /river/runner
|
||||
# 运行的上层目录创建 node_modules,以便 Node 与 TypeScript 使用
|
||||
cd /river/runner
|
||||
npm i
|
||||
|
||||
cd /plugins/js
|
||||
npm install
|
||||
npm install -g ts-node typescript
|
||||
|
||||
echo "Hello World!"
|
1
plugins/js/.gitignore
vendored
1
plugins/js/.gitignore
vendored
@ -1 +0,0 @@
|
||||
node_modules/
|
@ -1,5 +0,0 @@
|
||||
```bash
|
||||
cd plugins/js
|
||||
./validate.js success.js
|
||||
./validate.js failure.js
|
||||
```
|
@ -1,6 +0,0 @@
|
||||
return 42; // should be inside a function
|
||||
function f() {
|
||||
'use strict';
|
||||
var x = 042;
|
||||
with (z) { }
|
||||
}
|
13
plugins/js/package-lock.json
generated
13
plugins/js/package-lock.json
generated
@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "js",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "js",
|
||||
"version": "1.0.0",
|
||||
"description": "```bash cd plugins/js ./validate.js success.js ./validate.js failure.js ```",
|
||||
"main": "failure.js",
|
||||
"dependencies": {
|
||||
"esprima": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
@ -1 +0,0 @@
|
||||
console.log("Hello World!");
|
@ -1,23 +0,0 @@
|
||||
#!/usr/bin/node
|
||||
const fs = require('fs');
|
||||
const esprima = require('esprima');
|
||||
|
||||
const file = process.argv[2];
|
||||
const code = fs.readFileSync(file).toString();
|
||||
|
||||
const res = esprima.parseScript(code, { tolerant: true });
|
||||
if (res.errors.length != 0) {
|
||||
const split = code.split('\n');
|
||||
for (const error of res.errors) {
|
||||
console.error(split[error.lineNumber - 1]);
|
||||
for (let i = 1; i < error.column; i++) {
|
||||
process.stderr.write(' ');
|
||||
}
|
||||
console.error('^');
|
||||
console.error(`${error.toString()}
|
||||
at (${file}:${error.lineNumber}:${error.column})`);
|
||||
console.error('--------------------------------------------------------------------------');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
@ -2,29 +2,33 @@ syntax = "proto3";
|
||||
package river;
|
||||
|
||||
service River {
|
||||
rpc Upload(UploadFile) returns (UploadState) {}
|
||||
rpc Judge(stream JudgeRequest) returns (stream JudgeResponse) {}
|
||||
}
|
||||
|
||||
message UploadFile {
|
||||
string filepath = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message UploadState {
|
||||
oneof state {
|
||||
string filepath = 1;
|
||||
string errmsg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message CompileData {
|
||||
bytes code = 1;
|
||||
string language = 1;
|
||||
string code = 2;
|
||||
}
|
||||
|
||||
message JudgeData {
|
||||
bytes in_data = 1;
|
||||
bytes out_data = 2;
|
||||
string in_file = 1;
|
||||
string out_file = 2;
|
||||
int32 time_limit = 3;
|
||||
int32 memory_limit = 4;
|
||||
}
|
||||
|
||||
enum Language {
|
||||
C = 0;
|
||||
Cpp = 1;
|
||||
Python = 2;
|
||||
Rust = 3;
|
||||
Node = 4;
|
||||
TypeScript = 5;
|
||||
Go = 6;
|
||||
Java = 7;
|
||||
JudgeType judge_type = 5;
|
||||
}
|
||||
|
||||
enum JudgeType {
|
||||
@ -33,16 +37,13 @@ enum JudgeType {
|
||||
}
|
||||
|
||||
message JudgeRequest {
|
||||
Language language = 1;
|
||||
JudgeType judge_type = 2;
|
||||
|
||||
oneof data {
|
||||
CompileData compile_data = 3;
|
||||
JudgeData judge_data = 4;
|
||||
CompileData compile_data = 1;
|
||||
JudgeData judge_data = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum JudgeResult {
|
||||
enum JudgeResultEnum {
|
||||
Accepted = 0;
|
||||
WrongAnswer = 1;
|
||||
TimeLimitExceeded = 2;
|
||||
@ -61,16 +62,16 @@ enum JudgeStatus {
|
||||
Ended = 2;
|
||||
}
|
||||
|
||||
message JudgeResponse {
|
||||
message JudgeResult {
|
||||
int64 time_used = 1;
|
||||
int64 memory_used = 2;
|
||||
JudgeResultEnum result = 3;
|
||||
string errmsg = 4;
|
||||
}
|
||||
|
||||
message JudgeResponse {
|
||||
oneof state {
|
||||
JudgeResult result = 3;
|
||||
JudgeStatus status = 9;
|
||||
JudgeResult result = 1;
|
||||
JudgeStatus status = 2;
|
||||
}
|
||||
// int32 errno = 4;
|
||||
// int32 exit_code = 5;
|
||||
string stdout = 6;
|
||||
string stderr = 7;
|
||||
string errmsg = 8;
|
||||
}
|
||||
|
7
runner/.gitignore
vendored
7
runner/.gitignore
vendored
@ -1,7 +0,0 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
!package.json
|
||||
!tsconfig.json
|
||||
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "river",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node": "^14.14.6"
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noErrorTruncation": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
}
|
||||
}
|
118
src/allow.rs
118
src/allow.rs
@ -1,118 +0,0 @@
|
||||
use super::seccomp::*;
|
||||
use libc;
|
||||
|
||||
pub fn gen_rules() -> Vec<SyscallRuleSet> {
|
||||
vec![
|
||||
allow_syscall(libc::SYS_access),
|
||||
allow_syscall(libc::SYS_arch_prctl),
|
||||
// allow_syscall(libc::SYS_brk),
|
||||
allow_syscall(libc::SYS_chdir),
|
||||
allow_syscall(libc::SYS_chmod),
|
||||
allow_syscall(libc::SYS_chown),
|
||||
allow_syscall(libc::SYS_clock_adjtime),
|
||||
allow_syscall(libc::SYS_clock_getres),
|
||||
allow_syscall(libc::SYS_clock_gettime),
|
||||
allow_syscall(libc::SYS_clone),
|
||||
allow_syscall(libc::SYS_close),
|
||||
allow_syscall(libc::SYS_connect),
|
||||
allow_syscall(libc::SYS_copy_file_range),
|
||||
allow_syscall(libc::SYS_dup),
|
||||
allow_syscall(libc::SYS_dup2),
|
||||
allow_syscall(libc::SYS_dup3),
|
||||
allow_syscall(libc::SYS_epoll_create1),
|
||||
allow_syscall(libc::SYS_epoll_ctl),
|
||||
allow_syscall(libc::SYS_epoll_pwait),
|
||||
allow_syscall(libc::SYS_eventfd2),
|
||||
allow_syscall(libc::SYS_epoll_wait),
|
||||
allow_syscall(libc::SYS_execve),
|
||||
allow_syscall(libc::SYS_exit),
|
||||
allow_syscall(libc::SYS_exit_group),
|
||||
allow_syscall(libc::SYS_fallocate),
|
||||
allow_syscall(libc::SYS_fchdir),
|
||||
allow_syscall(libc::SYS_fchmod),
|
||||
allow_syscall(libc::SYS_fchmodat),
|
||||
allow_syscall(libc::SYS_fchown),
|
||||
allow_syscall(libc::SYS_fchownat),
|
||||
allow_syscall(libc::SYS_fcntl),
|
||||
allow_syscall(libc::SYS_fork),
|
||||
allow_syscall(libc::SYS_fstat),
|
||||
allow_syscall(libc::SYS_ftruncate),
|
||||
allow_syscall(libc::SYS_futex),
|
||||
allow_syscall(libc::SYS_getcwd),
|
||||
allow_syscall(libc::SYS_getdents),
|
||||
allow_syscall(libc::SYS_getdents64),
|
||||
allow_syscall(libc::SYS_getegid),
|
||||
allow_syscall(libc::SYS_geteuid),
|
||||
allow_syscall(libc::SYS_getgid),
|
||||
allow_syscall(libc::SYS_getpid),
|
||||
allow_syscall(libc::SYS_getsockname),
|
||||
allow_syscall(libc::SYS_getsockopt),
|
||||
allow_syscall(libc::SYS_gettid),
|
||||
allow_syscall(libc::SYS_getrandom),
|
||||
allow_syscall(libc::SYS_getrlimit),
|
||||
allow_syscall(libc::SYS_getrusage),
|
||||
allow_syscall(libc::SYS_getuid),
|
||||
allow_syscall(libc::SYS_ioctl),
|
||||
allow_syscall(libc::SYS_lseek),
|
||||
allow_syscall(libc::SYS_lstat),
|
||||
allow_syscall(libc::SYS_madvise),
|
||||
// allow_syscall(libc::SYS_mmap),
|
||||
allow_syscall(libc::SYS_mkdir),
|
||||
allow_syscall(libc::SYS_mkdirat),
|
||||
allow_syscall(libc::SYS_mlock),
|
||||
allow_syscall(libc::SYS_mprotect),
|
||||
// allow_syscall(libc::SYS_munmap),
|
||||
allow_syscall(libc::SYS_nanosleep),
|
||||
allow_syscall(libc::SYS_newfstatat),
|
||||
allow_syscall(libc::SYS_open),
|
||||
allow_syscall(libc::SYS_openat),
|
||||
allow_syscall(libc::SYS_pipe2),
|
||||
allow_syscall(libc::SYS_poll),
|
||||
allow_syscall(libc::SYS_prctl),
|
||||
allow_syscall(libc::SYS_pread64),
|
||||
allow_syscall(libc::SYS_prlimit64),
|
||||
allow_syscall(libc::SYS_pwrite64),
|
||||
allow_syscall(libc::SYS_pwritev),
|
||||
allow_syscall(libc::SYS_read),
|
||||
allow_syscall(libc::SYS_readlink),
|
||||
allow_syscall(libc::SYS_readlinkat),
|
||||
allow_syscall(libc::SYS_rename),
|
||||
allow_syscall(libc::SYS_renameat),
|
||||
allow_syscall(libc::SYS_rmdir),
|
||||
allow_syscall(libc::SYS_rt_sigaction),
|
||||
allow_syscall(libc::SYS_rt_sigprocmask),
|
||||
allow_syscall(libc::SYS_rt_sigreturn),
|
||||
allow_syscall(libc::SYS_sched_getaffinity),
|
||||
allow_syscall(libc::SYS_sched_yield),
|
||||
allow_syscall(libc::SYS_select),
|
||||
allow_syscall(libc::SYS_set_robust_list),
|
||||
allow_syscall(libc::SYS_set_tid_address),
|
||||
allow_syscall(libc::SYS_setsockopt),
|
||||
allow_syscall(libc::SYS_sigaltstack),
|
||||
allow_syscall(libc::SYS_socket),
|
||||
allow_syscall(libc::SYS_socketpair),
|
||||
allow_syscall(libc::SYS_stat),
|
||||
allow_syscall(libc::SYS_statx),
|
||||
allow_syscall(libc::SYS_sysinfo),
|
||||
allow_syscall(libc::SYS_tgkill),
|
||||
allow_syscall(libc::SYS_unlinkat),
|
||||
allow_syscall(libc::SYS_umask),
|
||||
allow_syscall(libc::SYS_uname),
|
||||
allow_syscall(libc::SYS_unlink),
|
||||
allow_syscall(libc::SYS_utimensat),
|
||||
allow_syscall(libc::SYS_vfork),
|
||||
allow_syscall(libc::SYS_wait4),
|
||||
allow_syscall(libc::SYS_waitid),
|
||||
allow_syscall(libc::SYS_write),
|
||||
allow_syscall(libc::SYS_writev),
|
||||
]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn trace_syscall(syscall_number: i64) -> SyscallRuleSet {
|
||||
(
|
||||
syscall_number,
|
||||
// 为什么是 42?因为 42 是宇宙终极问题的答案
|
||||
vec![SeccompRule::new(vec![], SeccompAction::Trace(42))],
|
||||
)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use crate::river::Language;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::fs;
|
||||
|
||||
pub static STDIN_FILENAME: &str = "stdin.txt";
|
||||
pub static STDOUT_FILENAME: &str = "stdout.txt";
|
||||
pub static STDERR_FILENAME: &str = "stderr.txt";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref LANGUAGES: HashMap<i32, LanguageConf> = {
|
||||
let config = fs::read_to_string("languages.yaml").unwrap();
|
||||
let ls: Languages = serde_yaml::from_str(&config).unwrap();
|
||||
let mut m = HashMap::new();
|
||||
// add language
|
||||
m.insert(Language::C as i32, ls.C);
|
||||
m.insert(Language::Cpp as i32, ls.Cpp);
|
||||
m.insert(Language::Python as i32, ls.Python);
|
||||
m.insert(Language::Rust as i32, ls.Rust);
|
||||
m.insert(Language::Node as i32, ls.Node);
|
||||
m.insert(Language::TypeScript as i32, ls.TypeScript);
|
||||
m.insert(Language::Go as i32, ls.Go);
|
||||
m.insert(Language::Java as i32, ls.Java);
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LanguageConf {
|
||||
pub compile_cmd: String,
|
||||
pub code_file: String,
|
||||
pub run_cmd: String,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Languages {
|
||||
C: LanguageConf,
|
||||
Cpp: LanguageConf,
|
||||
Python: LanguageConf,
|
||||
Rust: LanguageConf,
|
||||
Node: LanguageConf,
|
||||
TypeScript: LanguageConf,
|
||||
Go: LanguageConf,
|
||||
Java: LanguageConf,
|
||||
}
|
69
src/error.rs
69
src/error.rs
@ -1,69 +0,0 @@
|
||||
use crate::river::judge_response::State;
|
||||
use crate::river::JudgeResponse;
|
||||
use crate::river::JudgeResult;
|
||||
use libc::strerror;
|
||||
use std::ffi::{CStr, NulError, OsString};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
CreateTempDirError(io::Error),
|
||||
LanguageNotFound(i32),
|
||||
FileWriteError(io::Error),
|
||||
FileReadError(io::Error),
|
||||
ChannelRecvError,
|
||||
StringToCStringError(NulError),
|
||||
OsStringToStringError(OsString),
|
||||
RemoveFileError(PathBuf),
|
||||
PathBufToStringError(PathBuf),
|
||||
UnknownRequestData,
|
||||
RequestDataNotFound,
|
||||
SyscallError(String),
|
||||
OpenFileError(PathBuf, io::Error),
|
||||
ReadFileError(PathBuf, io::Error),
|
||||
CustomError(String),
|
||||
JudgeThreadError(String),
|
||||
}
|
||||
|
||||
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::SyscallError(ref syscall) => {
|
||||
let errno = io::Error::last_os_error().raw_os_error();
|
||||
let reason = errno_str(errno);
|
||||
write!(f, "SyscallError: `{}` {}", syscall, reason)
|
||||
}
|
||||
Error::CustomError(ref reason) => write!(f, "{}", reason),
|
||||
_ => write!(f, "{:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_error(err: Error) -> JudgeResponse {
|
||||
warn!("{:?}", err);
|
||||
JudgeResponse {
|
||||
time_used: 0,
|
||||
memory_used: 0,
|
||||
state: Some(State::Result(JudgeResult::SystemError as i32)),
|
||||
stdout: "".into(),
|
||||
stderr: "".into(),
|
||||
errmsg: format!("{}", err).into(),
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
use super::error::{Error, Result};
|
||||
use std::env;
|
||||
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 {
|
||||
pub fn build(cmd: &String) -> Result<ExecArgs> {
|
||||
let cmds: Vec<&str> = cmd.split_whitespace().collect();
|
||||
let pathname = cmds[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 cmds.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 自动回收资源
|
||||
// TODO: 优先级较低,因为目前只在子进程里进行这个操作,且操作后会很快 exec,操作系统会回收这些内存
|
||||
println!("Dropping!");
|
||||
}
|
||||
}
|
170
src/judger.rs
170
src/judger.rs
@ -1,170 +0,0 @@
|
||||
use super::config::{LANGUAGES, STDERR_FILENAME, STDOUT_FILENAME};
|
||||
use super::error::{Error, Result};
|
||||
use super::process::Process;
|
||||
use super::runner::RunnerStatus;
|
||||
use crate::result::standard_result;
|
||||
use crate::river::judge_response::State;
|
||||
use crate::river::Language;
|
||||
use crate::river::{CompileData, JudgeData, JudgeResult, JudgeStatus};
|
||||
use crate::river::{JudgeRequest, JudgeResponse};
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
impl JudgeResponse {
|
||||
fn new() -> JudgeResponse {
|
||||
JudgeResponse {
|
||||
time_used: 0,
|
||||
memory_used: 0,
|
||||
errmsg: "".into(),
|
||||
stdout: "".into(),
|
||||
stderr: "".into(),
|
||||
state: Some(State::Status(JudgeStatus::Running as i32)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_process_status(self: &mut Self, status: &RunnerStatus) {
|
||||
self.time_used = status.time_used;
|
||||
self.memory_used = status.memory_used;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn judger(
|
||||
request: &JudgeRequest,
|
||||
data: &JudgeData,
|
||||
path: &Path,
|
||||
) -> Result<JudgeResponse> {
|
||||
let mut resp = JudgeResponse::new();
|
||||
let conf = match LANGUAGES.get(&request.language) {
|
||||
Some(c) => c,
|
||||
None => return Err(Error::LanguageNotFound(request.language)),
|
||||
};
|
||||
let process = Process::new(
|
||||
conf.run_cmd.to_string(),
|
||||
path.to_path_buf(),
|
||||
&data.in_data,
|
||||
data.time_limit,
|
||||
data.memory_limit,
|
||||
)?;
|
||||
debug!("run command: {}", conf.run_cmd);
|
||||
|
||||
// 开始执行并等待返回结果
|
||||
let mut runner = process.runner.clone();
|
||||
// 为 Java 虚拟机取消内存限制和 trace(万恶的 JVM)
|
||||
// 看起来虚拟机语言都有同样的问题
|
||||
if request.language == Language::Java as i32
|
||||
|| request.language == Language::Go as i32
|
||||
|| request.language == Language::Node as i32
|
||||
|| request.language == Language::TypeScript as i32
|
||||
{
|
||||
runner.memory_limit = -1;
|
||||
runner.traceme = false;
|
||||
}
|
||||
let status = runner.await?;
|
||||
|
||||
resp.set_process_status(&status);
|
||||
// 先判断 tle 和 mle,一是因为 tle 和 mle 也会导致信号中断,二是优先程序复杂度判断
|
||||
if status.time_used > data.time_limit.into() {
|
||||
// TLE
|
||||
resp.state = Some(State::Result(JudgeResult::TimeLimitExceeded as i32));
|
||||
} else if status.memory_used > data.memory_limit.into() {
|
||||
// MLE
|
||||
resp.state = Some(State::Result(JudgeResult::MemoryLimitExceeded as i32));
|
||||
} else if status.signal != 0 {
|
||||
// 被信号中断的程序,RE
|
||||
resp.stderr = match fs::read_to_string(path.join(STDERR_FILENAME)).await {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::FileReadError(e)),
|
||||
};
|
||||
resp.errmsg = format!("Program was interrupted by signal: {}", status.signal);
|
||||
resp.state = Some(State::Result(JudgeResult::RuntimeError as i32));
|
||||
} else if status.exit_code != 0 {
|
||||
// 返回值不为 0 的程序,RE(虽然有可能是用户自己返回的)
|
||||
resp.errmsg = format!("Exceptional program return code: {}", status.exit_code);
|
||||
resp.state = Some(State::Result(JudgeResult::RuntimeError as i32));
|
||||
} else {
|
||||
// 没有 ole 这种操作,之前 ole 就是错的
|
||||
let mut file = match fs::File::open(path.join(STDOUT_FILENAME)).await {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::OpenFileError(path.join(STDOUT_FILENAME), e)),
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
if let Err(e) = file.read_to_end(&mut out).await {
|
||||
return Err(Error::ReadFileError(path.join(STDOUT_FILENAME), e));
|
||||
};
|
||||
let result = standard_result(&out, &data.out_data)?;
|
||||
resp.state = Some(State::Result(result as i32));
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn compile(
|
||||
request: &JudgeRequest,
|
||||
data: &CompileData,
|
||||
path: &Path,
|
||||
) -> Result<JudgeResponse> {
|
||||
let mut resp = JudgeResponse::new();
|
||||
let conf = match LANGUAGES.get(&request.language) {
|
||||
Some(c) => c,
|
||||
None => return Err(Error::LanguageNotFound(request.language)),
|
||||
};
|
||||
// 写入代码
|
||||
if let Err(e) = fs::write(path.join(&conf.code_file), &data.code).await {
|
||||
return Err(Error::FileWriteError(e));
|
||||
};
|
||||
|
||||
debug!("build command: {}", conf.compile_cmd);
|
||||
let v = vec![];
|
||||
let process = Process::new(
|
||||
conf.compile_cmd.to_string(),
|
||||
path.to_path_buf(),
|
||||
&v,
|
||||
// 编译的资源限制为固定的
|
||||
10000,
|
||||
1024 * 1024,
|
||||
)?;
|
||||
|
||||
let mut runner = process.runner.clone();
|
||||
runner.traceme = false;
|
||||
// 为 Java 虚拟机取消内存限制(万恶的 JVM)
|
||||
if request.language == Language::Java as i32 || request.language == Language::Go as i32 {
|
||||
runner.memory_limit = -1;
|
||||
}
|
||||
if request.language == Language::Rust as i32 {
|
||||
// https://github.com/rust-lang/rust/issues/46345
|
||||
runner.memory_limit = -1;
|
||||
}
|
||||
let status = runner.await?;
|
||||
resp.set_process_status(&status);
|
||||
if status.exit_code != 0 || status.signal != 0 {
|
||||
debug!("compile exit code: {}", status.exit_code);
|
||||
debug!("compile signal: {}", status.signal);
|
||||
// 从 stdout 和 stderr 中获取错误信息
|
||||
resp.stdout = match fs::read_to_string(path.join(STDOUT_FILENAME)).await {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::FileReadError(e)),
|
||||
};
|
||||
resp.stderr = match fs::read_to_string(path.join(STDERR_FILENAME)).await {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(Error::FileReadError(e)),
|
||||
};
|
||||
debug!("stdout: {}", resp.stdout);
|
||||
debug!("stderr: {}", resp.stderr);
|
||||
debug!("errmsg: {}", status.errmsg);
|
||||
resp.state = Some(State::Result(JudgeResult::CompileError as i32));
|
||||
} else {
|
||||
resp.state = Some(State::Result(JudgeResult::CompileSuccess as i32));
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn pending() -> JudgeResponse {
|
||||
let mut resp = JudgeResponse::new();
|
||||
resp.state = Some(State::Status(JudgeStatus::Pending as i32));
|
||||
resp
|
||||
}
|
||||
|
||||
pub fn running() -> JudgeResponse {
|
||||
let resp = JudgeResponse::new();
|
||||
resp
|
||||
}
|
76
src/main.rs
76
src/main.rs
@ -5,25 +5,12 @@ extern crate log;
|
||||
use env_logger::Env;
|
||||
use futures::StreamExt;
|
||||
use futures_core::Stream;
|
||||
use river::judge_request::Data;
|
||||
use river::judge_response::State;
|
||||
use river::river_server::{River, RiverServer};
|
||||
use river::{JudgeRequest, JudgeResponse, JudgeResult};
|
||||
use river::{JudgeRequest, JudgeResponse, UploadFile, UploadState};
|
||||
use std::pin::Pin;
|
||||
use tempfile::tempdir_in;
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
mod allow;
|
||||
mod config;
|
||||
mod error;
|
||||
mod exec_args;
|
||||
mod judger;
|
||||
mod process;
|
||||
mod result;
|
||||
mod runner;
|
||||
mod seccomp;
|
||||
|
||||
pub mod river {
|
||||
tonic::include_proto!("river");
|
||||
}
|
||||
@ -35,66 +22,23 @@ pub struct RiverService {}
|
||||
impl River for RiverService {
|
||||
type JudgeStream =
|
||||
Pin<Box<dyn Stream<Item = Result<JudgeResponse, Status>> + Send + Sync + 'static>>;
|
||||
async fn upload(&self, _request: Request<UploadFile>) -> Result<Response<UploadState>, Status> {
|
||||
let state = river::UploadState {
|
||||
state: Some(river::upload_state::State::Filepath("Success".to_string())),
|
||||
};
|
||||
Ok(Response::new(state))
|
||||
}
|
||||
|
||||
async fn judge(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<JudgeRequest>>,
|
||||
) -> Result<Response<Self::JudgeStream>, Status> {
|
||||
let mut stream = request.into_inner();
|
||||
let mut _stream = request.into_inner();
|
||||
|
||||
let output = async_stream::try_stream! {
|
||||
// 创建评测使用的临时目录
|
||||
// 无需主动删除,变量在 drop 之后会自动删除临时文件夹
|
||||
let pwd = match tempdir_in("./runner") {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
yield error::system_error(error::Error::CreateTempDirError(err));
|
||||
return
|
||||
},
|
||||
yield JudgeResponse {
|
||||
state: Some(river::judge_response::State::Status(river::JudgeStatus::Pending as i32))
|
||||
};
|
||||
// 是否通过编译
|
||||
let mut compile_success = false;
|
||||
debug!("create tempdir: {:?}", pwd);
|
||||
|
||||
while let Some(req) = stream.next().await {
|
||||
// TODO: 使用锁或者资源量等机制限制并发
|
||||
yield judger::pending();
|
||||
|
||||
let req = req?;
|
||||
|
||||
yield judger::running();
|
||||
let result = match &req.data {
|
||||
Some(Data::CompileData(data)) => {
|
||||
debug!("compile request");
|
||||
judger::compile(&req, &data, &pwd.path()).await
|
||||
},
|
||||
Some(Data::JudgeData(data)) => {
|
||||
debug!("judge request");
|
||||
// 必须通过编译才能运行
|
||||
if !compile_success {
|
||||
Ok(error::system_error(error::Error::CustomError("Please compile first".to_string())))
|
||||
} else {
|
||||
judger::judger(&req, &data, &pwd.path()).await
|
||||
}
|
||||
},
|
||||
None => Err(error::Error::RequestDataNotFound),
|
||||
_ => Err(error::Error::UnknownRequestData),
|
||||
};
|
||||
let result = match result {
|
||||
Ok(res) => res,
|
||||
Err(e) => error::system_error(e)
|
||||
};
|
||||
// 如果通过了编译,则标记为成功
|
||||
if let Some(Data::CompileData(_)) = &req.data {
|
||||
if let Some(State::Result(rst)) = result.state {
|
||||
if rst == JudgeResult::CompileSuccess as i32 {
|
||||
compile_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
yield result;
|
||||
}
|
||||
while let Some(_) = stream.next().await {}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output) as Self::JudgeStream))
|
||||
|
160
src/memory.c
160
src/memory.c
@ -1,160 +0,0 @@
|
||||
#include <ctype.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* in: ' 42964 kB'
|
||||
* out: 42964
|
||||
* */
|
||||
int GetNumByVmLine(char body[])
|
||||
{
|
||||
int offset, ans, start;
|
||||
offset = ans = 0;
|
||||
start = 0; // FALSE on C
|
||||
while (1)
|
||||
{
|
||||
if (!start)
|
||||
{
|
||||
if (isdigit(body[offset]))
|
||||
{
|
||||
start = 1; // TRUE on C
|
||||
}
|
||||
else
|
||||
{
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (start)
|
||||
{
|
||||
if (isdigit(body[offset]))
|
||||
{
|
||||
ans *= 10;
|
||||
ans += (int)(body[offset] - '0');
|
||||
offset++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
long MemoryUsage(int fd)
|
||||
{
|
||||
int i;
|
||||
ssize_t len;
|
||||
char body[4096];
|
||||
long vm_data = 0, vm_stk = 0;
|
||||
|
||||
if ((len = pread(fd, body, 4096, 0)) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
switch (body[i])
|
||||
{
|
||||
case 'V':
|
||||
goto V;
|
||||
default:
|
||||
goto NEXTLINE;
|
||||
}
|
||||
V:
|
||||
i++;
|
||||
switch (body[i])
|
||||
{
|
||||
case 'm':
|
||||
goto Vm;
|
||||
default:
|
||||
goto NEXTLINE;
|
||||
}
|
||||
Vm:
|
||||
i++;
|
||||
switch (body[i])
|
||||
{
|
||||
case 'R':
|
||||
i += 2;
|
||||
goto VmRSS;
|
||||
case 'D':
|
||||
i += 3;
|
||||
goto VmData;
|
||||
case 'S':
|
||||
goto VmS;
|
||||
case 'E':
|
||||
i += 2;
|
||||
goto VmExe;
|
||||
case 'L':
|
||||
goto VmL;
|
||||
default:
|
||||
goto NEXTLINE;
|
||||
}
|
||||
|
||||
VmRSS:
|
||||
i += 2;
|
||||
// vm_rss = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
VmData:
|
||||
i += 2;
|
||||
vm_data = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
VmS:
|
||||
i++;
|
||||
switch (body[i])
|
||||
{
|
||||
case 't':
|
||||
i++;
|
||||
goto VmStk;
|
||||
case 'i':
|
||||
i += 2;
|
||||
goto VmSize;
|
||||
default:
|
||||
goto NEXTLINE;
|
||||
}
|
||||
|
||||
VmStk:
|
||||
i += 2;
|
||||
vm_stk = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
VmSize:
|
||||
i += 2;
|
||||
// vm_size = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
VmExe:
|
||||
i += 2;
|
||||
// vm_exe = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
VmL:
|
||||
i++;
|
||||
switch (body[i])
|
||||
{
|
||||
case 'i':
|
||||
i++;
|
||||
goto VmLib;
|
||||
}
|
||||
|
||||
VmLib:
|
||||
i += 2;
|
||||
// vm_lib = GetNumByVmLine(body + i);
|
||||
goto NEXTLINE;
|
||||
|
||||
NEXTLINE:
|
||||
while (body[i] != '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return vm_data + vm_stk;
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use super::config::STDIN_FILENAME;
|
||||
use super::error::{Error, Result};
|
||||
use super::runner::Runner;
|
||||
use libc;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct Process {
|
||||
// 因为 await 会获取所有权,导致 await 执行完会直接 drop
|
||||
// 因此资源不能直接绑定要 await 的目标,否则会在出现在收集需要的信息之前资源已经被回收
|
||||
// 同时,直接将结构传递给子进程也有可能会出现 double drop 的情况
|
||||
// 因此,此处需要抽离一层
|
||||
pub runner: Runner,
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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,
|
||||
traceme: true,
|
||||
cmd: cmd,
|
||||
workdir: workdir,
|
||||
tx: Arc::new(Mutex::new(tx)),
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
let mut status = 0;
|
||||
let pid;
|
||||
unsafe {
|
||||
pid = libc::waitpid(self.runner.pid, &mut status, libc::WNOHANG);
|
||||
}
|
||||
// > 0: 对应子进程退出但未回收资源
|
||||
// = 0: 对应子进程存在但未退出
|
||||
// 如果在运行过程中上层异常中断,则需要 kill 子进程并回收资源
|
||||
if pid >= 0 {
|
||||
unsafe {
|
||||
libc::kill(self.runner.pid, 9);
|
||||
libc::waitpid(self.runner.pid, &mut status, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn hello() {
|
||||
let s = String::from("hello");
|
||||
let bytes = s.into_bytes();
|
||||
assert_eq!(&[104, 101, 108, 108, 111][..], &bytes[..]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn run() {}
|
||||
}
|
183
src/result.rs
183
src/result.rs
@ -1,183 +0,0 @@
|
||||
use super::error::Result;
|
||||
use crate::river::JudgeResult;
|
||||
|
||||
pub fn standard_result(out: &[u8], ans: &[u8]) -> Result<JudgeResult> {
|
||||
let out_len = out.len();
|
||||
let ans_len = ans.len();
|
||||
let mut out_offset = 0;
|
||||
let mut ans_offset = 0;
|
||||
// 没有 PE,PE 直接 WA
|
||||
let mut r = JudgeResult::Accepted;
|
||||
while out_offset <= out_len && ans_offset <= ans_len {
|
||||
let (out_start, out_end, out_exists) = next_line(&out, out_offset, out_len);
|
||||
let (ans_start, ans_end, ans_exists) = next_line(&ans, ans_offset, ans_len);
|
||||
if !out_exists || !ans_exists {
|
||||
// 如果一个已经读取完但另一个还有数据,则结果为 WA
|
||||
if out_exists != ans_exists {
|
||||
r = JudgeResult::WrongAnswer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 如果两个数据当前行长度不同,则结果为 WA(这个长度已经排除了末尾空白符号)
|
||||
if out_end - out_start != ans_end - ans_start {
|
||||
r = JudgeResult::WrongAnswer;
|
||||
break;
|
||||
}
|
||||
let line_len = out_end - out_start;
|
||||
for i in 0..line_len {
|
||||
// 逐个对比
|
||||
if out[out_start + i] != ans[ans_start + i] {
|
||||
r = JudgeResult::WrongAnswer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果结果出来了,则退出循环
|
||||
if r != JudgeResult::Accepted {
|
||||
break;
|
||||
}
|
||||
out_offset = out_end;
|
||||
ans_offset = ans_end;
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略空行与每行末尾的空格与制表符
|
||||
* 如果某行只有空白字符,则忽略此行
|
||||
* "Hello ; "
|
||||
* " "
|
||||
* " World"
|
||||
* -----------------
|
||||
* "Hello ;"
|
||||
* " World"
|
||||
*/
|
||||
fn next_line(v: &[u8], offset: usize, len: usize) -> (usize, usize, bool) {
|
||||
let mut line_offset = offset;
|
||||
let mut left = 0;
|
||||
let mut right = len;
|
||||
let mut has_line = false;
|
||||
while line_offset < len {
|
||||
let ch = v[line_offset] as char;
|
||||
// 当读取到某行结束时
|
||||
if ch == '\n' {
|
||||
if has_line {
|
||||
// 如果已经有新行的数据,则在这个位置结束
|
||||
right = line_offset;
|
||||
break;
|
||||
} else {
|
||||
// 如果还没有数据,说明整行为空,忽略当前行,将下一行设为起点重复过程
|
||||
left = line_offset + 1;
|
||||
}
|
||||
}
|
||||
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
|
||||
// 空白字符
|
||||
} else {
|
||||
// 非空白字符
|
||||
has_line = true;
|
||||
}
|
||||
line_offset += 1;
|
||||
}
|
||||
// 排除该行末尾的空白字符
|
||||
while left < right {
|
||||
let ch = v[right - 1] as char;
|
||||
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
|
||||
// 空白字符
|
||||
} else {
|
||||
// 非空白字符
|
||||
break;
|
||||
}
|
||||
right -= 1;
|
||||
}
|
||||
(left, right, has_line)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::river::JudgeResult;
|
||||
#[test]
|
||||
fn test1() {
|
||||
let v: &[u8] = "Hello World!".as_bytes();
|
||||
let (l, r, e) = next_line(v, 0, v.len());
|
||||
assert_eq!(l, 0);
|
||||
assert_eq!(r, 12);
|
||||
assert!(e);
|
||||
let (_l, _r, e) = next_line(v, r, v.len());
|
||||
assert!(!e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test2() {
|
||||
let v: &[u8] = "Hello World! ".as_bytes();
|
||||
let (l, r, e) = next_line(v, 0, v.len());
|
||||
assert_eq!(l, 0);
|
||||
assert_eq!(r, 12);
|
||||
assert!(e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test3() {
|
||||
let v: &[u8] = " Hello World!".as_bytes();
|
||||
let (l, r, e) = next_line(v, 0, v.len());
|
||||
assert_eq!(l, 0);
|
||||
assert_eq!(r, 15);
|
||||
assert!(e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test4() {
|
||||
let v: &[u8] = " Hello World!\n Hello World!\n\n\n\n \n\n\n\n\n\n \t\t\t \t\n\n\n\n".as_bytes();
|
||||
let (l, r, e) = next_line(v, 0, v.len());
|
||||
assert_eq!(l, 0);
|
||||
assert_eq!(r, 15);
|
||||
assert!(e);
|
||||
let (l, r, e) = next_line(v, r, v.len());
|
||||
assert_eq!(l, 16);
|
||||
assert_eq!(r, 31);
|
||||
assert!(e);
|
||||
let (_l, _r, e) = next_line(v, r, v.len());
|
||||
assert!(!e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test5() {
|
||||
let ans: &[u8] = "Hello World!".as_bytes();
|
||||
let out: &[u8] = "Hello World!".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test6() {
|
||||
let ans: &[u8] = "Hello World!".as_bytes();
|
||||
let out: &[u8] = "Hello World! ".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test7() {
|
||||
let ans: &[u8] = "Hello World! \n\n\n\n \n\n\n\n".as_bytes();
|
||||
let out: &[u8] = "Hello World!\t\t\t\t\n\n\n\n \n\n\n\n\t\t\t\t".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test8() {
|
||||
let ans: &[u8] = "Hello World!".as_bytes();
|
||||
let out: &[u8] = "".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::WrongAnswer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test9() {
|
||||
let ans: &[u8] = "".as_bytes();
|
||||
let out: &[u8] = "".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test10() {
|
||||
let ans: &[u8] = "Hello World!".as_bytes();
|
||||
let out: &[u8] = "Hello World!\n".as_bytes();
|
||||
assert_eq!(standard_result(out, ans).unwrap(), JudgeResult::Accepted);
|
||||
}
|
||||
}
|
421
src/runner.rs
421
src/runner.rs
@ -1,421 +0,0 @@
|
||||
extern crate nix;
|
||||
|
||||
use super::allow::{gen_rules, trace_syscall};
|
||||
use super::config::{STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
|
||||
use super::error::{errno_str, Error, Result};
|
||||
use super::exec_args::ExecArgs;
|
||||
use super::seccomp::*;
|
||||
use nix::unistd::close;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::fs::{remove_file, File};
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
use std::thread;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
fn MemoryUsage(fd: i32) -> i64;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Runner {
|
||||
pub pid: i32,
|
||||
pub workdir: PathBuf,
|
||||
pub time_limit: i32,
|
||||
pub memory_limit: i32,
|
||||
pub traceme: bool,
|
||||
pub cmd: String,
|
||||
pub tx: Arc<Mutex<mpsc::Sender<RunnerStatus>>>,
|
||||
pub rx: Arc<Mutex<mpsc::Receiver<RunnerStatus>>>,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
fn set_pid(&mut self, pid: i32) {
|
||||
self.pid = pid;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RunnerStatus {
|
||||
pub rusage: libc::rusage,
|
||||
pub exit_code: i32,
|
||||
pub status: i32,
|
||||
pub signal: i32,
|
||||
pub time_used: i64,
|
||||
pub memory_used: i64,
|
||||
pub real_time_used: u128,
|
||||
pub errmsg: String,
|
||||
}
|
||||
|
||||
const ITIMER_REAL: libc::c_int = 0;
|
||||
extern "C" {
|
||||
#[cfg_attr(
|
||||
all(target_os = "macos", target_arch = "x86"),
|
||||
link_name = "setitimer$UNIX2003"
|
||||
)]
|
||||
fn setitimer(
|
||||
which: libc::c_int,
|
||||
new_value: *const libc::itimerval,
|
||||
old_value: *mut libc::itimerval,
|
||||
) -> libc::c_int;
|
||||
}
|
||||
|
||||
impl Future for Runner {
|
||||
type Output = Result<RunnerStatus>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<RunnerStatus>> {
|
||||
let runner = Pin::into_inner(self);
|
||||
// 如果 pid == -1,则说明子进程还没有运行,开始进程
|
||||
if runner.pid == -1 {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
// 因为 poll 和 spawn 都需要 process 的所有权,这是矛盾的
|
||||
// 因此此处进行 clone,需要处理因此产生的 double drop 的问题
|
||||
let mut runner_clone = runner.clone();
|
||||
let waker = cx.waker().clone();
|
||||
thread::spawn(move || {
|
||||
let pid;
|
||||
unsafe {
|
||||
pid = libc::fork();
|
||||
}
|
||||
if pid == 0 {
|
||||
runner_clone.run();
|
||||
} else if pid > 0 {
|
||||
tx.send(pid).unwrap();
|
||||
runner_clone.set_pid(pid);
|
||||
let status = runner_clone.wait();
|
||||
let status_tx = runner_clone.tx.lock().unwrap();
|
||||
status_tx.send(status).unwrap();
|
||||
waker.wake();
|
||||
} else {
|
||||
panic!("How dare you!");
|
||||
}
|
||||
});
|
||||
|
||||
// 等待子线程启动子进程并返回 pid
|
||||
let pid = match rx.recv() {
|
||||
Ok(val) => val,
|
||||
Err(_) => return Poll::Ready(Err(Error::ChannelRecvError)),
|
||||
};
|
||||
runner.set_pid(pid);
|
||||
return Poll::Pending;
|
||||
} else {
|
||||
// 再次进入 poll,说明子进程已经结束,通知了 wake
|
||||
// 此时 channel 应该是有数据的
|
||||
let status = match runner.rx.lock() {
|
||||
Ok(rx) => match rx.recv() {
|
||||
Ok(val) => val,
|
||||
Err(_) => return Poll::Ready(Err(Error::ChannelRecvError)),
|
||||
},
|
||||
Err(_) => return Poll::Ready(Err(Error::ChannelRecvError)),
|
||||
};
|
||||
// 处理评测进程的异常
|
||||
// 程序本身无法发出负数的 signal,因此此处使用负数作为异常标识
|
||||
if status.signal < 0 {
|
||||
return Poll::Ready(Err(Error::JudgeThreadError(status.errmsg)));
|
||||
}
|
||||
return Poll::Ready(Ok(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// 子进程里崩溃也无法返回,崩溃就直接崩溃了
|
||||
let exec_args = ExecArgs::build(&self.cmd).unwrap();
|
||||
// 修改工作目录
|
||||
env::set_current_dir(&self.workdir).unwrap();
|
||||
let mut rl = libc::rlimit {
|
||||
rlim_cur: 0,
|
||||
rlim_max: 0,
|
||||
};
|
||||
// 实际运行时间限制设置为 CPU 时间 + 2 * 2,尽量在防止恶意代码占用评测资源的情况下给正常用户的代码最宽松的环境
|
||||
let rt = libc::itimerval {
|
||||
it_interval: libc::timeval {
|
||||
tv_sec: 0,
|
||||
tv_usec: 0,
|
||||
},
|
||||
it_value: libc::timeval {
|
||||
tv_sec: i64::from(self.time_limit / 1000 + 2) * 2,
|
||||
tv_usec: 0,
|
||||
},
|
||||
};
|
||||
unsafe {
|
||||
// 重定向文件描述符
|
||||
dup(STDIN_FILENAME, libc::STDIN_FILENO, libc::O_RDONLY, 0o644);
|
||||
if Path::new(STDOUT_FILENAME).exists() {
|
||||
remove_file(STDOUT_FILENAME).unwrap();
|
||||
}
|
||||
dup(
|
||||
STDOUT_FILENAME,
|
||||
libc::STDOUT_FILENO,
|
||||
libc::O_CREAT | libc::O_RDWR,
|
||||
0o644,
|
||||
);
|
||||
if Path::new(STDERR_FILENAME).exists() {
|
||||
remove_file(STDERR_FILENAME).unwrap();
|
||||
}
|
||||
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!");
|
||||
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
|
||||
panic!("How dare you!");
|
||||
}
|
||||
// CPU 时间限制,粒度为 S
|
||||
rl.rlim_cur = (self.time_limit as u64) / 1000 + 1;
|
||||
rl.rlim_max = rl.rlim_cur + 1;
|
||||
if libc::setrlimit(libc::RLIMIT_CPU, &rl) != 0 {
|
||||
eprintln!("setrlimit failure!");
|
||||
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
|
||||
panic!("How dare you!");
|
||||
}
|
||||
// 设置内存限制
|
||||
if self.memory_limit > 0 {
|
||||
rl.rlim_cur = (self.memory_limit as u64) * 1024;
|
||||
rl.rlim_max = rl.rlim_cur + 1024;
|
||||
if libc::setrlimit(libc::RLIMIT_DATA, &rl) != 0 {
|
||||
eprintln!("setrlimit failure!");
|
||||
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
|
||||
panic!("How dare you!");
|
||||
}
|
||||
if libc::setrlimit(libc::RLIMIT_AS, &rl) != 0 {
|
||||
eprintln!("setrlimit failure!");
|
||||
eprintln!("{:?}", io::Error::last_os_error().raw_os_error());
|
||||
panic!("How dare you!");
|
||||
}
|
||||
}
|
||||
if self.traceme {
|
||||
// 设置 trace 模式
|
||||
libc::ptrace(libc::PTRACE_TRACEME, 0, 0, 0);
|
||||
// 发送信号以确保父进程先执行
|
||||
libc::kill(libc::getpid(), libc::SIGSTOP);
|
||||
}
|
||||
let mut filter =
|
||||
SeccompFilter::new(gen_rules().into_iter().collect(), SeccompAction::Kill).unwrap();
|
||||
if self.traceme {
|
||||
let (syscall_number, rules) = trace_syscall(libc::SYS_brk);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = trace_syscall(libc::SYS_mmap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = trace_syscall(libc::SYS_munmap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = trace_syscall(libc::SYS_mremap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
} else {
|
||||
let (syscall_number, rules) = allow_syscall(libc::SYS_brk);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = allow_syscall(libc::SYS_mmap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = allow_syscall(libc::SYS_munmap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
let (syscall_number, rules) = allow_syscall(libc::SYS_mremap);
|
||||
filter.add_rules(syscall_number, rules).unwrap();
|
||||
}
|
||||
SeccompFilter::apply(filter.try_into().unwrap()).unwrap();
|
||||
libc::execve(exec_args.pathname, exec_args.argv, exec_args.envp);
|
||||
libc::kill(libc::getpid(), libc::SIGKILL);
|
||||
}
|
||||
panic!("How dare you!");
|
||||
}
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn wait(&self) -> RunnerStatus {
|
||||
let pid = self.pid;
|
||||
let start = SystemTime::now();
|
||||
let mut status = 0;
|
||||
let mut rusage = libc::rusage {
|
||||
ru_utime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_stime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_maxrss: 0 as libc::c_long,
|
||||
ru_ixrss: 0 as libc::c_long,
|
||||
ru_idrss: 0 as libc::c_long,
|
||||
ru_isrss: 0 as libc::c_long,
|
||||
ru_minflt: 0 as libc::c_long,
|
||||
ru_majflt: 0 as libc::c_long,
|
||||
ru_nswap: 0 as libc::c_long,
|
||||
ru_inblock: 0 as libc::c_long,
|
||||
ru_oublock: 0 as libc::c_long,
|
||||
ru_msgsnd: 0 as libc::c_long,
|
||||
ru_msgrcv: 0 as libc::c_long,
|
||||
ru_nsignals: 0 as libc::c_long,
|
||||
ru_nvcsw: 0 as libc::c_long,
|
||||
ru_nivcsw: 0 as libc::c_long,
|
||||
};
|
||||
// 程序占用内存定义为程序数据段 + 栈大小
|
||||
// from: https://www.hackerearth.com/practice/notes/vivekprakash/technical-diving-into-memory-used-by-a-program-in-online-judges/
|
||||
// VmRSS 为程序当前驻留在物理内存中的大小,对虚拟内存等无效
|
||||
let mut vm_mem = 0;
|
||||
let status_file = format!("/proc/{}/status", pid);
|
||||
let file = match File::open(status_file) {
|
||||
Ok(val) => val,
|
||||
Err(_) => {
|
||||
return judge_error(format!(
|
||||
"open file `{}` failure!",
|
||||
format!("/proc/{}/status", pid)
|
||||
))
|
||||
}
|
||||
};
|
||||
let status_fd = file.into_raw_fd();
|
||||
let child_proc_str = format!("/proc/{}", pid);
|
||||
let child_proc = Path::new(&child_proc_str);
|
||||
|
||||
unsafe {
|
||||
if self.traceme {
|
||||
// 设置 trace 模式与 seccomp 的互动
|
||||
libc::waitpid(pid, &mut status, 0);
|
||||
libc::ptrace(libc::PTRACE_SETOPTIONS, pid, 0, libc::PTRACE_O_TRACESECCOMP);
|
||||
// 控制子进程恢复执行
|
||||
libc::ptrace(libc::PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
loop {
|
||||
if self.traceme {
|
||||
// in call
|
||||
// seccomp 会在系统调用之前触发 trace,因此此处空等一次,等待到系统调用返回时的 trace
|
||||
libc::ptrace(libc::PTRACE_SYSCALL, pid, 0, 0);
|
||||
libc::waitpid(pid, &mut status, 0);
|
||||
|
||||
let vmem = MemoryUsage(status_fd);
|
||||
if vm_mem < vmem {
|
||||
vm_mem = vmem;
|
||||
}
|
||||
// trace 模式下,如果检测到内存已经超出限制,则直接 kill & break
|
||||
if vm_mem > self.memory_limit.into() {
|
||||
debug!("MemoryLimitExceeded! break");
|
||||
libc::kill(pid, libc::SIGKILL);
|
||||
break;
|
||||
}
|
||||
// debug!("vm_mem: {}", vm_mem);
|
||||
|
||||
// 控制子进程恢复执行
|
||||
libc::ptrace(libc::PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
// out call
|
||||
// 等待子进程结束
|
||||
if !child_proc.exists() {
|
||||
return judge_error("Process exited abnormally".to_string());
|
||||
}
|
||||
if libc::wait4(pid, &mut status, 0, &mut rusage) < 0 || libc::WIFEXITED(status) {
|
||||
debug!("exited: {}", libc::WIFEXITED(status));
|
||||
break;
|
||||
}
|
||||
// debug!("exited: {}", libc::WIFEXITED(status));
|
||||
}
|
||||
}
|
||||
|
||||
match close(status_fd) {
|
||||
Ok(_) => {}
|
||||
Err(_) => return judge_error("close status file failure!".to_string()),
|
||||
};
|
||||
let mut exit_code = 0;
|
||||
let exited = libc::WIFEXITED(status);
|
||||
if exited {
|
||||
exit_code = libc::WEXITSTATUS(status);
|
||||
}
|
||||
let signal = if libc::WIFSIGNALED(status) {
|
||||
libc::WTERMSIG(status)
|
||||
} else if libc::WIFSTOPPED(status) {
|
||||
libc::WSTOPSIG(status)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// TODO: 添加 CGroup 的量度
|
||||
let time_used = rusage.ru_utime.tv_sec * 1000
|
||||
+ i64::from(rusage.ru_utime.tv_usec) / 1000
|
||||
+ rusage.ru_stime.tv_sec * 1000
|
||||
+ i64::from(rusage.ru_stime.tv_usec) / 1000;
|
||||
|
||||
let memory_used = if self.traceme {
|
||||
vm_mem
|
||||
} else {
|
||||
rusage.ru_maxrss
|
||||
};
|
||||
let real_time_used = match start.elapsed() {
|
||||
Ok(elapsed) => elapsed.as_millis(),
|
||||
Err(_) => return judge_error("real time elapsed failure!".to_string()),
|
||||
};
|
||||
return RunnerStatus {
|
||||
rusage: rusage,
|
||||
exit_code: exit_code,
|
||||
status: status,
|
||||
signal: signal,
|
||||
time_used: time_used,
|
||||
memory_used: memory_used,
|
||||
real_time_used: real_time_used,
|
||||
errmsg: "".to_string(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn judge_error(errmsg: String) -> RunnerStatus {
|
||||
RunnerStatus {
|
||||
rusage: libc::rusage {
|
||||
ru_utime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_stime: libc::timeval {
|
||||
tv_sec: 0 as libc::time_t,
|
||||
tv_usec: 0 as libc::suseconds_t,
|
||||
},
|
||||
ru_maxrss: 0 as libc::c_long,
|
||||
ru_ixrss: 0 as libc::c_long,
|
||||
ru_idrss: 0 as libc::c_long,
|
||||
ru_isrss: 0 as libc::c_long,
|
||||
ru_minflt: 0 as libc::c_long,
|
||||
ru_majflt: 0 as libc::c_long,
|
||||
ru_nswap: 0 as libc::c_long,
|
||||
ru_inblock: 0 as libc::c_long,
|
||||
ru_oublock: 0 as libc::c_long,
|
||||
ru_msgsnd: 0 as libc::c_long,
|
||||
ru_msgrcv: 0 as libc::c_long,
|
||||
ru_nsignals: 0 as libc::c_long,
|
||||
ru_nvcsw: 0 as libc::c_long,
|
||||
ru_nivcsw: 0 as libc::c_long,
|
||||
},
|
||||
exit_code: -1,
|
||||
status: -1,
|
||||
signal: -1,
|
||||
time_used: -1,
|
||||
memory_used: -1,
|
||||
real_time_used: 0,
|
||||
errmsg: errmsg,
|
||||
}
|
||||
}
|
1170
src/seccomp.rs
1170
src/seccomp.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user