Merge pull request #28 from slimeoj/nodejs

judger nodejs binding initialize 🎉
This commit is contained in:
李扬 2019-04-28 14:29:08 +08:00 committed by GitHub
commit e828f1ea4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 592 additions and 0 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ build/
__pycache__
*.pyc
cmake-build-debug/
.ycm_extra_conf.py
node_modules/

View File

@ -0,0 +1,9 @@
{
"targets": [
{
"target_name": "judger",
'cflags_cc!': [ '-std=c++11','-O2' ],
"sources": [ "judger.cc"],
}
]
}

234
bindings/NodeJS/judger.cc Normal file
View File

@ -0,0 +1,234 @@
#include "node.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/wait.h>
#include <unistd.h>
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::JSON;
using v8::Local;
using v8::Int32;
using v8::Array;
using v8::Integer;
using v8::Uint32;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;
using v8::MaybeLocal;
using v8::Exception;
using v8::Context;
/* 转成数字 */
bool ToNumber(Isolate * isolate,Local<Value> &args,char * key,std::string &str){
Local<Context> context= isolate->GetCurrentContext();
Local<Value> val = args->ToObject()->Get(context,String::NewFromUtf8(isolate,key)).ToLocalChecked();
if(val->IsNullOrUndefined())
return true;
else if( val->IsNumber()){
Local<Integer> num = val->ToInteger(context).ToLocalChecked();
str+=" --";
str+=std::string(key);
str+="=";
str+=std::to_string(num->Value());
return true;
}
else {
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,"typeof argument must be Object!")));
return false;
}
}
char * _ToCharPTR(Local<Value> &val,Local<Context> &context,char * str_content){
String::Utf8Value str_val( val->ToString(context).ToLocalChecked());
strcpy(str_content,*str_val);
return str_content;
}
/* 转成字符串 */
bool ToCStr(Isolate * isolate,Local<Value> &args,char * key,std::string &str){
char str_content[255]={0};
Local<Context> context= isolate->GetCurrentContext();
Local<Value> val;
if(args->IsObject())
val = args->ToObject()->Get(context,String::NewFromUtf8(isolate,key)).ToLocalChecked();
else
val = args;
if(val->IsNullOrUndefined())
return true;
else if(val->IsString()){
_ToCharPTR(val,context,str_content);
str+=" --";
str+=std::string(key);
str+="=";
str+=std::string(str_content);
return true;
}
else {
char ret[100];
sprintf(ret,"typeof %s must be String!",key);
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,ret)));
return false;
}
}
/**
* :
* {
* max_cpu_time
* max_real_time
* max_memory
* memory_limit_check_only ??
* max_stack
* max_process
* max_output_size
* exe_path
* input_path
* output_path
* error_path
* args:[]
* env:[]
* }
*
* {
* cpu_time
* real_time
* memory
* signal
* exit_code
* result
* error:
* }
*/
void Method(const FunctionCallbackInfo<Value>& args)
{
Isolate* isolate = args.GetIsolate();
Local<Context> context= isolate->GetCurrentContext();
std::string _args = "/usr/lib/judger/libjudger.so";
if( !args[0]->IsObject()){
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,"typeof argument must be Object!")));
}
Local<Value> argument = args[0];
std::string int_vars[] = { "max_cpu_time","max_real_time","max_memory","memory_limit_check_only","max_stack","max_process_number","max_output_size","uid","gid"};
std::string str_vars[] = { "input_path","output_path","error_path","exe_path","log_path","seccomp_rule_name"};
for( auto var :int_vars){
if( ! ToNumber(isolate,argument,(char *)var.c_str(),_args))
return;
}
for( auto var : str_vars){
if( !ToCStr(isolate,argument,(char *)var.c_str(),_args))
return;
}
/* args */
Local<Value> margs= argument->ToObject()->Get(context,String::NewFromUtf8(isolate,"args")).ToLocalChecked();
if( margs->IsNullOrUndefined()){
;
}
else if( margs->IsArray()){
Local<Array> args = margs.As<Array>();
int len = args->Length();
int i;
for(i=0;i<len;i++){
Local<Value> in = args->Get(i);
if( !ToCStr(isolate,in,(char *)"args",_args))
return;
}
}
else { //not array
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,"args must be a list")));
return;
}
Local<Value> menv= argument->ToObject()->Get(context,String::NewFromUtf8(isolate,"env")).ToLocalChecked();
if( menv->IsNullOrUndefined()){
;
}
else if( margs->IsArray()){
Local<Array> env = menv.As<Array>();
int len = env->Length();
int i;
for(i=0;i<len;i++){
Local<Value> in = env->Get(i);
if(!ToCStr(isolate,in,(char *)"env",_args))
return;
}
}
else { //not array
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,"env must be a list")));
return;
}
char buf[255];
FILE *_result_f;
std::string result;
if(( _result_f = popen(_args.c_str(),"r"))==NULL)
isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate,"run /usr/lib/judger/libjudger.so failed!")));
while(fgets(buf,sizeof(buf),_result_f)){
result += std::string(buf);
}
pclose(_result_f);
const char *pr = result.c_str();
int len = strlen(pr);
for(len--;len>=0;len--){
if( pr[len] == '{')
break;
}
if(len)
printf("%*.*s",len,len,pr); //输出 程序的输出
MaybeLocal<Value> mres = JSON::Parse(isolate,String::NewFromUtf8(isolate,pr+len));
args.GetReturnValue().Set(mres.ToLocalChecked());
}
void init(Local<Object> exports)
{
v8::Isolate* isolate = v8::Isolate::GetCurrent();
NODE_SET_METHOD(exports, "run", Method);
exports->Set( String::NewFromUtf8(isolate,"UNLIMITED"),Integer::New(isolate, -1));
exports->Set(String::NewFromUtf8(isolate,"VERSION"),Integer::New(isolate, 0x020101));
exports->Set(String::NewFromUtf8(isolate,"RESULT_SUCCESS"),Integer::New(isolate, 0));
exports->Set(String::NewFromUtf8(isolate,"RESULT_WRONG_ANSWER"),Integer::New(isolate, -1));
exports->Set(String::NewFromUtf8(isolate,"RESULT_CPU_TIME_LIMIT_EXCEEDED"),Integer::New(isolate, 1));
exports->Set(String::NewFromUtf8(isolate,"RESULT_REAL_TIME_LIMIT_EXCEEDED"),Integer::New(isolate, 2));
exports->Set(String::NewFromUtf8(isolate,"RESULT_MEMORY_LIMIT_EXCEEDED"),Integer::New(isolate, 3));
exports->Set(String::NewFromUtf8(isolate,"RESULT_RUNTIME_ERROR"),Integer::New(isolate, 4));
exports->Set(String::NewFromUtf8(isolate,"RESULT_SYSTEM_ERROR"),Integer::New(isolate, 5));
exports->Set(String::NewFromUtf8(isolate,"ERROR_INVALID_CONFIG"),Integer::New(isolate, -1));
exports->Set(String::NewFromUtf8(isolate,"ERROR_FORK_FAILED"),Integer::New(isolate, -2));
exports->Set(String::NewFromUtf8(isolate,"ERROR_PTHREAD_FAILED"),Integer::New(isolate, -3));
exports->Set(String::NewFromUtf8(isolate,"ERROR_WAIT_FAILED"),Integer::New(isolate, -4));
exports->Set(String::NewFromUtf8(isolate,"ERROR_ROOT_REQUIRED"),Integer::New(isolate, -5));
exports->Set(String::NewFromUtf8(isolate,"ERROR_LOAD_SECCOMP_FAILED"),Integer::New(isolate, -6));
exports->Set(String::NewFromUtf8(isolate,"ERROR_SETRLIMIT_FAILED"),Integer::New(isolate, -7));
exports->Set(String::NewFromUtf8(isolate,"ERROR_DUP2_FAILED"),Integer::New(isolate, -8));
exports->Set(String::NewFromUtf8(isolate,"ERROR_SETUID_FAILED"),Integer::New(isolate, -9));
exports->Set(String::NewFromUtf8(isolate,"ERROR_EXECVE_FAILED"),Integer::New(isolate, -10));
exports->Set(String::NewFromUtf8(isolate,"ERROR_SPJ_ERROR"),Integer::New(isolate, -11));
}
NODE_MODULE(addon, init)
}

View File

@ -0,0 +1,16 @@
{
"name": "judger",
"version": "1.0.0",
"description": "judger for online system",
"main": "./build/Release/judger.node",
"scripts": {
"build": "node-gyp rebuild",
"test": "sudo ./node_modules/.bin/mocha -t 5000 ../../tests/Nodejs_and_core"
},
"author": "virusdefender,rainboy",
"license": "GPL",
"devDependencies": {
"mocha": "^6.1.4",
"node-gyp": "^4.0.0"
}
}

15
bindings/NodeJS/readme.md Normal file
View File

@ -0,0 +1,15 @@
## 安装
先安装`judger`,然后
```
npm install
npm run build
npm run test
```
## 已知bug
#### bug_1
`freopen("data.out","w",stdout)`,不能产生"data.out",这个应该是judger代码的锅,:cry:

View File

@ -0,0 +1,71 @@
const fs = require("fs")
const path = require("path")
const execSync = require("child_process").execSync
var out_base_path = '/tmp/judge'
var compile_base_path = ""
var compile_c = function(name){
out_path =path.join(out_base_path,'main')
execSync(`gcc -o ${out_path} ${path.join(this.compile_base_path,name)}`)
return out_path
}
var compile_cpp = function(name){
out_path =path.join(out_base_path,'main')
execSync(`g++ -o ${out_path} ${path.join(this.compile_base_path,name)}`)
return out_path
}
var make_input = content =>{
fs.writeFileSync('/tmp/judge/input',content,{'encoding':'utf-8'})
return '/tmp/judge/input'
}
var read_input = read_path => {
return fs.readFileSync(read_path,{'encoding':'utf-8'})
}
var output_path = ()=>{
return '/tmp/judge/output'
}
var error_path = ()=>{
return '/tmp/judge/error'
}
var mkdir = _path => {
execSync(`mkdir -p ${_path}`)
}
var rmdir = _path => {
execSync(`rm -r ${_path}`)
}
var baseconfig = ()=>{
return {
max_cpu_time:1000,
max_real_time:8000,
max_memory:128*1024*1024,
max_stack:128*1024*1024,
max_process_number:8,
max_output_size:128*1024*1024,
exe_path:"/dev/null",
input_path:"/dev/null",
output_path:"/dev/null",
error_path:"/dev/null",
args:[],
env:[],
log_path:"/dev/null",
seccomp_rule_name:null,
gid:0,
uid:0,
info:false,
debug:false,
}
}
module.exports = {
compile_c,compile_cpp,make_input,read_input,mkdir,rmdir,baseconfig,compile_base_path,
output_path,
error_path
}

View File

@ -0,0 +1,245 @@
const assert = require("assert")
const path = require("path")
var judger = require("../../bindings/NodeJS")
var base = require('./base.js')
var signal = require("os").constants.signals
var compile_base_path = base.compile_base_path = path.join(__dirname,"../test_src","integration")
describe('#常用测试',function(){
before( ()=>{
//mkdir /tmp/judge
base.mkdir('/tmp/judge')
})
after( ()=>{
base.rmdir('/tmp/judge')
})
it('args must be a list 1',()=>{
assert.throws(()=>judger.run({args:{},output_path:"/dev/null"}),/args must be a list$/)
})
it('args must be a list 2',()=>{
assert.throws(()=>judger.run({args:"1234",output_path:"/dev/null"}),/args must be a list$/)
})
it('test_env_must_be_list',()=>{
assert.throws(()=>judger.run({env:"1234",output_path:"/dev/null"}),/env must be a list$/)
})
it('test_seccomp_rule_can_be_none',()=>{
let ret = judger.run({ output_path:"/dev/null",exe_path:"/bin/ls",args:["/"],env:["a=b"] })
assert.strictEqual(ret.result,0)
})
it('test normal.c',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("normal.c")
config["input_path"] = base.make_input("judger_test")
config["output_path"] = config["error_path"] = base.output_path()
let ret = judger.run(config)
let output = "judger_test\nHello world"
let content = base.read_input( base.output_path())
assert.strictEqual(content,output)
})
it('test math.c' ,()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("math.c")
config["output_path"] = config["error_path"] = base.output_path()
let ret = judger.run(config)
assert.strictEqual(ret.result,judger.RESULT_SUCCESS)
let content = base.read_input( base.output_path())
assert.strictEqual(content,"abs 1024")
})
it('test_args',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("args.c")
config["args"] = ["test", "hehe", "000"]
config["output_path"] = config["error_path"] = base.output_path()
let result = judger.run(config)
let output = "argv[0]: /tmp/judge/main\nargv[1]: test\nargv[2]: hehe\nargv[3]: 000\n"
assert.strictEqual(result.result, judger.RESULT_SUCCESS)
assert.strictEqual(output, base.read_input(config["output_path"]))
})
it('test env',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("env.c")
config["output_path"] = config["error_path"] = base.output_path()
config["env"] = ["env=judger_test","test=judger"]
let result = judger.run(config)
let output = "judger_test\njudger\n"
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
assert.strictEqual(output, base.read_input(config["output_path"]))
})
it('test real time',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("sleep.c")
config["max_real_time"] = 3
let result = judger.run(config)
//assert.strictEqual(result["result"], judger.RESULT_REAL_TIME_LIMIT_EXCEEDED)
assert.strictEqual(result["signal"], signal.SIGKILL)
assert.ok(result["real_time"] >= config["max_real_time"])
})
it('test cpu time',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("while1.c")
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_CPU_TIME_LIMIT_EXCEEDED)
assert.strictEqual(result["signal"], signal.SIGKILL)
assert.ok(result["cpu_time"] >= config["max_cpu_time"])
})
it('test memory1',()=>{
let config = base.baseconfig()
config["max_memory"] = 64 * 1024 * 1024
config["exe_path"] = base.compile_c("memory1.c")
let result= judger.run(config)
//# malloc succeeded
assert.ok(result["memory"] > 80 * 1024 * 1024)
assert.strictEqual(result["result"], judger.RESULT_MEMORY_LIMIT_EXCEEDED)
})
it('test memory2',()=>{
let config = base.baseconfig()
config["max_memory"] = 64 * 1024 * 1024
config["exe_path"] = base.compile_c("memory2.c")
let result= judger.run(config)
//# malloc failed, return 1
assert.strictEqual(result["exit_code"], 1)
//# malloc failed, so it should use a little memory
assert.ok(result["memory"] < 12 * 1024 * 1024)
assert.strictEqual(result["result"], judger.RESULT_RUNTIME_ERROR)
})
it('test memory3',()=>{
let config = base.baseconfig()
config["max_memory"] = 512 * 1024 * 1024
config["exe_path"] = base.compile_c("memory3.c")
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
assert.ok(result["memory"] >= 102400000 * 4)
})
it('test re1',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("re1.c")
let result= judger.run(config)
//# re1.c return 25
assert.strictEqual(result["exit_code"], 25)
})
it('test re2',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("re2.c")
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_RUNTIME_ERROR)
assert.strictEqual(result["signal"], signal.SIGSEGV)
})
it('test child proc cpu time limit',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("child_proc_cpu_time_limit.c")
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_CPU_TIME_LIMIT_EXCEEDED)
})
it('test child proc real time limit',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("child_proc_real_time_limit.c")
config["max_real_time"] = 3
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_REAL_TIME_LIMIT_EXCEEDED)
assert.strictEqual(result["signal"], signal.SIGKILL)
})
it('test stdout and stderr',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("stdout_stderr.c")
config["output_path"] = config["error_path"] = base.output_path()
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
let output = "stderr\n+++++++++++++++\n--------------\nstdout\n"
assert.strictEqual(output, base.read_input(config["output_path"]))
})
it('test uid and gid',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("uid_gid.c")
config["output_path"] = config["error_path"] = base.output_path()
config["uid"] = 65534
config["gid"] = 65534
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
output = "uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)\nuid 65534\ngid 65534\n"
assert.strictEqual(output, base.read_input(config["output_path"]))
})
it('test gcc random',()=>{
let config = base.baseconfig()
config["max_memory"] = judger.UNLIMITED
config["exe_path"] = "/usr/bin/gcc"
config["args"] = [path.join(compile_base_path,'gcc_random.c'),
"-o", '/tmp/judge/gcc_random']
let result= judger.run(config)
assert.ok(result["real_time"] >= 2000)
})
it('test cpp meta',()=>{
let config = base.baseconfig()
config["exe_path"] = "/usr/bin/g++"
config["max_memory"] = 1024 * 1024 * 1024
config["args"] = [path.join(compile_base_path,'cpp_meta.cpp'),
"-o",'/tmp/judge/cpp_meta']
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_CPU_TIME_LIMIT_EXCEEDED)
assert.ok(result["cpu_time"] > 1500)
assert.ok(result["real_time"] >= 2000)
})
it('test output size',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_c("output_size.c")
config["output_path"] = base.output_path()
config["max_output_size"] = 1000 * 10
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_RUNTIME_ERROR)
assert.ok(base.read_input("/tmp/judge/fsize_test").length <= 1000*10)
})
it('test stack size',()=>{
let config = base.baseconfig()
config["max_memory"] = 256 * 1024 * 1024
config["exe_path"] = base.compile_c("stack.c")
config["max_stack"] = 16 * 1024 * 1024
config["output_path"] = config["error_path"] = base.output_path()
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_RUNTIME_ERROR)
config["max_stack"] = 128 * 1024 * 1024
result= judger.run(config).result
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
assert.strictEqual("big stack", base.read_input(config["output_path"]))
})
it('test writev',()=>{
let config = base.baseconfig()
config["exe_path"] = base.compile_cpp("writev.cpp")
config["seccomp_rule_name"] = "c_cpp"
config["input_path"] = base.make_input("111" * 10000 + "\n")
config["output_path"] = config["error_path"] = base.output_path()
let result= judger.run(config)
assert.strictEqual(result["result"], judger.RESULT_SUCCESS)
})
})