You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrojudge/src/sandbox.ts

129 lines
4.4 KiB
TypeScript

import Axios from 'axios';
import fs from 'fs-extra';
import { argv } from 'yargs';
import * as STATUS from './status';
import { FormatError, SystemError } from './error';
import { cmd, parseMemoryMB } from './utils';
import { getConfig } from './config';
import { Logger } from './log';
const logger = new Logger('sandbox');
const env = ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'HOME=/w'];
const axios = Axios.create({ baseURL: getConfig('sandbox_host') });
let callId = 0;
const statusMap = {
'Time Limit Exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
'Memory Limit Exceeded': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
'Output Limit Exceeded': STATUS.STATUS_RUNTIME_ERROR,
Accepted: STATUS.STATUS_ACCEPTED,
'Nonzero Exit Status': STATUS.STATUS_RUNTIME_ERROR,
'Internal Error': STATUS.STATUS_SYSTEM_ERROR,
'File Error': STATUS.STATUS_SYSTEM_ERROR,
Signalled: STATUS.STATUS_RUNTIME_ERROR,
};
function proc({
execute = '',
time = 16000,
memory = 1024,
process_limit = 32,
stdin = '', copyIn = {}, copyOut = [], copyOutCached = [],
} = {}) {
const size = parseMemoryMB(getConfig('stdio_size'));
const memoryMax = parseMemoryMB(getConfig('memoryMax'));
if (memory > memoryMax) throw new FormatError('Memory limit larger than memory_max');
return {
args: cmd(execute.replace(/\$\{dir\}/g, '/w')),
env,
files: [
stdin ? { src: stdin } : { content: '' },
{ name: 'stdout', max: 1024 * 1024 * size },
{ name: 'stderr', max: 1024 * 1024 * size },
],
cpuLimit: time * 1000 * 1000,
realCpuLimit: time * 3000 * 1000,
memoryLimit: memory * 1024 * 1024,
procLimit: process_limit,
copyIn,
copyOut,
copyOutCached,
};
}
async function adaptResult(result, params) {
// FIXME: Signalled?
const ret: any = {
status: statusMap[result.status] || STATUS.STATUS_ACCEPTED,
time_usage_ms: result.time / 1000000,
memory_usage_kb: result.memory / 1024,
files: result.files,
code: result.exitStatus,
};
if (ret.time_usage_ms >= (params.time || 16000)) {
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
}
ret.files = result.files || {};
ret.fileIds = result.fileIds || {};
if (params.stdout) await fs.writeFile(params.stdout, ret.files.stdout || '');
else ret.stdout = ret.files.stdout || '';
if (params.stderr) await fs.writeFile(params.stderr, ret.files.stderr || '');
else ret.stderr = ret.files.stderr || '';
if (result.error) ret.error = result.error;
return ret;
}
export async function runMultiple(execute) {
let res;
try {
const body = {
cmd: [
proc(execute[0]),
proc(execute[1]),
],
pipeMapping: [{
in: { index: 0, fd: 1 },
out: { index: 1, fd: 0 },
}, {
in: { index: 1, fd: 1 },
out: { index: 0, fd: 0 },
}],
};
body.cmd[0].files[0] = null;
body.cmd[0].files[1] = null;
body.cmd[1].files[0] = null;
body.cmd[1].files[1] = null;
const id = callId++;
if (argv['show-sandbox-call']) logger.debug('%d %s', id, JSON.stringify(body));
res = await axios.post('/run', body);
if (argv['show-sandbox-call']) logger.debug('%d %s', id, JSON.stringify(res.data));
} catch (e) {
throw new SystemError('Sandbox Error');
}
return await Promise.all(res.data.map((i) => adaptResult(i, {})));
}
export async function del(fileId) {
const res = await axios.delete(`/file/${fileId}`);
return res.data;
}
export async function run(execute, params?) {
let result;
global.reqCount++;
// eslint-disable-next-line no-return-await
if (typeof execute === 'object') return await runMultiple(execute);
try {
const body = { cmd: [proc({ execute, ...params })] };
const id = callId++;
if (argv['show-sandbox-call']) logger.debug('%d %s', id, JSON.stringify(body));
const res = await axios.post('/run', body);
if (argv['show-sandbox-call']) logger.debug('%d %s', id, JSON.stringify(res.data));
[result] = res.data;
} catch (e) {
// FIXME request body larger than maxBodyLength limit
throw new SystemError('Sandbox Error', e.message);
}
return await adaptResult(result, params);
}