├── .gitignore ├── compile.js ├── compile_process.js ├── config.json ├── daemon.js ├── judger.js ├── languages ├── c.js ├── cpp.js ├── cpp11.js ├── csharp.js ├── haskell.js ├── java.js ├── lua.js ├── luajit.js ├── nodejs.js ├── ocaml.js ├── pascal.js ├── python2.js ├── python3.js ├── ruby.js ├── vala.js └── vbnet.js ├── package.json ├── runner.js ├── spj.js └── testdata └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | let child_process = require('child_process'); 2 | 3 | function getLanguageModel(language) { 4 | try { 5 | let lang = require('./languages/' + language); 6 | lang.name = language; 7 | return lang; 8 | } catch (e) { 9 | return null; 10 | } 11 | } 12 | 13 | async function compile(code, language) { 14 | return new Promise((resolve, reject) => { 15 | let cp = child_process.fork('./compile_process.js'); 16 | cp.send({ 17 | code: code, 18 | lang: language.name, 19 | randomPrefix: randomPrefix 20 | }); 21 | 22 | let returned = false; 23 | cp.on('message', res => { 24 | returned = true; 25 | resolve(res); 26 | }); 27 | 28 | cp.on('error', err => { 29 | returned = true; 30 | reject(err); 31 | }); 32 | 33 | cp.on('close', (code, signal) => { 34 | if (!returned) reject({ code: code, signal: signal }); 35 | }); 36 | }); 37 | } 38 | 39 | module.exports = [ 40 | getLanguageModel, 41 | compile 42 | ]; 43 | -------------------------------------------------------------------------------- /compile_process.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | let fs = require('fs'); 3 | let randomstring = require("randomstring"); 4 | let config = require('./config.json'); 5 | 6 | function getLanguageModel(language) { 7 | try { 8 | let lang = require('./languages/' + language); 9 | lang.name = language; 10 | return lang; 11 | } catch (e) { 12 | return null; 13 | } 14 | } 15 | 16 | async function compile(code, language, randomPrefix) { 17 | let srcFile = path.join(config.tmp_dir, language.getFilename(`tmp_${randomPrefix}_${randomstring.generate()}`)); 18 | await fs.writeFileAsync(srcFile, code); 19 | let result = await language.compile(srcFile); 20 | return result; 21 | } 22 | 23 | process.setgid('nogroup'); 24 | process.setgroups(['nogroup']); 25 | process.setuid('nobody'); 26 | process.env['TMPDIR'] = config.tmp_dir; 27 | process.chdir(config.tmp_dir); 28 | 29 | process.on('message', async msg => { 30 | let lang = getLanguageModel(msg.lang); 31 | let res = await compile(msg.code, lang, msg.randomPrefix); 32 | if (res.output && res.output.length > 10 * 1024) { 33 | res.output = res.output.substr(0, 10 * 1024) + '...'; 34 | } 35 | process.send(res); 36 | process.exit(); 37 | }); 38 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tmp_dir": "/tmp", 3 | "syzoj_url": "http://127.0.0.1:5283", 4 | "judge_token": "233", 5 | "testdata_dir": "testdata", 6 | "delay": 1000, 7 | "output_limit": 104857600, 8 | "spj_time_limit": 1500, 9 | "spj_memory_limit": 256, 10 | "spj_message_limit": 10240, 11 | "threads": 2 12 | } 13 | -------------------------------------------------------------------------------- /daemon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of SYZOJ. 3 | * 4 | * Copyright (c) 2016 Menci 5 | * 6 | * SYZOJ is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * SYZOJ is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with SYZOJ. If not, see . 18 | */ 19 | 20 | let child_process = require('child_process'); 21 | let config = require('./config'); 22 | 23 | let i = parseInt(process.argv[2]) || 1; 24 | if (config.threads > i) { 25 | let ch = child_process.exec(`node --harmony-async-await daemon.js ${i + 1}`); 26 | ch.stdout.pipe(process.stdout); 27 | } 28 | 29 | if (process.getuid() !== 0) { 30 | console.log('Need root privileges.'); 31 | process.exit(); 32 | } 33 | 34 | function start () { 35 | let obj = child_process.exec(`node --harmony-async-await judger.js ${2333 + i}`, start); 36 | obj.stdout.pipe(process.stdout); 37 | } 38 | 39 | start(); 40 | -------------------------------------------------------------------------------- /judger.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let fs = Promise.promisifyAll(require('fs')); 3 | let path = require('path'); 4 | let url = require('url'); 5 | let AdmZip = require('adm-zip'); 6 | let request = require('request-promise'); 7 | let randomstring = require("randomstring"); 8 | let child_process = require('child_process'); 9 | let shellEscape = require('shell-escape'); 10 | let config = require('./config'); 11 | 12 | let [compileSpecialJudge, runSpecialJudge] = require('./spj'); 13 | let [sb, runTestcase, run] = require('./runner'); 14 | let [getLanguageModel, compile] = require('./compile'); 15 | 16 | global.config = config; 17 | global.randomPrefix = randomstring.generate(); 18 | 19 | function execute () { 20 | return child_process.execSync(shellEscape(Array.from(arguments))); 21 | } 22 | 23 | async function getJudgeTask() { 24 | return new Promise(async (resolve, reject) => { 25 | let task; 26 | do { 27 | try { 28 | task = await request({ 29 | uri: url.resolve(config.syzoj_url, '/api/waiting_judge'), 30 | qs: { 31 | 'session_id': config.judge_token 32 | }, 33 | json: true, 34 | jar: true 35 | }); 36 | } catch (e) {} 37 | 38 | await Promise.delay(config.delay); 39 | } while (!task || task.have_task === 0); 40 | 41 | resolve(task); 42 | }); 43 | } 44 | 45 | async function parseTestdata(testdata) { 46 | let dir = path.join(config.testdata_dir, testdata); 47 | let dataRuleText; 48 | let res = []; 49 | let list = await fs.readdirAsync(dir); 50 | try { 51 | dataRuleText = await fs.readFileAsync(path.join(dir, 'data_rule.txt')); 52 | } catch (e) { 53 | // No data_rule.txt 54 | 55 | res[0] = {}; 56 | res[0].cases = []; 57 | for (let file of list) { 58 | let parsedName = path.parse(file); 59 | if (parsedName.ext === '.in') { 60 | if (list.includes(`${parsedName.name}.out`)) { 61 | res[0].cases.push({ 62 | input: path.join(dir, file), 63 | output: path.join(dir, `${parsedName.name}.out`) 64 | }); 65 | } 66 | 67 | if (list.includes(`${parsedName.name}.ans`)) { 68 | res[0].cases.push({ 69 | input: path.join(dir, file), 70 | output: path.join(dir, `${parsedName.name}.ans`) 71 | }); 72 | } 73 | } 74 | } 75 | 76 | res[0].type = 'sum'; 77 | res[0].score = 100; 78 | res[0].cases.sort((a, b) => { 79 | function getLastInteger(s) { 80 | let re = /(\d+)\D*$/; 81 | let x = re.exec(s); 82 | if (x) return parseInt(x[1]); 83 | else return -1; 84 | } 85 | 86 | return getLastInteger(a.input) - getLastInteger(b.input); 87 | }); 88 | 89 | return res; 90 | } 91 | 92 | function parseDataRule(dataRuleText) { 93 | let lines = dataRuleText.split('\r').join('').split('\n').filter(x => x.length !== 0); 94 | 95 | if (lines.length < 3) throw 'Invalid data_rule.txt'; 96 | 97 | let input = lines[lines.length - 2]; 98 | let output = lines[lines.length - 1]; 99 | 100 | for (let s = 0; s < lines.length - 2; ++s) { 101 | res[s] = {}; 102 | res[s].cases = []; 103 | let numbers = lines[s].split(' ').filter(x => x); 104 | if (numbers[0].includes(':')) { 105 | let tokens = numbers[0].split(':'); 106 | res[s].type = tokens[0] || 'sum'; 107 | res[s].score = parseFloat(tokens[1]) || (100 / (lines.length - 2)); 108 | numbers.shift(); 109 | } else { 110 | res[s].type = 'sum'; 111 | res[s].score = 100; 112 | } 113 | for (let i of numbers) { 114 | let testcase = { 115 | input: path.join(dir, input.replace('#', i)), 116 | output: path.join(dir, output.replace('#', i)) 117 | }; 118 | 119 | res[s].cases.push(testcase); 120 | } 121 | } 122 | 123 | return res.filter(x => x.cases && x.cases.length !== 0); 124 | } 125 | 126 | let dataRule = parseDataRule(dataRuleText.toString()); 127 | return dataRule; 128 | } 129 | 130 | async function downloadTestData(testdata) { 131 | let zip = await request({ 132 | uri: url.resolve(config.syzoj_url, '/static/uploads/' + testdata), 133 | encoding: null, 134 | jar: true, 135 | transform: data => { 136 | return new AdmZip(data); 137 | } 138 | }); 139 | 140 | let dir = path.join(config.testdata_dir, testdata); 141 | await fs.mkdirAsync(dir); 142 | zip.extractAllTo(dir); 143 | } 144 | 145 | async function getTestData(testdata) { 146 | if (!testdata) return null; 147 | 148 | try { 149 | async function isdir(path) { 150 | let stat; 151 | try { 152 | stat = await fs.statAsync(path); 153 | return stat.isDirectory(); 154 | } catch (e) { 155 | return false; 156 | } 157 | } 158 | 159 | let dir = path.join(config.testdata_dir, testdata); 160 | if (!await isdir(dir)) { 161 | await downloadTestData(testdata); 162 | } 163 | return parseTestdata(testdata); 164 | } catch (e) { 165 | return null; 166 | } 167 | } 168 | 169 | function diff(filename1, filename2) { 170 | try { 171 | execute('diff', '-Bb', filename1, filename2); 172 | return true; 173 | } catch (e) { 174 | return false; 175 | } 176 | } 177 | 178 | function shorterRead(fileName, maxLen) { 179 | let fd = fs.openSync(fileName, 'r'); 180 | let len = fs.fstatSync(fd).size; 181 | if (len > maxLen) { 182 | let buf = Buffer.allocUnsafe(maxLen); 183 | fs.readSync(fd, buf, 0, maxLen, 0); 184 | let res = buf.toString() + '...'; 185 | fs.closeSync(fd); 186 | return res; 187 | } else { 188 | fs.closeSync(fd); 189 | return fs.readFileSync(fileName).toString(); 190 | } 191 | } 192 | 193 | function shorterReadString(buffer, maxLen) { 194 | let s = buffer.toString(); 195 | if (s.length > maxLen) return s.substr(0, maxLen) + '...'; 196 | else return s; 197 | } 198 | 199 | async function judgeTestcase(task, language, execFile, extraFiles, testcase) { 200 | let runResult = await runTestcase(task, language, execFile, extraFiles, testcase); 201 | 202 | let result = { 203 | status: '', 204 | time_used: parseInt(runResult.result.time_usage / 1000), 205 | memory_used: runResult.result.memory_usage, 206 | input: shorterRead(testcase.input, 120), 207 | user_out: '', 208 | answer: shorterRead(testcase.output, 120), 209 | score: 0 210 | }; 211 | 212 | let outputFile = runResult.getOutputFile(); 213 | if (outputFile) { 214 | result.user_out = shorterRead(outputFile, 120); 215 | } 216 | 217 | let stderrFile = runResult.getStderrFile(); 218 | if (stderrFile) { 219 | result.user_err = shorterRead(stderrFile, 1024); 220 | } 221 | 222 | if (result.time_used > task.time_limit) { 223 | result.status = 'Time Limit Exceeded'; 224 | } else if (result.memory_used > task.memory_limit * 1024) { 225 | result.status = 'Memory Limit Exceeded'; 226 | } else if (runResult.result.status !== 'Exited Normally') { 227 | result.status = runResult.result.status; 228 | } else if (!outputFile) { 229 | result.status = 'File Error'; 230 | } else { 231 | // AC or WA 232 | let spjResult = await runSpecialJudge(task, path.join(config.testdata_dir, task.testdata), testcase.input, outputFile, testcase.output); 233 | if (spjResult === null) { 234 | // No Special Judge 235 | if (diff(testcase.output, outputFile)) { 236 | result.status = 'Accepted'; 237 | result.score = 100; 238 | } else { 239 | result.status = 'Wrong Answer'; 240 | } 241 | } else { 242 | result.score = spjResult.score; 243 | if (!spjResult.success) result.status = 'Judgement Failed'; 244 | else if (spjResult.score === 100) result.status = 'Accepted'; 245 | else if (spjResult.score === 0) result.status = 'Wrong Answer'; 246 | else result.status = 'Partially Correct'; 247 | result.spj_message = shorterReadString(spjResult.message, config.spj_message_limit); 248 | } 249 | } 250 | 251 | return result; 252 | } 253 | 254 | async function judge(task, callback) { 255 | let result = { 256 | status: '', 257 | score: 0, 258 | total_time: 0, 259 | max_memory: 0, 260 | case_num: 0, 261 | compiler_output: '' 262 | }; 263 | 264 | result.status = 'Compiling'; 265 | result.pending = true; 266 | await callback(result); 267 | 268 | // Compile the source code 269 | let language = getLanguageModel(task.language); 270 | let compileResult = await compile(task.code, language); 271 | result.compiler_output = compileResult.output; 272 | 273 | if (!compileResult.success) { 274 | result.status = 'Compile Error'; 275 | result.pending = false; 276 | return await callback(result); 277 | } 278 | 279 | let dataRule = await getTestData(task.testdata); 280 | if (!dataRule) { 281 | result.status = 'No Testdata'; 282 | result.pending = false; 283 | return await callback(result); 284 | } 285 | 286 | let spjCompileResult = await compileSpecialJudge(path.join(config.testdata_dir, task.testdata)); 287 | if (spjCompileResult && !spjCompileResult.success) { 288 | result.status = 'Judgement Failed'; 289 | result.spj_compiler_output = spjCompileResult.output; 290 | result.pending = false; 291 | return await callback(result); 292 | } 293 | 294 | result.subtasks = []; 295 | for (let s = 0; s < dataRule.length; ++s) { 296 | result.subtasks[s] = { 297 | case_num: dataRule[s].cases.length, 298 | status: 'Waiting', 299 | pending: true 300 | }; 301 | } 302 | 303 | let overallFinalStatus = null, overallScore = 0; 304 | result.score = 0; 305 | for (let s = 0; s < dataRule.length; ++s) { 306 | let subtask = dataRule[s]; 307 | let subtaskResult = result.subtasks[s]; 308 | let subtaskFinalStatus = null, subtaskScore = null; 309 | let caseNum = 0; 310 | for (let testcase of subtask.cases) { 311 | subtaskResult.status = 'Running on #' + (caseNum + 1); 312 | if (dataRule.length === 1) { 313 | result.status = 'Running on #' + (caseNum + 1); 314 | } else { 315 | result.status = 'Running on #' + (s + 1) + '.' + (caseNum + 1); 316 | } 317 | subtaskResult.pending = true; 318 | await callback(result); 319 | 320 | overallScore -= subtaskScore; 321 | result.score = Math.min(100, Math.ceil(overallScore)); 322 | let caseResult = await judgeTestcase(task, language, compileResult.execFile, compileResult.extraFiles, testcase); 323 | 324 | switch (subtask.type) { 325 | case 'min': 326 | caseResult.score = caseResult.score * (subtask.score / 100); 327 | subtaskScore = Math.min((subtaskScore == null) ? subtask.score : subtaskScore, caseResult.score); 328 | break; 329 | case 'mul': 330 | subtaskScore = ((subtaskScore == null) ? subtask.score : subtaskScore) * (caseResult.score / 100); 331 | caseResult.score = caseResult.score * (subtask.score / 100); 332 | break; 333 | case 'sum': default: 334 | subtask.type = 'sum'; 335 | caseResult.score = caseResult.score / subtask.cases.length * (subtask.score / 100); 336 | subtaskScore = (subtaskScore || 0) + caseResult.score; 337 | break; 338 | } 339 | 340 | overallScore += subtaskScore; 341 | result.score = Math.min(100, Math.ceil(overallScore)); 342 | result.max_memory = Math.max(result.max_memory, caseResult.memory_used); 343 | result.total_time += caseResult.time_used; 344 | subtaskResult[caseNum++] = caseResult; 345 | 346 | if (!subtaskFinalStatus && caseResult.status !== 'Accepted') { 347 | subtaskFinalStatus = caseResult.status; 348 | } 349 | } 350 | subtaskResult.score = subtaskScore; 351 | if (subtaskFinalStatus) subtaskResult.status = subtaskFinalStatus; 352 | else subtaskResult.status = 'Accepted'; 353 | subtaskResult.pending = false; 354 | 355 | if (!overallFinalStatus && subtaskResult.status !== 'Accepted') { 356 | overallFinalStatus = subtaskResult.status; 357 | } 358 | } 359 | 360 | if (overallFinalStatus) result.status = overallFinalStatus; 361 | else result.status = 'Accepted'; 362 | result.pending = false; 363 | 364 | await callback(result); 365 | } 366 | 367 | async function uploadJudgeResult(task, result) { 368 | return await request({ 369 | uri: url.resolve(config.syzoj_url, '/api/update_judge/' + task.judge_id), 370 | method: 'POST', 371 | body: { 372 | result: JSON.stringify(result) 373 | }, 374 | qs: { 375 | session_id: config.judge_token 376 | }, 377 | json: true, 378 | jar: true 379 | }); 380 | } 381 | 382 | async function main() { 383 | let task = await getJudgeTask(); 384 | console.log(task); 385 | try { 386 | await judge(task, async result => { 387 | let uploadResult = await uploadJudgeResult(task, result); 388 | }); 389 | } catch (e) { 390 | await uploadJudgeResult(task, { 391 | status: "System Error", 392 | score: 0, 393 | total_time: 0, 394 | max_memory: 0, 395 | case_num: 0, 396 | pending: false 397 | }); 398 | console.log(e); 399 | } 400 | 401 | child_process.execSync('rm -rf ' + path.join(config.tmp_dir, `tmp_${randomPrefix}_*`)); 402 | sb.destroy(); 403 | process.exit() 404 | } 405 | 406 | main(); 407 | -------------------------------------------------------------------------------- /languages/c.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.c'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | try { 33 | output = await child_process.execAsync(`gcc ${file} -o ${execFile} -O2 -lm -DONLINE_JUDGE -fdiagnostics-color=always 2>&1 || true`, { 34 | timeout: 5000 35 | }); 36 | } catch (e) { 37 | output = 'Time limit exceeded while compiling'; 38 | } 39 | 40 | return { 41 | success: await isFile(execFile), 42 | execFile: execFile, 43 | output: output 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /languages/cpp.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.cpp'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`g++ ${file} -o ${execFile} -O2 -lm -DONLINE_JUDGE -fdiagnostics-color=always 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | } catch (e) { 38 | output = 'Time limit exceeded while compiling'; 39 | } 40 | 41 | return { 42 | success: await isFile(execFile), 43 | execFile: execFile, 44 | output: output 45 | }; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /languages/cpp11.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.cpp'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`g++ ${file} -o ${execFile} -O2 -lm -DONLINE_JUDGE -std=c++11 -fdiagnostics-color=always 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | } catch (e) { 38 | output = 'Time limit exceeded while compiling'; 39 | } 40 | 41 | return { 42 | success: await isFile(execFile), 43 | execFile: execFile, 44 | output: output 45 | }; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /languages/csharp.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 10240, 17 | minProcessLimit: 2, 18 | minMemory: 256 * 1024, 19 | largeStack: false, 20 | getFilename(file) { 21 | return file + '.cs'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`mcs ${file} -define:ONLINE_JUDGE 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | output += await child_process.execAsync(`mkbundle ${execFile}.exe -o ${execFile} 2>&1 || true`, { 38 | timeout: 5000 39 | }); 40 | } catch (e) { 41 | output = 'Time limit exceeded while compiling'; 42 | } 43 | 44 | return { 45 | success: await isFile(execFile), 46 | execFile: execFile, 47 | output: output 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /languages/haskell.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: false, 20 | getFilename(file) { 21 | return file + '.hs'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`ghc ${file} -o ${execFile} -O2 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | } catch (e) { 38 | output = 'Time limit exceeded while compiling'; 39 | } 40 | 41 | return { 42 | success: await isFile(execFile), 43 | execFile: execFile, 44 | output: output 45 | }; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /languages/java.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 2, 18 | minMemory: 0, 19 | largeStack: false, 20 | getFilename(file) { 21 | return file + '.java'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`gcj ${file} 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | 38 | let className = null, re = /^\S[\S\s]*error: The public type ([a-zA-Z_$0-9]+) must be defined in its own file$/; 39 | for (let line of output.split('\n')) { 40 | let res = re.exec(line); 41 | if (res) { 42 | className = res[1]; 43 | break; 44 | } 45 | } 46 | 47 | if (!className) { 48 | output = 'Failed to detect the main class name, here is the compiler output:\n\n' + output; 49 | } else { 50 | let dir = `${execFile}_dir`; 51 | await fs.mkdirAsync(dir); 52 | let newFile = `${dir}/${className}.java` 53 | await fs.renameAsync(file, newFile); 54 | 55 | let newFileEscaped = newFile.split('$').join('\\$'); 56 | let classNameEscaped = className.split('$').join('\\$'); 57 | output = await child_process.execAsync(`gcj ${newFileEscaped} -o ${execFile} --main=${classNameEscaped} -O2 2>&1 || true`, { 58 | timeout: 5000 59 | }); 60 | } 61 | } catch (e) { 62 | output = 'Time limit exceeded while compiling'; 63 | } 64 | 65 | return { 66 | success: await isFile(execFile), 67 | execFile: execFile, 68 | output: output 69 | }; 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /languages/lua.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 4); // ".exe"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".exe", 5);\n\n\tconst char *LUA_PATH = "/usr/bin/lua5.2";\n\texecl(LUA_PATH, LUA_PATH, buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 2, 20 | minMemory: 0, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.lua'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(luac -o ${execFile}.exe ${file} 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(execFile + '.exe'), 58 | filename: file 59 | }] 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /languages/luajit.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 4); // ".exe"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".exe", 5);\n\n\tconst char *LUA_PATH = "/usr/bin/luajit";\n\texecl(LUA_PATH, LUA_PATH, "-O3", buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 2, 20 | minMemory: 0, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.lua'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(luajit -b ${file} ${execFile}.exe 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(execFile + '.exe'), 58 | filename: file 59 | }] 60 | }; 61 | } 62 | }; -------------------------------------------------------------------------------- /languages/nodejs.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 3); // ".js"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".js", 4);\n\n\tconst char *NODE_PATH = "/usr/bin/nodejs";\n\texecl(NODE_PATH, NODE_PATH, buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 15, 20 | minMemory: 768 * 1024, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.js'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(nodejs -c ${file} 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(file), 58 | filename: file 59 | }] 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /languages/ocaml.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.ml'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, '_build', parsed.name) + '.native'; 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | let dotNativeFile = parsed.name + '.native'; 34 | try { 35 | output = await child_process.execAsync(`ocamlbuild ${dotNativeFile} 2>&1 || true`, { 36 | timeout: 5000 37 | }); 38 | } catch (e) { 39 | output = 'Time limit exceeded while compiling'; 40 | } 41 | 42 | return { 43 | success: await isFile(execFile), 44 | execFile: execFile, 45 | output: output 46 | }; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /languages/pascal.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.pas'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | try { 33 | output = await child_process.execAsync(`fpc ${file} -O2 2>&1 || true`, { 34 | timeout: 5000 35 | }); 36 | } catch (e) { 37 | output = 'Time limit exceeded while compiling'; 38 | } 39 | 40 | return { 41 | success: await isFile(execFile), 42 | execFile: execFile, 43 | output: output 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /languages/python2.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 4); // ".pyo"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".pyo", 5);\n\n\tconst char *PYTHON_PATH = "/usr/bin/python2";\n\texecl(PYTHON_PATH, PYTHON_PATH, buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 2, 20 | minMemory: 0, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.py'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(python2 -O -m py_compile ${file} 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(execFile + '.pyo'), 58 | filename: execFile + '.pyo' 59 | }] 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /languages/python3.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 3); // ".py"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".py", 4);\n\n\tconst char *PYTHON_PATH = "/usr/bin/python3";\n\texecl(PYTHON_PATH, PYTHON_PATH, buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 2, 20 | minMemory: 0, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.py'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(python3 -m py_compile ${file} 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(file), 58 | filename: file 59 | }] 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /languages/ruby.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | const BUNDLE_SOURCE = '#include \n#include \n#include \n\nint main(int argc, char **argv)\n{\n\tsize_t len = strlen(argv[0]);\n\tchar *buf = (char *)malloc(len + 3); // ".rb"\n\tmemcpy(buf, argv[0], len);\n\tmemcpy(buf + len, ".rb", 4);\n\n\tconst char *RUBY_PATH = "/usr/bin/ruby";\n\texecl(RUBY_PATH, RUBY_PATH, buf, NULL);\n}\n'; 7 | 8 | async function isFile(file) { 9 | try { 10 | let stat = await fs.statAsync(file); 11 | return stat.isFile(); 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | minOutputLimit: 10240, 19 | minProcessLimit: 2, 20 | minMemory: 768 * 1024, 21 | largeStack: false, 22 | getFilename(file) { 23 | return file + '.rb'; 24 | }, 25 | async compile(file) { 26 | let parsed = path.parse(file) 27 | let execFile = path.join(parsed.dir, parsed.name); 28 | 29 | if (await isFile(execFile)) { 30 | await fs.unlinkAsync(execFile); 31 | } 32 | 33 | let output, success = false; 34 | 35 | try { 36 | output = await child_process.execAsync(`(ruby -c ${file} 2>&1 && echo -n Y) || echo -n N`, { 37 | timeout: 5000 38 | }); 39 | 40 | let ch = output[output.length - 1]; 41 | output = output.substr(0, output.length - 1); 42 | 43 | if (ch === 'Y') { 44 | success = true; 45 | await fs.writeFileAsync(execFile + '.c', BUNDLE_SOURCE); 46 | await child_process.execAsync(`gcc ${execFile}.c -o ${execFile} -static`); 47 | } 48 | } catch (e) { 49 | output = 'Time limit exceeded while compiling'; 50 | } 51 | 52 | return { 53 | success: success, 54 | execFile: execFile, 55 | output: output, 56 | extraFiles: !success ? null : [{ 57 | targetFilename: path.basename(file), 58 | filename: file 59 | }] 60 | }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /languages/vala.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 1024, 17 | minProcessLimit: 1, 18 | minMemory: 0, 19 | largeStack: true, 20 | getFilename(file) { 21 | return file + '.vala'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | try { 33 | output = await child_process.execAsync(`bash -c 'faketty () { script -qfc "$(printf "%q " "$@")"; }; faketty valac ${file} -o ${execFile} -D ONLINE_JUDGE -X -O2 || true'`, { 34 | timeout: 5000 35 | }); 36 | } catch (e) { 37 | output = 'Time limit exceeded while compiling'; 38 | } 39 | 40 | return { 41 | success: await isFile(execFile), 42 | execFile: execFile, 43 | output: output 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /languages/vbnet.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let path = require('path'); 3 | let child_process = Promise.promisifyAll(require('child_process')); 4 | let fs = Promise.promisifyAll(require('fs')); 5 | 6 | async function isFile(file) { 7 | try { 8 | let stat = await fs.statAsync(file); 9 | return stat.isFile(); 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | module.exports = { 16 | minOutputLimit: 10240, 17 | minProcessLimit: 2, 18 | minMemory: 0, 19 | largeStack: false, 20 | getFilename(file) { 21 | return file + '.vb'; 22 | }, 23 | async compile(file) { 24 | let parsed = path.parse(file) 25 | let execFile = path.join(parsed.dir, parsed.name); 26 | 27 | if (await isFile(execFile)) { 28 | await fs.unlinkAsync(execFile); 29 | } 30 | 31 | let output; 32 | 33 | try { 34 | output = await child_process.execAsync(`vbnc ${file} -define:ONLINE_JUDGE 2>&1 || true`, { 35 | timeout: 5000 36 | }); 37 | output += await child_process.execAsync(`mkbundle ${execFile}.exe -o ${execFile} 2>&1 || true`, { 38 | timeout: 5000 39 | }); 40 | } catch (e) { 41 | output = 'Time limit exceeded while compiling'; 42 | } 43 | 44 | return { 45 | success: await isFile(execFile), 46 | execFile: execFile, 47 | output: output 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "syzoj-judge", 3 | "version": "0.0.0", 4 | "description": "Sandboxed judger for SYZOJ", 5 | "main": "daemon.js", 6 | "scripts": { 7 | "start": "node --harmony-async-await daemon.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "url": "https://github.com/syzoj/syzoj-judge", 12 | "type": "git" 13 | }, 14 | "author": "Menci", 15 | "license": "GPL-3.0", 16 | "dependencies": { 17 | "adm-zip": "^0.4.7", 18 | "chroot-sandbox": "^0.0.4", 19 | "randomstring": "^1.1.5", 20 | "request": "^2.79.0", 21 | "request-promise": "^4.1.1", 22 | "shell-escape": "^0.2.0", 23 | "tmp": "0.0.31" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /runner.js: -------------------------------------------------------------------------------- 1 | let randomstring = require("randomstring"); 2 | let Sandbox = require('chroot-sandbox'); 3 | let sb = new Sandbox(parseInt(process.argv[2]) || 2333, [ 4 | ['/usr/bin', '/usr/bin', true], 5 | ['/usr/share', '/usr/share', true], 6 | ['/usr/lib', '/usr/lib', true], 7 | ['/lib', '/lib', true], 8 | ['/lib64', '/lib64', true], 9 | ['/etc/alternatives/', '/etc/alternatives/', true], 10 | ['/dev', '/dev', true], 11 | ['/proc', '/proc', true], 12 | ]); 13 | 14 | function runTestcase(task, language, execFile, extraFiles, testcase) { 15 | sb.reset(); 16 | 17 | if (!task.file_io_input_name) task.file_io_input_name = 'data.in' 18 | if (!task.file_io_output_name) task.file_io_output_name = 'data.out' 19 | 20 | if (extraFiles) { 21 | for (let file of extraFiles) { 22 | if (typeof file === 'string') sb.put(file); 23 | else sb.put(file.filename, file.mask, file.targetFilename); 24 | } 25 | } 26 | 27 | sb.put(testcase.input, 777, task.file_io_input_name); 28 | 29 | let program = sb.put(execFile); 30 | 31 | let stderr = randomstring.generate(10); 32 | let runOptions = { 33 | program: program, 34 | file_stdin: '', 35 | file_stdout: '', 36 | file_stderr: stderr, 37 | time_limit: Math.ceil(task.time_limit / 1000), 38 | time_limit_reserve: 1, 39 | memory_limit: task.memory_limit * 1024, 40 | memory_limit_reserve: language.minMemory + 32 * 1024, 41 | large_stack: language.largeStack, 42 | output_limit: Math.max(config.output_limit, language.minOutputLimit), 43 | process_limit: language.minProcessLimit, 44 | network: false 45 | }; 46 | 47 | if (!task.file_io) { 48 | runOptions.file_stdin = task.file_io_input_name; 49 | runOptions.file_stdout = task.file_io_output_name; 50 | } 51 | 52 | let result = sb.run(runOptions); 53 | 54 | return { 55 | result: result, 56 | getOutputFile: () => { 57 | return sb.get(task.file_io_output_name); 58 | }, 59 | getStderrFile: () => { 60 | return sb.get(stderr); 61 | } 62 | }; 63 | } 64 | 65 | function runForSpecialJudge (execFile, extraFiles, language) { 66 | sb.reset(); 67 | 68 | // console.log(arguments); 69 | let program = sb.put(execFile); 70 | 71 | if (extraFiles) { 72 | for (let file of extraFiles) { 73 | if (typeof file === 'string') sb.put(file); 74 | else { 75 | if (typeof file.data !== 'undefined') { 76 | sb.put(Buffer.from(file.data), file.mask, file.targetFilename); 77 | } else { 78 | sb.put(file.filename, file.mask, file.targetFilename); 79 | } 80 | } 81 | } 82 | } 83 | 84 | let runOptions = { 85 | program: program, 86 | file_stdout: 'stdout', 87 | file_stderr: 'stderr', 88 | time_limit: Math.ceil(config.spj_time_limit / 1000), 89 | time_limit_reserve: 1, 90 | memory_limit: config.spj_time_limit * 1024, 91 | memory_limit_reserve: language.minMemory + 32 * 1024, 92 | large_stack: language.largeStack, 93 | output_limit: Math.max(config.spj_message_limit * 2, language.minOutputLimit), 94 | process_limit: language.minProcessLimit, 95 | network: false 96 | }; 97 | 98 | return sb.run(runOptions); 99 | } 100 | 101 | module.exports = [ 102 | sb, 103 | runTestcase, 104 | runForSpecialJudge 105 | ]; 106 | -------------------------------------------------------------------------------- /spj.js: -------------------------------------------------------------------------------- 1 | let Promise = require('bluebird'); 2 | let fs = Promise.promisifyAll(require('fs')); 3 | let path = require('path'); 4 | let tmp = require('tmp'); 5 | let shellEscape = require('shell-escape'); 6 | let child_process = require('child_process'); 7 | let [sb, runTestcase, runForSpecialJudge] = require('./runner'); 8 | let [getLanguageModel, compile] = require('./compile'); 9 | 10 | function isFile(file) { 11 | try { 12 | let stat = fs.statSync(file); 13 | return stat.isFile(); 14 | } catch (e) { 15 | return false; 16 | } 17 | } 18 | 19 | let spjCompileResult = null, spjLang = null; 20 | function runNewSpecialJudge (task, dir, input, user_out, answer) { 21 | let extraFiles = JSON.parse(JSON.stringify(spjCompileResult.extraFiles || [])) || null; 22 | 23 | extraFiles.push({ 24 | filename: input, 25 | targetFilename: 'input' 26 | }); 27 | 28 | let tmpOutput = tmp.fileSync(); 29 | child_process.execSync(shellEscape(['cp', '-r', user_out, tmpOutput.name])); 30 | extraFiles.push({ 31 | filename: tmpOutput.name, 32 | targetFilename: 'user_out' 33 | }); 34 | 35 | extraFiles.push({ 36 | filename: answer, 37 | targetFilename: 'answer' 38 | }); 39 | 40 | extraFiles.push({ 41 | data: task.code, 42 | targetFilename: 'code' 43 | }); 44 | 45 | let result = runForSpecialJudge(spjCompileResult.execFile, extraFiles, spjLang); 46 | 47 | tmpOutput.removeCallback(); 48 | 49 | function readOutput (file) { 50 | let fileName = sb.get(file); 51 | if (!fileName) return ''; 52 | return fs.readFileSync(fileName).toString().trim(); 53 | } 54 | 55 | let stderr = readOutput('stderr'); 56 | if (result.status !== 'Exited Normally') { 57 | return { 58 | success: false, 59 | score: 0, 60 | message: 'Special Judge Error: ' + result.status + (stderr ? ('\n\n' + stderr) : '') 61 | }; 62 | } else { 63 | let scoreText = readOutput('stdout'); 64 | let score = parseFloat(scoreText); 65 | if (score > 100 || score < 0 || !isFinite(score)) { 66 | return { 67 | success: false, 68 | score: 0, 69 | message: `Special Judge returned result contains an illegal score "${scoreText}"` + (stderr ? ('\n\n' + stderr) : '') 70 | }; 71 | } 72 | return { 73 | success: true, 74 | score: score, 75 | message: stderr 76 | } 77 | } 78 | } 79 | 80 | async function runSpecialJudge (task, dir, input, user_out, answer) { 81 | if (spjCompileResult) { 82 | return runNewSpecialJudge(task, dir, input, user_out, answer); 83 | } 84 | return null; 85 | } 86 | 87 | let LEGACY_SPECIAL_JUDGE_WRAPPER = ` 88 | function exit(obj) { 89 | process.stdout.write(String(obj.score)); 90 | if (obj.message) process.stderr.write(String(obj.message)); 91 | process.exit(); 92 | } 93 | 94 | let fs = require('fs'); 95 | let input = fs.readFileSync('input').toString(); 96 | let user_out = fs.readFileSync('user_out').toString(); 97 | let answer = fs.readFileSync('answer').toString(); 98 | let code = fs.readFileSync('code').toString(); 99 | exports.main(); 100 | ` 101 | async function compileLegacySpecialJudge (code) { 102 | return await compile(code + '\n' + LEGACY_SPECIAL_JUDGE_WRAPPER, spjLang = getLanguageModel('nodejs')); 103 | } 104 | 105 | async function compileSpecialJudge (dir) { 106 | let files = fs.readdirSync(dir); 107 | for (let file of files) { 108 | let tmp = /^spj_([\S\s]+?)\.(?:[\S\s]+?)$/.exec(file); 109 | if (!tmp) continue; 110 | spjLang = getLanguageModel(tmp[1]); 111 | if (!spjLang) continue; 112 | 113 | return spjCompileResult = await compile(fs.readFileSync(path.join(dir, file)).toString(), spjLang); 114 | } 115 | 116 | if (files.includes('spj.js')) { 117 | return spjCompileResult = await compileLegacySpecialJudge(fs.readFileSync(path.join(dir, 'spj.js')).toString()); 118 | } 119 | 120 | return null; 121 | } 122 | 123 | module.exports = [ 124 | compileSpecialJudge, 125 | runSpecialJudge 126 | ]; 127 | -------------------------------------------------------------------------------- /testdata/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzoj/syzoj-judge-legacy-1/0dadd25f1a3d13c7a462108401f62f387b01c402/testdata/.gitkeep --------------------------------------------------------------------------------