重构代码结构

This commit is contained in:
MeiK2333 2020-12-07 17:09:14 +08:00
parent a17d1a7f25
commit 664564e79b
80 changed files with 42 additions and 3571 deletions

View File

@ -3,23 +3,3 @@
## 环境要求
- linux
## Example
```bash
cd example
python3 main.py
```
## TODOs
已经完成基本功能,后续需要优化
- 基于 cgroups 的资源控制
- 用户、组限制
- 安全测试
- 优化 args 生成代码,减少测量出的用户代码执行时间
- special judge
- docker 部署
- 更多语言支持
- 使用环境变量等机制自定义评测目录

View File

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

@ -1,8 +0,0 @@
*.pyc
venv/
__pycache__/
*.out
*.class
package.json
package-lock.json
node_modules

View File

View File

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

View File

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

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

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

View File

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

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

View File

@ -1,6 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}

View File

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

View File

@ -1 +0,0 @@
2 3

View File

@ -1,9 +0,0 @@
package main
import "fmt"
func main() {
var a, b int
fmt.Scan(&a)
fmt.Scan(&b)
fmt.Println(a + b)
}

View File

@ -1 +0,0 @@
5

View File

@ -1,5 +0,0 @@
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

View File

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

View File

@ -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();
}
}

View File

@ -1 +0,0 @@
2 3

View File

@ -1 +0,0 @@
5

View File

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

View File

@ -1 +0,0 @@
console.log("Hello World!");

View File

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

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

View File

@ -1 +0,0 @@
print("Hello World!")

View File

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

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

@ -1,4 +0,0 @@
grpcio==1.33.1
grpcio-tools==1.33.1
protobuf==3.13.0
six==1.15.0

View File

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

View File

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

View File

@ -1,3 +0,0 @@
fn main() {
println!("Hello World!");
}

View File

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

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

@ -1 +0,0 @@
main.js

View File

View File

@ -1 +0,0 @@
console.log("Hello World!");

View File

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

View File

@ -1 +0,0 @@
main.js

View File

@ -1 +0,0 @@
2 3

View File

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

View File

@ -1 +0,0 @@
5

View File

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

View File

@ -1 +0,0 @@
node_modules/

View File

@ -1,5 +0,0 @@
```bash
cd plugins/js
./validate.js success.js
./validate.js failure.js
```

View File

@ -1,6 +0,0 @@
return 42; // should be inside a function
function f() {
'use strict';
var x = 042;
with (z) { }
}

View File

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

View File

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

View File

@ -1 +0,0 @@
console.log("Hello World!");

View File

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

View File

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

@ -1,7 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
!package.json
!tsconfig.json

View File

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

View File

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

View File

@ -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))],
)
}

View File

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

View File

@ -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(),
}
}

View File

@ -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!");
}
}

View File

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

View File

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

View File

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

View File

@ -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() {}
}

View File

@ -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;
// 没有 PEPE 直接 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);
}
}

View File

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

File diff suppressed because it is too large Load Diff