-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathrun.ts
148 lines (135 loc) · 3.86 KB
/
run.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { logJudger } from '../server/util/log'
import { spawn, spawnSync, ChildProcess } from 'child_process'
const env = {
HOME: '/tmp',
PATH: '/bin:/usr/bin:/usr/local/bin'
}
const BLACKLIST = [
'clone[a&268435456==268435456]',
'execve',
'flock',
'ptrace',
'sync',
'fdatasync',
'fsync',
'msync',
'sync_file_range',
'syncfs',
'unshare',
'setns',
'query_module',
'sysinfo',
'syslog',
'sysfs'
]
export interface RunOpts {
cmd: string
args?: string[]
uid?: number
gid?: number
network?: boolean
remountDev?: boolean
passExitcode?: boolean
maxCpuTime?: number
maxRealTime?: number
maxMemory?: number
maxStack?: number
chroot?: string
chdir?: string
syscalls?: boolean
stdin?: any
stdout?: any
stderr?: any
[index: string]: any
}
const defaultOpts: Partial<RunOpts> = {
args: [],
uid: 65534,
gid: 65534,
network: false,
remountDev: true,
passExitcode: false,
syscalls: false
}
const buildArgs = (args: RunOpts) => {
const o = Object.assign({}, defaultOpts, args)
const builder = ['--uid', o.uid, '--gid', o.gid, '--network', o.network]
builder.push('--remount-dev', o.remountDev, '--pass-exitcode', o.passExitcode)
if (o.syscalls) builder.push('--syscalls', `!${BLACKLIST.join(',')}`)
if (o.maxRealTime) builder.push('--max-real-time', o.maxRealTime)
if (o.maxCpuTime) builder.push('--max-cpu-time', o.maxCpuTime)
if (o.maxMemory) builder.push('--max-memory', o.maxMemory)
if (o.maxStack) builder.push('--max-stack', o.maxStack)
if (o.chroot) builder.push('--chroot', o.chroot)
if (o.chdir) builder.push('--chdir', o.chdir)
return builder.concat(o.cmd, o.args || []).map(String)
}
export const lrunSync = (opts: RunOpts) => {
const { stdin, stdout, stderr } = opts
const stdio = [stdin, stdout, stderr, 'pipe']
logJudger.debug('lrun', buildArgs(opts))
return spawnSync('lrun', buildArgs(opts), { maxBuffer: 10240, stdio, env })
}
export const lrun = (opts: RunOpts) => {
const { stdin, stdout, stderr } = opts
const stdio = [stdin, stdout, stderr, 'pipe']
logJudger.debug('lrun', buildArgs(opts))
return spawn('lrun', buildArgs(opts), { stdio, env })
}
export enum ExceedType {
CPU_TIME,
REAL_TIME,
MEMORY
}
export interface RunResult {
memory: number
cpuTime: number
realTime: number
exitCode: number
signal: number
exceed: null | ExceedType
error?: string // user program stderr output
}
const getExceedType = (str?: string) => {
switch (str) {
case 'CPU_TIME': return ExceedType.CPU_TIME
case 'REAL_TIME': return ExceedType.REAL_TIME
case 'MEMORY': return ExceedType.MEMORY
default: return null
}
}
export const parseResult = (res: string): RunResult => ({
memory: Number(res.match(/MEMORY\s+(\d+)/)?.[1]),
cpuTime: Number(res.match(/CPUTIME\s+([0-9.]+)/)?.[1]),
realTime: Number(res.match(/REALTIME\s+([0-9.]+)/)?.[1]),
exitCode: Number(res.match(/EXITCODE\s+(\d+)/)?.[1]),
signal: Number(res.match(/TERMSIG\s+(\d+)/)?.[1]),
exceed: getExceedType(res.match(/EXCEED\s+(\w+)/)?.[1])
})
export const wait = (cp: ChildProcess) => new Promise<RunResult>((resolve, reject) => {
let fd2 = '', fd3 = ''
cp.stdio[2]?.on('data', c => fd2 += c)
cp.stdio[3]?.on('data', c => fd3 += c)
cp.on('close', () => {
try {
const res = parseResult(fd3)
if (fd2) res.error = fd2
resolve(res)
} catch {
reject(fd2 + fd3)
}
})
})
export const interRun = (test: RunOpts, inter: RunOpts) => new Promise<RunResult>((resolve) => {
let t3 = '', i3 = '', work = 2
const t = lrun(test), i = lrun(inter)
const ret = () => resolve(parseResult(t3 || i3))
t.stdio[3]?.on('data', (r) => t3 += r)
i.stdio[3]?.on('data', (r) => i3 += r)
t.on('close', () => --work || ret())
i.on('close', () => --work || ret())
t.stdin.on('error', () => t.kill())
i.stdin.on('error', () => i.kill())
t.stdout.pipe(i.stdin)
i.stdout.pipe(t.stdin)
})