diff --git a/.gitignore b/.gitignore index 8358b00..e1c1505 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ build/ __pycache__ *.pyc cmake-build-debug/ +.ycm_extra_conf.py +node_modules/ diff --git a/bindings/NodeJS/binding.gyp b/bindings/NodeJS/binding.gyp new file mode 100644 index 0000000..63d2da1 --- /dev/null +++ b/bindings/NodeJS/binding.gyp @@ -0,0 +1,9 @@ +{ + "targets": [ + { + "target_name": "judger", + 'cflags_cc!': [ '-std=c++11','-O2' ], + "sources": [ "judger.cc"], + } + ] +} diff --git a/bindings/NodeJS/judger.cc b/bindings/NodeJS/judger.cc new file mode 100644 index 0000000..a386e1b --- /dev/null +++ b/bindings/NodeJS/judger.cc @@ -0,0 +1,234 @@ +#include "node.h" +#include +#include +#include +#include +#include +#include + + +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 &args,char * key,std::string &str){ + Local context= isolate->GetCurrentContext(); + Local val = args->ToObject()->Get(context,String::NewFromUtf8(isolate,key)).ToLocalChecked(); + if(val->IsNullOrUndefined()) + return true; + else if( val->IsNumber()){ + Local 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 &val,Local &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 &args,char * key,std::string &str){ + + char str_content[255]={0}; + Local context= isolate->GetCurrentContext(); + Local 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& args) + { + Isolate* isolate = args.GetIsolate(); + Local 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 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 margs= argument->ToObject()->Get(context,String::NewFromUtf8(isolate,"args")).ToLocalChecked(); + if( margs->IsNullOrUndefined()){ + ; + } + else if( margs->IsArray()){ + Local args = margs.As(); + int len = args->Length(); + int i; + for(i=0;i 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 menv= argument->ToObject()->Get(context,String::NewFromUtf8(isolate,"env")).ToLocalChecked(); + if( menv->IsNullOrUndefined()){ + ; + } + else if( margs->IsArray()){ + Local env = menv.As(); + int len = env->Length(); + int i; + for(i=0;i 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 mres = JSON::Parse(isolate,String::NewFromUtf8(isolate,pr+len)); + args.GetReturnValue().Set(mres.ToLocalChecked()); + } + + void init(Local 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) + +} diff --git a/bindings/NodeJS/package.json b/bindings/NodeJS/package.json new file mode 100644 index 0000000..65bb06f --- /dev/null +++ b/bindings/NodeJS/package.json @@ -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" + } +} diff --git a/bindings/NodeJS/readme.md b/bindings/NodeJS/readme.md new file mode 100644 index 0000000..43a80ad --- /dev/null +++ b/bindings/NodeJS/readme.md @@ -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: diff --git a/tests/Nodejs_and_core/base.js b/tests/Nodejs_and_core/base.js new file mode 100644 index 0000000..e8a94f0 --- /dev/null +++ b/tests/Nodejs_and_core/base.js @@ -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 +} diff --git a/tests/Nodejs_and_core/test_integration.js b/tests/Nodejs_and_core/test_integration.js new file mode 100644 index 0000000..2a78dff --- /dev/null +++ b/tests/Nodejs_and_core/test_integration.js @@ -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) + }) +})