mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-12-22 15:41:52 +00:00
feat(judger): uoj_judger_v2 (#2)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
3fb14ca50d
@ -1,9 +1,9 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG CLONE_ADDFLAG
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
|
||||
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
|
||||
|
||||
ADD . /opt/uoj_judger
|
||||
WORKDIR /opt/uoj_judger
|
||||
|
@ -3,7 +3,8 @@
|
||||
getAptPackage(){
|
||||
printf "\n\n==> Getting environment packages\n"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
|
||||
apt-get update
|
||||
apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
|
||||
}
|
||||
|
||||
setJudgeConf(){
|
||||
@ -21,12 +22,6 @@ UOJEOF
|
||||
su judger <<EOD
|
||||
ln -s /var/uoj_data_copy /opt/uoj_judger/uoj_judger/data
|
||||
cd /opt/uoj_judger && chmod +x judge_client
|
||||
cat >uoj_judger/include/uoj_work_path.h <<UOJEOF
|
||||
#define UOJ_WORK_PATH "/opt/uoj_judger/uoj_judger"
|
||||
#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
|
||||
#define UOJ_JUDGER_PYTHON3_VERSION "3.8"
|
||||
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
|
||||
UOJEOF
|
||||
cd uoj_judger && make -j$(($(nproc) + 1))
|
||||
EOD
|
||||
}
|
||||
|
@ -5,433 +5,600 @@ import sys
|
||||
import os
|
||||
import pipes
|
||||
import socket
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock, Condition, RLock
|
||||
import fcntl
|
||||
import shutil
|
||||
|
||||
import traceback
|
||||
import time
|
||||
import logging
|
||||
import codecs
|
||||
from contextlib import closing
|
||||
from typing import *
|
||||
|
||||
import requests
|
||||
|
||||
import queue as queue
|
||||
from queue import Queue, Empty
|
||||
|
||||
taskQ = Queue()
|
||||
submission = None
|
||||
jconf: dict
|
||||
|
||||
# path related function
|
||||
|
||||
def uoj_url(uri):
|
||||
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
|
||||
def uoj_judger_path(path = ''):
|
||||
return "uoj_judger" + path
|
||||
|
||||
# os related funciton
|
||||
def clean_up_folder(path):
|
||||
for f in os.listdir(path):
|
||||
f_path = os.path.join(path, f)
|
||||
if os.path.isfile(f_path):
|
||||
os.unlink(f_path)
|
||||
else:
|
||||
shutil.rmtree(f_path)
|
||||
for f in os.listdir(path):
|
||||
f_path = os.path.join(path, f)
|
||||
if os.path.isfile(f_path):
|
||||
os.unlink(f_path)
|
||||
else:
|
||||
shutil.rmtree(f_path)
|
||||
|
||||
|
||||
def execute(cmd):
|
||||
if os.system(cmd):
|
||||
raise Exception('failed to execute: %s' % cmd)
|
||||
if os.system(cmd) != 0:
|
||||
raise Exception('failed to execute: %s' % cmd)
|
||||
|
||||
|
||||
def freopen(f, g):
|
||||
os.dup2(g.fileno(), f.fileno())
|
||||
g.close()
|
||||
os.dup2(g.fileno(), f.fileno())
|
||||
g.close()
|
||||
|
||||
|
||||
# utf 8
|
||||
def uoj_text_replace(e):
|
||||
return ''.join('<b>\\x%02x</b>' % b for b in e.object[e.start:e.end]), e.end
|
||||
|
||||
|
||||
codecs.register_error('uoj_text_replace', uoj_text_replace)
|
||||
|
||||
|
||||
# init
|
||||
def init():
|
||||
global jconf
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
with open('.conf.json', 'r') as fp:
|
||||
jconf = json.load(fp)
|
||||
assert 'uoj_protocol' in jconf
|
||||
assert 'uoj_host' in jconf
|
||||
assert 'judger_name' in jconf
|
||||
assert 'judger_password' in jconf
|
||||
assert 'socket_port' in jconf
|
||||
assert 'socket_password' in jconf
|
||||
global jconf
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
with open('.conf.json', 'r') as fp:
|
||||
jconf = json.load(fp)
|
||||
assert 'uoj_protocol' in jconf
|
||||
assert 'uoj_host' in jconf
|
||||
assert 'judger_name' in jconf
|
||||
assert 'judger_password' in jconf
|
||||
assert 'socket_port' in jconf
|
||||
assert 'socket_password' in jconf
|
||||
if 'judger_cpus' not in jconf:
|
||||
jconf['judger_cpus'] = [
|
||||
'0'] # it should be distinct physical cores! Usually, you can just set it to some even numbers
|
||||
|
||||
# socket server
|
||||
def socket_server_loop():
|
||||
SOCK_CLOEXEC = 524288
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('', jconf['socket_port']))
|
||||
s.listen(5)
|
||||
|
||||
while True:
|
||||
try:
|
||||
conn, addr = s.accept()
|
||||
with closing(conn) as conn:
|
||||
data = conn.recv(1024).decode()
|
||||
assert data != None
|
||||
task = json.loads(data)
|
||||
assert task['password'] == jconf['socket_password']
|
||||
assert 'cmd' in task
|
||||
|
||||
taskQ.put(task)
|
||||
|
||||
if task['cmd'] == 'stop':
|
||||
print('the judge client is closing...')
|
||||
taskQ.join()
|
||||
conn.sendall(b'ok')
|
||||
return 'stop'
|
||||
except Exception:
|
||||
print('['+time.asctime()+']', 'connection rejected', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr)
|
||||
|
||||
def start_judger_server():
|
||||
global socket_server_thread
|
||||
|
||||
print_judge_client_status()
|
||||
print('hello!', file=sys.stderr)
|
||||
|
||||
socket_server_thread = Thread(target = socket_server_loop)
|
||||
socket_server_thread.setDaemon(True)
|
||||
socket_server_thread.start()
|
||||
|
||||
judger_loop()
|
||||
# path related function
|
||||
def uoj_url(uri):
|
||||
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
|
||||
|
||||
# report thread
|
||||
def report_loop():
|
||||
if 'is_hack' in submission:
|
||||
return
|
||||
while not submission_judged:
|
||||
try:
|
||||
with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
try:
|
||||
status = f.read(100)
|
||||
except Exception:
|
||||
status = None
|
||||
finally:
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
|
||||
if status != None:
|
||||
data = {}
|
||||
data['update-status'] = True
|
||||
data['id'] = submission['id']
|
||||
if 'is_custom_test' in submission:
|
||||
data['is_custom_test'] = True
|
||||
data['status'] = status
|
||||
uoj_interact(data)
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# handle task in main thread
|
||||
def handle_task():
|
||||
need_restart = False
|
||||
try:
|
||||
while True:
|
||||
task = taskQ.get_nowait()
|
||||
|
||||
if task['cmd'] == 'update':
|
||||
try:
|
||||
uoj_download('/judger', 'judger_update.zip')
|
||||
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
|
||||
except:
|
||||
print("error when update", file=sys.stderr)
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
uoj_sync_judge_client()
|
||||
need_restart = True
|
||||
elif task['cmd'] == 'stop':
|
||||
taskQ.task_done()
|
||||
socket_server_thread.join()
|
||||
|
||||
print_judge_client_status()
|
||||
print("goodbye!", file=sys.stderr)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
taskQ.task_done()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
if need_restart:
|
||||
os.execl('./judge_client', './judge_client')
|
||||
def uoj_judger_path(path=''):
|
||||
return os.getcwd() + "/uoj_judger" + path
|
||||
|
||||
def print_judge_client_status():
|
||||
print('[' + time.asctime() + ']', end=' ', file=sys.stderr)
|
||||
if submission != None:
|
||||
print(submission, end=' ', file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
|
||||
# interact with uoj_judger
|
||||
def get_judger_result():
|
||||
res = {}
|
||||
with open(uoj_judger_path('/result/result.txt'), 'r') as fres:
|
||||
res['score'] = 0
|
||||
res['time'] = 0
|
||||
res['memory'] = 0
|
||||
while True:
|
||||
line = fres.readline()
|
||||
if line == '':
|
||||
break
|
||||
line = line.strip()
|
||||
if line == 'details':
|
||||
res['details'] = fres.read()
|
||||
break
|
||||
|
||||
sp = line.split()
|
||||
assert len(sp) >= 1
|
||||
if sp[0] == 'error':
|
||||
res['error'] = line[len('error') + 1:]
|
||||
else:
|
||||
assert len(sp) == 2
|
||||
res[sp[0]] = sp[1]
|
||||
res['score'] = int(res['score'])
|
||||
res['time'] = int(res['time'])
|
||||
res['memory'] = int(res['memory'])
|
||||
res['status'] = 'Judged'
|
||||
return res
|
||||
class RWLock:
|
||||
def __init__(self):
|
||||
self.rwlock = 0
|
||||
self.writers_waiting = 0
|
||||
self.monitor = Lock()
|
||||
self.readers_ok = Condition(self.monitor)
|
||||
self.writers_ok = Condition(self.monitor)
|
||||
|
||||
def update_problem_data(problem_id, problem_mtime):
|
||||
try:
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
return
|
||||
copy_name = uoj_judger_path('/data/%d' % problem_id)
|
||||
copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id)
|
||||
if os.path.isdir(copy_name):
|
||||
quoted_copy_name = pipes.quote(copy_name)
|
||||
if os.path.getmtime(copy_name) >= problem_mtime:
|
||||
execute('touch -a %s' % quoted_copy_name)
|
||||
return
|
||||
else:
|
||||
execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name))
|
||||
del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99]
|
||||
for fname in del_list:
|
||||
quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname))
|
||||
os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname))
|
||||
|
||||
uoj_download('/problem/%d' % problem_id, copy_zip_name)
|
||||
execute('cd %s && unzip -o -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id))
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
raise Exception('failed to update problem data of #%d' % problem_id)
|
||||
else:
|
||||
print_judge_client_status()
|
||||
print('updated problem data of #%d successfully' % problem_id, file=sys.stderr)
|
||||
def acquire_read(self):
|
||||
self.monitor.acquire()
|
||||
while self.rwlock < 0 or self.writers_waiting:
|
||||
self.readers_ok.wait()
|
||||
self.rwlock += 1
|
||||
self.monitor.release()
|
||||
|
||||
def acquire_write(self):
|
||||
self.monitor.acquire()
|
||||
while self.rwlock != 0:
|
||||
self.writers_waiting += 1
|
||||
self.writers_ok.wait()
|
||||
self.writers_waiting -= 1
|
||||
self.rwlock = -1
|
||||
self.monitor.release()
|
||||
|
||||
def promote(self):
|
||||
self.monitor.acquire()
|
||||
self.rwlock -= 1
|
||||
while self.rwlock != 0:
|
||||
self.writers_waiting += 1
|
||||
self.writers_ok.wait()
|
||||
self.writers_waiting -= 1
|
||||
self.rwlock = -1
|
||||
self.monitor.release()
|
||||
|
||||
def demote(self):
|
||||
self.monitor.acquire()
|
||||
self.rwlock = 1
|
||||
self.readers_ok.notifyAll()
|
||||
self.monitor.release()
|
||||
|
||||
def release(self):
|
||||
self.monitor.acquire()
|
||||
if self.rwlock < 0:
|
||||
self.rwlock = 0
|
||||
else:
|
||||
self.rwlock -= 1
|
||||
wake_writers = self.writers_waiting and self.rwlock == 0
|
||||
wake_readers = self.writers_waiting == 0
|
||||
self.monitor.release()
|
||||
if wake_writers:
|
||||
self.writers_ok.acquire()
|
||||
self.writers_ok.notify()
|
||||
self.writers_ok.release()
|
||||
elif wake_readers:
|
||||
self.readers_ok.acquire()
|
||||
self.readers_ok.notify_all()
|
||||
self.readers_ok.release()
|
||||
|
||||
|
||||
class Judger:
|
||||
problem_data_lock = RWLock()
|
||||
send_and_fetch_lock = RLock()
|
||||
|
||||
judger_id: int
|
||||
cpus: str
|
||||
main_path: str
|
||||
submission: Optional[dict]
|
||||
submission_judged: bool
|
||||
main_thread: Optional[Thread]
|
||||
report_thread: Optional[Thread]
|
||||
_taskQ: Queue
|
||||
|
||||
def log_judge_client_status(self):
|
||||
logging.info('submission: ' + str(self.submission))
|
||||
|
||||
def __init__(self, judger_id, cpus):
|
||||
self.judger_id = judger_id
|
||||
self.cpus = cpus
|
||||
self.main_path = '/tmp/' + jconf['judger_name'] + '/' + str(self.judger_id)
|
||||
self.submission = None
|
||||
self.main_thread = Thread(target=self._main_loop)
|
||||
self.report_thread = None
|
||||
self.submission_judged = False
|
||||
self._taskQ = Queue()
|
||||
|
||||
def start(self):
|
||||
execute('mkdir -p %s' % (self.main_path + '/result'))
|
||||
execute('mkdir -p %s' % (self.main_path + '/work'))
|
||||
|
||||
self.log_judge_client_status()
|
||||
logging.info('hello from judger #%d' % self.judger_id)
|
||||
|
||||
self.main_thread.start()
|
||||
|
||||
def suspend(self):
|
||||
self._taskQ.put('suspend')
|
||||
self._taskQ.join()
|
||||
|
||||
def resume(self):
|
||||
self._taskQ.put('resume')
|
||||
self._taskQ.join()
|
||||
|
||||
def exit(self):
|
||||
self._taskQ.put('exit')
|
||||
self._taskQ.join()
|
||||
self.main_thread.join()
|
||||
|
||||
# report thread
|
||||
def _report_loop(self):
|
||||
if 'is_hack' in self.submission:
|
||||
return
|
||||
while not self.submission_judged:
|
||||
try:
|
||||
with open(self.main_path + '/result/cur_status.txt', 'r') as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
try:
|
||||
status = f.read(100)
|
||||
except Exception:
|
||||
status = None
|
||||
finally:
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
|
||||
if status != None:
|
||||
data = {}
|
||||
data['update-status'] = True
|
||||
data['id'] = self.submission['id']
|
||||
if 'is_custom_test' in self.submission:
|
||||
data['is_custom_test'] = True
|
||||
data['status'] = status
|
||||
uoj_interact(data)
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_result(self):
|
||||
res = {}
|
||||
with open(self.main_path + '/result/result.txt', 'r', encoding='utf-8', errors='uoj_text_replace') as fres:
|
||||
res['score'] = 0
|
||||
res['time'] = 0
|
||||
res['memory'] = 0
|
||||
while True:
|
||||
line = fres.readline()
|
||||
if line == '':
|
||||
break
|
||||
line = line.strip()
|
||||
if line == 'details':
|
||||
res['details'] = fres.read()
|
||||
break
|
||||
|
||||
sp = line.split()
|
||||
assert len(sp) >= 1
|
||||
if sp[0] == 'error':
|
||||
res['error'] = line[len('error') + 1:]
|
||||
else:
|
||||
assert len(sp) == 2
|
||||
res[sp[0]] = sp[1]
|
||||
res['score'] = int(res['score'])
|
||||
res['time'] = int(res['time'])
|
||||
res['memory'] = int(res['memory'])
|
||||
res['status'] = 'Judged'
|
||||
return res
|
||||
|
||||
def _remove_problem_data(self, problem_id):
|
||||
quoted_path = pipes.quote(uoj_judger_path(f'/data/{problem_id}'))
|
||||
execute(f'chmod 700 {quoted_path} -R && rm -rf {quoted_path}')
|
||||
|
||||
def _update_problem_data_atime(self, problem_id):
|
||||
path = uoj_judger_path(f'/data/{problem_id}')
|
||||
execute(f'touch -a {pipes.quote(path)}')
|
||||
|
||||
def _update_problem_data(self, problem_id, problem_mtime):
|
||||
Judger.problem_data_lock.acquire_read()
|
||||
try:
|
||||
copy_name = uoj_judger_path(f'/data/{problem_id}')
|
||||
copy_zip_name = uoj_judger_path(f'/data/{problem_id}.zip')
|
||||
if os.path.isdir(copy_name):
|
||||
if os.path.getmtime(copy_name) >= problem_mtime:
|
||||
self._update_problem_data_atime(problem_id)
|
||||
return
|
||||
else:
|
||||
Judger.problem_data_lock.promote()
|
||||
self._remove_problem_data(problem_id)
|
||||
else:
|
||||
Judger.problem_data_lock.promote()
|
||||
|
||||
del_list = sorted(os.listdir(uoj_judger_path('/data')),
|
||||
key=lambda p: os.path.getatime(uoj_judger_path(f'/data/{p}')))[:-99]
|
||||
for p in del_list:
|
||||
self._remove_problem_data(p)
|
||||
|
||||
uoj_download(f'/problem/{problem_id}', copy_zip_name)
|
||||
execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (
|
||||
uoj_judger_path('/data'), problem_id, problem_id, problem_id))
|
||||
os.utime(uoj_judger_path(f'/data/{problem_id}'), (time.time(), problem_mtime))
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('problem update error')
|
||||
raise Exception(f'failed to update problem data of #{problem_id}')
|
||||
else:
|
||||
self.log_judge_client_status()
|
||||
logging.info(f'updated problem data of #{problem_id} successfully')
|
||||
finally:
|
||||
Judger.problem_data_lock.release()
|
||||
|
||||
def _judge(self):
|
||||
clean_up_folder(self.main_path + '/work')
|
||||
clean_up_folder(self.main_path + '/result')
|
||||
self._update_problem_data(self.submission['problem_id'], self.submission['problem_mtime'])
|
||||
|
||||
with open(self.main_path + '/work/submission.conf', 'w') as fconf:
|
||||
uoj_download(self.submission['content']['file_name'], self.main_path + '/work/all.zip')
|
||||
execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(self.main_path + '/work'))
|
||||
for k, v in self.submission['content']['config']:
|
||||
print(k, v, file=fconf)
|
||||
|
||||
if 'is_hack' in self.submission:
|
||||
if self.submission['hack']['input_type'] == 'USE_FORMATTER':
|
||||
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input_raw.txt')
|
||||
execute('%s <%s >%s' % (
|
||||
pipes.quote(uoj_judger_path('/run/formatter')),
|
||||
pipes.quote(self.main_path + '/work/hack_input_raw.txt'),
|
||||
pipes.quote(self.main_path + '/work/hack_input.txt')
|
||||
))
|
||||
else:
|
||||
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input.txt')
|
||||
print('test_new_hack_only on', file=fconf)
|
||||
elif 'is_custom_test' in self.submission:
|
||||
print('custom_test on', file=fconf)
|
||||
|
||||
self.submission_judged = False
|
||||
self.report_thread = Thread(target=self._report_loop)
|
||||
self.report_thread.start()
|
||||
|
||||
Judger.problem_data_lock.acquire_read()
|
||||
try:
|
||||
main_judger_cmd = pipes.quote(uoj_judger_path('/main_judger'))
|
||||
if self.cpus != 'all':
|
||||
main_judger_cmd = 'taskset -c %s %s' % (pipes.quote(self.cpus), main_judger_cmd)
|
||||
execute('cd %s && %s' % (pipes.quote(self.main_path), main_judger_cmd))
|
||||
finally:
|
||||
Judger.problem_data_lock.release()
|
||||
self.submission_judged = True
|
||||
self.report_thread.join()
|
||||
self.report_thread = None
|
||||
|
||||
return self._get_result()
|
||||
|
||||
def _send_and_fetch(self, result=None, fetch_new=True):
|
||||
"""send judgement result, and fetch new submission to judge"""
|
||||
|
||||
data = {}
|
||||
files = {}
|
||||
|
||||
if not fetch_new:
|
||||
data['fetch_new'] = False
|
||||
|
||||
if result is not None:
|
||||
data['submit'] = True
|
||||
if 'is_hack' in self.submission:
|
||||
data['is_hack'] = True
|
||||
data['id'] = self.submission['hack']['id']
|
||||
if result != False and result['score']:
|
||||
try:
|
||||
logging.info("succ hack!")
|
||||
files = {
|
||||
('hack_input', open(self.main_path + '/work/hack_input.txt', 'rb')),
|
||||
('std_output', open(self.main_path + '/work/std_output.txt', 'rb'))
|
||||
}
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('hack: submit error')
|
||||
result = False
|
||||
elif 'is_custom_test' in self.submission:
|
||||
data['is_custom_test'] = True
|
||||
data['id'] = self.submission['id']
|
||||
else:
|
||||
data['id'] = self.submission['id']
|
||||
|
||||
if 'judge_time' in self.submission:
|
||||
data['judge_time'] = self.submission['judge_time']
|
||||
|
||||
if 'tid' in self.submission:
|
||||
data['tid'] = self.submission['tid']
|
||||
|
||||
if result == False:
|
||||
result = {
|
||||
'score': 0,
|
||||
'error': 'Judgment Failed',
|
||||
'details': 'Unknown Error'
|
||||
}
|
||||
result['status'] = 'Judged'
|
||||
data['result'] = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
while True:
|
||||
Judger.send_and_fetch_lock.acquire()
|
||||
try:
|
||||
ret = uoj_interact(data, files)
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('uoj_interact error')
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
Judger.send_and_fetch_lock.release()
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
self.submission = json.loads(ret)
|
||||
except Exception:
|
||||
if ret != 'Nothing to judge':
|
||||
logging.info(ret)
|
||||
self.submission = None
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _main_loop(self):
|
||||
while True:
|
||||
if not self._taskQ.empty():
|
||||
while True:
|
||||
task = self._taskQ.get()
|
||||
if task == 'resume':
|
||||
self._taskQ.task_done()
|
||||
break
|
||||
elif task == 'exit':
|
||||
self._taskQ.task_done()
|
||||
return
|
||||
else:
|
||||
self._taskQ.task_done()
|
||||
|
||||
if not self._send_and_fetch():
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
self.log_judge_client_status()
|
||||
logging.info('judging')
|
||||
|
||||
while True:
|
||||
try:
|
||||
res = self._judge()
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('judge error')
|
||||
res = False
|
||||
if not self._send_and_fetch(result=res, fetch_new=self._taskQ.empty()):
|
||||
break
|
||||
|
||||
|
||||
class JudgerServer:
|
||||
taskQ: Queue
|
||||
judgers: List[Judger]
|
||||
socket_thread: Thread
|
||||
|
||||
def __init__(self):
|
||||
self.taskQ = Queue()
|
||||
self.judgers = [Judger(i, cpus) for i, cpus in enumerate(jconf['judger_cpus'])]
|
||||
self.socket_thread = Thread(target=self._socket_loop)
|
||||
|
||||
def _socket_loop(self):
|
||||
try:
|
||||
SOCK_CLOEXEC = 524288
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('', jconf['socket_port']))
|
||||
s.listen(5)
|
||||
|
||||
while True:
|
||||
try:
|
||||
conn, addr = s.accept()
|
||||
with closing(conn) as conn:
|
||||
data = conn.recv(1024)
|
||||
assert data != None
|
||||
task = json.loads(data)
|
||||
assert task['password'] == jconf['socket_password']
|
||||
assert 'cmd' in task
|
||||
|
||||
self.taskQ.put(task)
|
||||
|
||||
if task['cmd'] == 'stop':
|
||||
logging.info('the judge client is closing...')
|
||||
self.taskQ.join()
|
||||
conn.sendall(b'ok')
|
||||
return 'stop'
|
||||
except Exception:
|
||||
logging.exception('connection rejected')
|
||||
else:
|
||||
logging.info('a new task accomplished')
|
||||
except Exception:
|
||||
self.taskQ.put({'cmd': 'stop'})
|
||||
logging.exception('socket server error!')
|
||||
|
||||
def run(self):
|
||||
self.socket_thread.start()
|
||||
for judger in self.judgers:
|
||||
judger.start()
|
||||
|
||||
while True:
|
||||
task = self.taskQ.get()
|
||||
|
||||
need_restart = False
|
||||
block_wait = False
|
||||
|
||||
for judger in self.judgers:
|
||||
judger.suspend()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if task['cmd'] in ['update', 'self-update']:
|
||||
try:
|
||||
uoj_download('/judger', 'judger_update.zip')
|
||||
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
|
||||
except:
|
||||
print(sys.stderr, "error when update")
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
uoj_sync_judge_client()
|
||||
need_restart = True
|
||||
elif task['cmd'] == 'stop':
|
||||
self.taskQ.task_done()
|
||||
self.socket_thread.join()
|
||||
for judger in self.judgers:
|
||||
judger.exit()
|
||||
logging.info("goodbye!")
|
||||
sys.exit(0)
|
||||
|
||||
self.taskQ.task_done()
|
||||
task = self.taskQ.get(block=block_wait)
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
if need_restart:
|
||||
os.execl('./judge_client', './judge_client')
|
||||
|
||||
for judger in self.judgers:
|
||||
judger.resume()
|
||||
|
||||
def judge():
|
||||
global report_thread
|
||||
global submission_judged
|
||||
|
||||
clean_up_folder(uoj_judger_path('/work'))
|
||||
clean_up_folder(uoj_judger_path('/result'))
|
||||
update_problem_data(submission['problem_id'], submission['problem_mtime'])
|
||||
|
||||
with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf:
|
||||
uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip'))
|
||||
execute("cd %s && unzip -o -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work')))
|
||||
for k, v in submission['content']['config']:
|
||||
print(k, v, file=fconf)
|
||||
|
||||
if 'is_hack' in submission:
|
||||
if submission['hack']['input_type'] == 'USE_FORMATTER':
|
||||
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt'))
|
||||
execute('%s <%s >%s' % (
|
||||
pipes.quote(uoj_judger_path('/run/formatter')),
|
||||
pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')),
|
||||
pipes.quote(uoj_judger_path('/work/hack_input.txt'))))
|
||||
else:
|
||||
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt'))
|
||||
print('test_new_hack_only on', file=fconf)
|
||||
elif 'is_custom_test' in submission:
|
||||
print('custom_test on', file=fconf)
|
||||
|
||||
report_thread = Thread(target = report_loop)
|
||||
report_thread.setDaemon(True)
|
||||
|
||||
submission_judged = False
|
||||
report_thread.start()
|
||||
execute(pipes.quote(uoj_judger_path('/main_judger')))
|
||||
submission_judged = True
|
||||
report_thread.join()
|
||||
|
||||
return get_judger_result()
|
||||
|
||||
# interact with uoj web server
|
||||
def uoj_interact(data, files = {}):
|
||||
data = data.copy()
|
||||
data.update({
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
})
|
||||
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
|
||||
# TODO: set a larger timeout
|
||||
def uoj_interact(data, files={}):
|
||||
data = data.copy()
|
||||
data.update({
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
})
|
||||
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
|
||||
|
||||
|
||||
def uoj_download(uri, filename):
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
with open(filename, 'wb') as f:
|
||||
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
|
||||
for chunk in r.iter_content(chunk_size=65536):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
with open(filename, 'wb') as f:
|
||||
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
|
||||
for chunk in r.iter_content(chunk_size=65536):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
def uoj_sync_judge_client():
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
|
||||
if ret != "ok":
|
||||
raise Exception('failed to sync judge clients: %s' % ret)
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
|
||||
if ret != "ok":
|
||||
raise Exception('failed to sync judge clients: %s' % ret)
|
||||
|
||||
def send_and_fetch(result = None, fetch_new = True):
|
||||
global submission
|
||||
|
||||
"""send judgement result, and fetch new submission to judge"""
|
||||
|
||||
data = {}
|
||||
files = {}
|
||||
|
||||
if not fetch_new:
|
||||
data['fetch_new'] = False
|
||||
|
||||
if result != None:
|
||||
data['submit'] = True
|
||||
if 'is_hack' in submission:
|
||||
data['is_hack'] = True
|
||||
data['id'] = submission['hack']['id']
|
||||
if result != False and result['score']:
|
||||
try:
|
||||
print("succ hack!", file=sys.stderr)
|
||||
files = {
|
||||
('hack_input', open('uoj_judger/work/hack_input.txt', 'r')),
|
||||
('std_output', open('uoj_judger/work/std_output.txt', 'r'))
|
||||
}
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
result = False
|
||||
elif 'is_custom_test' in submission:
|
||||
data['is_custom_test'] = True
|
||||
data['id'] = submission['id']
|
||||
else:
|
||||
data['id'] = submission['id']
|
||||
|
||||
if result == False:
|
||||
result = {
|
||||
'score': 0,
|
||||
'error': 'Judgement Failed',
|
||||
'details': 'Unknown Error'
|
||||
}
|
||||
result['status'] = 'Judged'
|
||||
data['result'] = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
while True:
|
||||
try:
|
||||
ret = uoj_interact(data, files)
|
||||
print(ret)
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
else:
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
submission = json.loads(ret)
|
||||
except Exception as e:
|
||||
submission = None
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# judge client
|
||||
def judger_loop():
|
||||
ok = False
|
||||
while True:
|
||||
fetch_new = True
|
||||
|
||||
if ok and not (taskQ.empty() and socket_server_thread.isAlive()):
|
||||
fetch_new = False
|
||||
|
||||
if not ok:
|
||||
while True:
|
||||
if not taskQ.empty():
|
||||
handle_task()
|
||||
if not socket_server_thread.isAlive():
|
||||
raise Exception('socket server exited unexpectedly')
|
||||
|
||||
if send_and_fetch():
|
||||
break
|
||||
|
||||
print('['+time.asctime()+']', 'Nothing to judge...')
|
||||
time.sleep(2)
|
||||
|
||||
ok = True
|
||||
print_judge_client_status()
|
||||
print('judging', file=sys.stderr)
|
||||
|
||||
try:
|
||||
res = judge()
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
res = False
|
||||
|
||||
ok = send_and_fetch(result=res,fetch_new=fetch_new)
|
||||
|
||||
# main function
|
||||
def main():
|
||||
init()
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
start_judger_server()
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] == 'start':
|
||||
pid = os.fork()
|
||||
if pid == -1:
|
||||
raise Exception('fork failed')
|
||||
elif pid > 0:
|
||||
return
|
||||
else:
|
||||
freopen(sys.stdout, open(os.devnull, 'wb'))
|
||||
freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0))
|
||||
start_judger_server()
|
||||
elif sys.argv[1] == 'update':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'update'
|
||||
}).encode())
|
||||
return
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise Exception('update failed')
|
||||
elif sys.argv[1] == 'stop':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'stop'
|
||||
}).encode())
|
||||
if s.recv(10).decode() != 'ok':
|
||||
raise Exception('stop failed')
|
||||
return
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise Exception('stop failed')
|
||||
raise Exception('invalid argument')
|
||||
init()
|
||||
|
||||
logging.basicConfig(filename='log/judge.log', level=logging.INFO,
|
||||
format='%(asctime)-15s [%(levelname)s]: %(message)s')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
JudgerServer().run()
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] == 'start':
|
||||
pid = os.fork()
|
||||
if pid == -1:
|
||||
raise Exception('fork failed')
|
||||
elif pid > 0:
|
||||
return
|
||||
else:
|
||||
freopen(sys.stdout, open(os.devnull, 'w'))
|
||||
freopen(sys.stderr, open('log/judge.log', 'a', encoding='utf-8', errors='uoj_text_replace'))
|
||||
JudgerServer().run()
|
||||
elif sys.argv[1] in ['update', 'self-update']:
|
||||
try:
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': sys.argv[1]
|
||||
}).encode())
|
||||
return
|
||||
except OSError:
|
||||
JudgerServer.update(broadcast=sys.argv[1] == 'update')
|
||||
return
|
||||
except Exception:
|
||||
logging.exception('update error')
|
||||
raise Exception('update failed')
|
||||
elif sys.argv[1] == 'stop':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'stop'
|
||||
}).encode())
|
||||
if s.recv(10) != b'ok':
|
||||
raise Exception('stop failed')
|
||||
return
|
||||
except Exception:
|
||||
logging.exception('stop error')
|
||||
raise Exception('stop failed')
|
||||
raise Exception('invalid argument')
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
main()
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
logging.exception('critical error!')
|
||||
sys.exit(1)
|
||||
|
@ -1,5 +1,5 @@
|
||||
INCLUDE_PATH = include
|
||||
CXXFLAGS = -I./include -O2
|
||||
CXXFLAGS = -I./include -O2 --std=c++17 -Wall -lstdc++fs
|
||||
|
||||
EXE_CHECKER = \
|
||||
builtin/checker/bcmp \
|
||||
@ -26,24 +26,35 @@ EXE = main_judger \
|
||||
run/formatter \
|
||||
run/run_program \
|
||||
run/run_interaction \
|
||||
builtin/judger/judger
|
||||
run/compile \
|
||||
builtin/judger/judger \
|
||||
$(EXE_CHECKER)
|
||||
|
||||
all: $(EXE) $(EXE_CHECKER)
|
||||
runner: $(EXE)
|
||||
checker: $(EXE_CHECKER)
|
||||
OBJ = tests/catch_amalgamated.o tests/test.o
|
||||
|
||||
all: $(EXE)
|
||||
|
||||
% : %.cpp
|
||||
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@
|
||||
|
||||
run/run_program: include/uoj_env.h run/run_program_conf.h
|
||||
run/formatter : include/testlib.h
|
||||
run/run_interaction: run/run_interaction.cpp include/uoj_env.h
|
||||
$(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@
|
||||
run/formatter : include/testlib.h
|
||||
run/compile : include/uoj_run.h
|
||||
|
||||
builtin/judger/judger: include
|
||||
main_judger: include
|
||||
run/run_program : run/run_program.cpp run/run_program_conf.h run/run_program_sandbox.h include/uoj_run.h
|
||||
$(CXX) $(CXXFLAGS) $< -o $@ -lseccomp -pthread
|
||||
|
||||
run/run_interaction : run/run_interaction.cpp include/uoj_run.h
|
||||
$(CXX) $(CXXFLAGS) $< -o $@ -pthread
|
||||
|
||||
builtin/judger/judger: include/*.h
|
||||
main_judger: include/*.h
|
||||
|
||||
tests/test.o: include/*.h
|
||||
|
||||
tests/test: tests/catch_amalgamated.o tests/test.o
|
||||
$(CXX) $(CXXFLAGS) $^ -o $@ -pthread
|
||||
|
||||
$(EXE_CHECKER): include/testlib.h
|
||||
|
||||
clean:
|
||||
rm -f $(EXE) $(EXE_CHECKER)
|
||||
rm -f $(EXE) $(OBJ)
|
||||
|
@ -1,150 +1,51 @@
|
||||
#include "uoj_judger.h"
|
||||
|
||||
struct SubtaskInfo {
|
||||
bool passed;
|
||||
int score;
|
||||
PointInfo submit_answer_test_point_with_auto_generation(int i, TestPointConfig tpc = TestPointConfig()) {
|
||||
static set<string> compiled;
|
||||
static set<int> generated;
|
||||
|
||||
SubtaskInfo() {
|
||||
int output_file_id = conf_int("output_file_id", i, i);
|
||||
tpc.submit_answer = true;
|
||||
tpc.output_file_name = work_path + "/" + conf_output_file_name(conf_int("output_file_id", i, i));
|
||||
|
||||
string gen_name = conf_str("output_gen", output_file_id, "");
|
||||
if (gen_name.empty()) {
|
||||
generated.insert(output_file_id);
|
||||
}
|
||||
if (generated.count(output_file_id)) {
|
||||
return test_point("", i, tpc);
|
||||
}
|
||||
SubtaskInfo(const bool &_p, const int &_s)
|
||||
: passed(_p), score(_s){}
|
||||
};
|
||||
|
||||
void ordinary_test() {
|
||||
int n = conf_int("n_tests", 10);
|
||||
int m = conf_int("n_ex_tests", 0);
|
||||
int nT = conf_int("n_subtasks", 0);
|
||||
tpc.limit = conf_run_limit("gen", output_file_id, RL_GENERATOR_DEFAULT);
|
||||
generated.insert(output_file_id);
|
||||
|
||||
if (!conf_is("submit_answer", "on")) {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (!compiled.count(gen_name)) {
|
||||
report_judge_status_f("Compiling %s", gen_name.c_str());
|
||||
if (auto c_ret = compile_submission_program(gen_name); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
compiled.insert(gen_name);
|
||||
}
|
||||
|
||||
bool passed = true;
|
||||
if (nT == 0) {
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
add_point_info(po);
|
||||
}
|
||||
} else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") {
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
po.scr = i == 1 ? 0 : -100;
|
||||
add_point_info(po);
|
||||
break;
|
||||
} else {
|
||||
po.scr = i == 1 ? 100 : 0;
|
||||
add_point_info(po);
|
||||
}
|
||||
}
|
||||
tpc.submit_answer = false;
|
||||
return test_point(gen_name, i, tpc);
|
||||
}
|
||||
|
||||
void ordinary_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
PrimaryDataTestConfig pdtc;
|
||||
pdtc.disable_ex_tests = true;
|
||||
primary_data_test([](int i) {
|
||||
return submit_answer_test_point_with_auto_generation(i);
|
||||
}, pdtc);
|
||||
} else {
|
||||
map<int, SubtaskInfo> subtasks;
|
||||
map<int,int> minScore;
|
||||
for (int t = 1; t <= nT; t++) {
|
||||
string subtaskType = conf_str("subtask_type", t, "packed");
|
||||
int startI = conf_int("subtask_end", t - 1, 0) + 1;
|
||||
int endI = conf_int("subtask_end", t, 0);
|
||||
|
||||
vector<PointInfo> points;
|
||||
minScore[t] = 100;
|
||||
|
||||
vector<int> dependences;
|
||||
if (conf_str("subtask_dependence", t, "none") == "many") {
|
||||
string cur = "subtask_dependence_" + vtos(t);
|
||||
int p = 1;
|
||||
while (conf_int(cur, p, 0) != 0) {
|
||||
dependences.push_back(conf_int(cur, p, 0));
|
||||
p++;
|
||||
}
|
||||
} else if (conf_int("subtask_dependence", t, 0) != 0) {
|
||||
dependences.push_back(conf_int("subtask_dependence", t, 0));
|
||||
}
|
||||
bool skipped = false;
|
||||
for (vector<int>::iterator it = dependences.begin(); it != dependences.end(); it++) {
|
||||
if (subtaskType == "packed") {
|
||||
if (!subtasks[*it].passed) {
|
||||
skipped = true;
|
||||
break;
|
||||
}
|
||||
} else if (subtaskType == "min") {
|
||||
minScore[t] = min(minScore[t], minScore[*it]);
|
||||
}
|
||||
}
|
||||
if (skipped) {
|
||||
add_subtask_info(t, 0, "Skipped", points);
|
||||
continue;
|
||||
}
|
||||
|
||||
int tfull = conf_int("subtask_score", t, 100 / nT);
|
||||
int tscore = scale_score(minScore[t], tfull);
|
||||
string info = "Accepted";
|
||||
for (int i = startI; i <= endI; i++) {
|
||||
report_judge_status_f("Judging Test #%d of Subtask #%d", i, t);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (subtaskType == "packed") {
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
po.scr = i == startI ? 0 : -tfull;
|
||||
tscore = 0;
|
||||
points.push_back(po);
|
||||
info = po.info;
|
||||
break;
|
||||
} else {
|
||||
po.scr = i == startI ? tfull : 0;
|
||||
tscore = tfull;
|
||||
points.push_back(po);
|
||||
}
|
||||
} else if (subtaskType == "min") {
|
||||
minScore[t] = min(minScore[t], po.scr);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, tfull);
|
||||
if (po.scr <= tscore) {
|
||||
tscore = po.scr;
|
||||
points.push_back(po);
|
||||
info = po.info;
|
||||
} else {
|
||||
points.push_back(po);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subtasks[t] = SubtaskInfo(info == "Accepted", tscore);
|
||||
|
||||
add_subtask_info(t, tscore, info, points);
|
||||
report_judge_status_f("Compiling");
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
}
|
||||
if (conf_is("submit_answer", "on") || !passed) {
|
||||
end_judge_ok();
|
||||
}
|
||||
|
||||
tot_score = 100;
|
||||
for (int i = 1; i <= m; i++) {
|
||||
report_judge_status_f("Judging Extra Test #%d", i);
|
||||
PointInfo po = test_point("answer", -i);
|
||||
if (po.scr != 100) {
|
||||
po.num = -1;
|
||||
po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
|
||||
po.scr = -3;
|
||||
add_point_info(po);
|
||||
end_judge_ok();
|
||||
}
|
||||
}
|
||||
if (m != 0) {
|
||||
PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
|
||||
add_point_info(po);
|
||||
primary_data_test([](int i) {
|
||||
return test_point("answer", i);
|
||||
});
|
||||
}
|
||||
end_judge_ok();
|
||||
}
|
||||
@ -153,8 +54,7 @@ void hack_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
end_judge_judgement_failed("Hack is not supported in this problem.");
|
||||
} else {
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
TestPointConfig tpc;
|
||||
@ -170,80 +70,52 @@ void hack_test() {
|
||||
|
||||
void sample_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
int n = conf_int("n_tests", 10);
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
if (conf_is("check_existence_only_in_sample_test", "on")) {
|
||||
if (conf_is("check_existence_only_in_sample_test", "on")) {
|
||||
main_data_test([](int i) {
|
||||
TestPointConfig tpc = TestPointConfig();
|
||||
tpc.auto_complete(i);
|
||||
|
||||
string usrout = file_preview(tpc.output_file_name);
|
||||
if (usrout == "") {
|
||||
add_point_info(PointInfo(i, 0, -1, -1,
|
||||
"default",
|
||||
file_preview(tpc.input_file_name), usrout,
|
||||
"wrong answer empty file\n"));
|
||||
} else {
|
||||
PointInfo po = PointInfo(i, 100, -1, -1,
|
||||
"default",
|
||||
file_preview(tpc.input_file_name), usrout,
|
||||
"ok nonempty file\n");
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
add_point_info(po);
|
||||
}
|
||||
} else {
|
||||
PointInfo po = test_point("answer", i);
|
||||
tpc.checker = "nonempty";
|
||||
return submit_answer_test_point_with_auto_generation(i, tpc);
|
||||
});
|
||||
} else {
|
||||
main_data_test([](int i) {
|
||||
PointInfo po = submit_answer_test_point_with_auto_generation(i);
|
||||
if (po.scr != 0) {
|
||||
po.info = "Accepted";
|
||||
po.scr = 100;
|
||||
}
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
po.res = "no comment";
|
||||
add_point_info(po);
|
||||
}
|
||||
return po;
|
||||
});
|
||||
}
|
||||
end_judge_ok();
|
||||
} else {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
int n = conf_int("n_sample_tests", 0);
|
||||
bool passed = true;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Sample Test #%d", i);
|
||||
PointInfo po = test_point("answer", -i);
|
||||
po.num = i;
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, 100 / n);
|
||||
add_point_info(po);
|
||||
}
|
||||
if (passed) {
|
||||
tot_score = 100;
|
||||
}
|
||||
sample_data_test([](int i) {
|
||||
return test_point("answer", i);
|
||||
});
|
||||
end_judge_ok();
|
||||
}
|
||||
}
|
||||
|
||||
void custom_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
end_judge_judgement_failed("Custom test is not supported in this problem.");
|
||||
} else {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
report_judge_status_f("Judging");
|
||||
add_custom_test_info(ordinary_custom_test("answer"));
|
||||
|
||||
end_judge_ok();
|
||||
report_judge_status_f("Compiling");
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
CustomTestConfig ctc;
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
ctc.base_limit = conf_run_limit("gen", 0, RL_GENERATOR_DEFAULT);
|
||||
}
|
||||
|
||||
report_judge_status_f("Judging");
|
||||
add_custom_test_info(ordinary_custom_test("answer", ctc));
|
||||
|
||||
end_judge_ok();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
@ -1,38 +0,0 @@
|
||||
#include "uoj_work_path.h"
|
||||
|
||||
#define UOJ_DATA_PATH UOJ_WORK_PATH "/data"
|
||||
#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result"
|
||||
|
||||
#define RS_SPJ_BASE 1000
|
||||
#define failed_spj RS_SPJ_BASE
|
||||
#define successed_hack 100000
|
||||
#define RS_SPJ RS_SPJ_BASE
|
||||
#define RS_HACK successed_hack
|
||||
#define RS_AC 0
|
||||
#define RS_WA 1
|
||||
#define RS_RE 2
|
||||
#define RS_MLE 3
|
||||
#define RS_TLE 4
|
||||
#define RS_OLE 5
|
||||
#define RS_DGS 6
|
||||
#define RS_JGF 7
|
||||
#define RS_SPJ_AC (RS_SPJ + RS_AC)
|
||||
#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE)
|
||||
#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE)
|
||||
#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE)
|
||||
#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE)
|
||||
#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS)
|
||||
#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF)
|
||||
#define RS_HK_RE (successed_hack + RS_RE)
|
||||
#define RS_HK_MLE (successed_hack + RS_MLE)
|
||||
#define RS_HK_TLE (successed_hack + RS_TLE)
|
||||
#define RS_HK_OLE (successed_hack + RS_OLE)
|
||||
#define RS_HK_DGS (successed_hack + RS_DGS)
|
||||
#define RS_HK_JGF (successed_hack + RS_JGF)
|
||||
#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE)
|
||||
#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE)
|
||||
#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE)
|
||||
#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE)
|
||||
#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS)
|
||||
#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF)
|
||||
|
File diff suppressed because it is too large
Load Diff
2478
judger/uoj_judger/include/uoj_judger_v2.h
Normal file
2478
judger/uoj_judger/include/uoj_judger_v2.h
Normal file
File diff suppressed because it is too large
Load Diff
439
judger/uoj_judger/include/uoj_run.h
Normal file
439
judger/uoj_judger/include/uoj_run.h
Normal file
@ -0,0 +1,439 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <cstdarg>
|
||||
#include <filesystem>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#define UOJ_GCC "/usr/bin/gcc-11"
|
||||
#define UOJ_GPLUSPLUS "/usr/bin/g++-11"
|
||||
#define UOJ_PYTHON2_7 "/usr/bin/python2.7"
|
||||
#define UOJ_PYTHON3 "/usr/bin/python3.10"
|
||||
#define UOJ_FPC "/usr/bin/fpc"
|
||||
#define UOJ_OPEN_JDK8 "/usr/lib/jvm/java-8-openjdk-amd64"
|
||||
#define UOJ_OPEN_JDK11 "/usr/lib/jvm/java-11-openjdk-amd64"
|
||||
#define UOJ_OPEN_JDK17 "/usr/lib/jvm/java-17-openjdk-amd64"
|
||||
|
||||
std::string escapeshellarg(int arg) {
|
||||
return std::to_string(arg);
|
||||
}
|
||||
std::string escapeshellarg(const std::string &arg) {
|
||||
std::string res = "'";
|
||||
for (char c : arg) {
|
||||
if (c == '\'') {
|
||||
res += "'\\''";
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
res += "'";
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& spaced_out(std::ostream &out, const T &arg) {
|
||||
return out << arg;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::ostream& spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
|
||||
return spaced_out(out << arg << " ", rest...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& add_spaced_out(std::ostream &out, const T &arg) {
|
||||
return out << " " << arg;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::ostream& add_spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
|
||||
return spaced_out(out << " " << arg, rest...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
int execute(const Args& ...args) {
|
||||
std::ostringstream sout;
|
||||
spaced_out(sout, args...);
|
||||
#ifdef UOJ_SHOW_EVERY_CMD
|
||||
std::cerr << sout.str() << std::endl;
|
||||
#endif
|
||||
int status = system(sout.str().c_str());
|
||||
if (status == -1 || !WIFEXITED(status)) {
|
||||
return -1;
|
||||
}
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int executef(const char *fmt, ...) {
|
||||
const int L = 1 << 10;
|
||||
char cmd[L];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int res = vsnprintf(cmd, L, fmt, ap);
|
||||
if (res < 0 || res >= L) {
|
||||
return -1;
|
||||
}
|
||||
res = execute(cmd);
|
||||
va_end(ap);
|
||||
return res;
|
||||
}
|
||||
|
||||
class cannot_determine_class_name_error : std::invalid_argument {
|
||||
public:
|
||||
explicit cannot_determine_class_name_error()
|
||||
: std::invalid_argument("cannot determine the class name!") {}
|
||||
};
|
||||
|
||||
std::string get_class_name_from_file(const std::string &fname) {
|
||||
std::ifstream fin(fname);
|
||||
if (!fin) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
std::string class_name;
|
||||
if (!(fin >> class_name)) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
if (class_name.length() > 100) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
for (char &c : class_name) {
|
||||
if (!isalnum(c) && c != '_') {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
bool put_class_name_to_file(const std::string &fname, const std::string &class_name) {
|
||||
std::ofstream fout(fname);
|
||||
if (!fout) {
|
||||
return false;
|
||||
}
|
||||
if (!(fout << class_name << std::endl)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> lang_upgrade_map = {
|
||||
{"Java7" , "Java8" },
|
||||
{"Java14", "Java17" },
|
||||
{"Python2", "Python2.7" },
|
||||
};
|
||||
|
||||
std::string upgraded_lang(const std::string &lang) {
|
||||
return lang_upgrade_map.count(lang) ? lang_upgrade_map[lang] : lang;
|
||||
}
|
||||
|
||||
namespace runp {
|
||||
namespace fs = std::filesystem;
|
||||
fs::path run_path;
|
||||
|
||||
struct limits_t {
|
||||
int time;
|
||||
int memory;
|
||||
int output;
|
||||
int real_time;
|
||||
int stack;
|
||||
|
||||
limits_t() = default;
|
||||
limits_t(const int &_time, const int &_memory, const int &_output)
|
||||
: time(_time), memory(_memory), output(_output), real_time(-1), stack(-1) {
|
||||
}
|
||||
};
|
||||
|
||||
// result type
|
||||
enum RS_TYPE {
|
||||
RS_AC = 0,
|
||||
RS_WA = 1,
|
||||
RS_RE = 2,
|
||||
RS_MLE = 3,
|
||||
RS_TLE = 4,
|
||||
RS_OLE = 5,
|
||||
RS_DGS = 6,
|
||||
RS_JGF = 7
|
||||
};
|
||||
|
||||
inline std::string rstype_str(RS_TYPE id) {
|
||||
switch (id) {
|
||||
case RS_AC: return "Accepted";
|
||||
case RS_WA: return "Wrong Answer";
|
||||
case RS_RE : return "Runtime Error";
|
||||
case RS_MLE: return "Memory Limit Exceeded";
|
||||
case RS_TLE: return "Time Limit Exceeded";
|
||||
case RS_OLE: return "Output Limit Exceeded";
|
||||
case RS_DGS: return "Dangerous Syscalls";
|
||||
case RS_JGF: return "Judgment Failed";
|
||||
default : return "Unknown Result";
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_type_from_lang(std::string lang) {
|
||||
lang = upgraded_lang(lang);
|
||||
if (lang == "Python2.7") {
|
||||
return "python2.7";
|
||||
} else if (lang == "Python3") {
|
||||
return "python3";
|
||||
} else if (lang == "Java8") {
|
||||
return "java8";
|
||||
} else if (lang == "Java11") {
|
||||
return "java11";
|
||||
} else if (lang == "Java17") {
|
||||
return "java17";
|
||||
} else {
|
||||
return "default";
|
||||
}
|
||||
}
|
||||
|
||||
struct result {
|
||||
static std::string result_file_name;
|
||||
|
||||
RS_TYPE type;
|
||||
std::string extra;
|
||||
int ust, usm;
|
||||
int exit_code;
|
||||
|
||||
result() = default;
|
||||
result(RS_TYPE type, std::string extra, int ust = -1, int usm = -1, int exit_code = -1)
|
||||
: type(type), extra(extra), ust(ust), usm(usm), exit_code(exit_code) {
|
||||
if (this->type != RS_AC) {
|
||||
this->ust = -1, this->usm = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static result failed_result() {
|
||||
result res;
|
||||
res.type = RS_JGF;
|
||||
res.ust = -1;
|
||||
res.usm = -1;
|
||||
return res;
|
||||
}
|
||||
|
||||
static result from_file(const std::string &file_name) {
|
||||
result res;
|
||||
FILE *fres = fopen(file_name.c_str(), "r");
|
||||
if (!fres) {
|
||||
return result::failed_result();
|
||||
}
|
||||
int type;
|
||||
if (fscanf(fres, "%d %d %d %d\n", &type, &res.ust, &res.usm, &res.exit_code) != 4) {
|
||||
fclose(fres);
|
||||
return result::failed_result();
|
||||
}
|
||||
res.type = (RS_TYPE)type;
|
||||
|
||||
int L = 1 << 15;
|
||||
char buf[L];
|
||||
while (!feof(fres)) {
|
||||
int c = fread(buf, 1, L, fres);
|
||||
res.extra.append(buf, c);
|
||||
if (ferror(fres)) {
|
||||
fclose(fres);
|
||||
return result::failed_result();
|
||||
}
|
||||
}
|
||||
fclose(fres);
|
||||
return res;
|
||||
}
|
||||
|
||||
[[noreturn]] void dump_and_exit() {
|
||||
FILE *f;
|
||||
if (result_file_name == "stdout") {
|
||||
f = stdout;
|
||||
} else if (result_file_name == "stderr") {
|
||||
f = stderr;
|
||||
} else {
|
||||
f = fopen(result_file_name.c_str(), "w");
|
||||
}
|
||||
fprintf(f, "%d %d %d %d\n", this->type, this->ust, this->usm, this->exit_code);
|
||||
fprintf(f, "%s\n", this->extra.c_str());
|
||||
if (f != stdout && f != stderr) {
|
||||
fclose(f);
|
||||
}
|
||||
exit(this->type == RS_JGF ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
std::string result::result_file_name("stdout");
|
||||
|
||||
template <typename T1, typename T2>
|
||||
inline std::ostream& add_runp_arg(std::ostream &out, const std::pair<T1, std::vector<T2>> &arg) {
|
||||
for (const auto &t : arg.second) {
|
||||
out << " --" << arg.first << "=" << escapeshellarg(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
template <typename T1, typename T2>
|
||||
inline std::ostream& add_runp_arg(std::ostream &out, const std::pair<T1, T2> &arg) {
|
||||
return out << " --" << arg.first << "=" << escapeshellarg(arg.second);
|
||||
}
|
||||
inline std::ostream& add_runp_arg(std::ostream &out, const std::vector<std::string> &arg) {
|
||||
for (const auto &t : arg) {
|
||||
out << " " << escapeshellarg(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
inline std::ostream& add_runp_arg(std::ostream &out, const std::string &arg) {
|
||||
return out << " " << escapeshellarg(arg);
|
||||
}
|
||||
|
||||
struct config {
|
||||
std::vector<std::string> readable_file_names; // other than stdin
|
||||
std::vector<std::string> writable_file_names; // other than stdout, stderr
|
||||
std::string result_file_name;
|
||||
std::string input_file_name;
|
||||
std::string output_file_name;
|
||||
std::string error_file_name = "/dev/null";
|
||||
std::string type = "default";
|
||||
std::string work_path;
|
||||
limits_t limits;
|
||||
std::string program_name;
|
||||
std::vector<std::string> rest_args;
|
||||
|
||||
// full args (possbily with interpreter)
|
||||
std::vector<std::string> full_args;
|
||||
|
||||
bool unsafe = false;
|
||||
bool allow_proc = false;
|
||||
bool need_show_trace_details = false;
|
||||
|
||||
config(std::string program_name = "", const std::vector<std::string> &rest_args = {})
|
||||
: program_name(program_name), rest_args(rest_args) {
|
||||
}
|
||||
|
||||
config &set_type(const std::string &type) {
|
||||
this->type = type;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string get_cmd() const {
|
||||
std::ostringstream sout;
|
||||
sout << escapeshellarg(run_path / "run_program");
|
||||
|
||||
if (this->need_show_trace_details) {
|
||||
add_runp_arg(sout, "--show-trace-details");
|
||||
}
|
||||
|
||||
add_runp_arg(sout, std::make_pair("res", this->result_file_name));
|
||||
add_runp_arg(sout, std::make_pair("in", this->input_file_name));
|
||||
add_runp_arg(sout, std::make_pair("out", this->output_file_name));
|
||||
add_runp_arg(sout, std::make_pair("err", this->error_file_name));
|
||||
add_runp_arg(sout, std::make_pair("type", this->type));
|
||||
|
||||
// limits
|
||||
add_runp_arg(sout, std::make_pair("tl", this->limits.time));
|
||||
add_runp_arg(sout, std::make_pair("ml", this->limits.memory));
|
||||
add_runp_arg(sout, std::make_pair("ol", this->limits.output));
|
||||
if (this->limits.real_time != -1) {
|
||||
add_runp_arg(sout, std::make_pair("rtl", this->limits.real_time));
|
||||
}
|
||||
if (this->limits.stack != -1) {
|
||||
add_runp_arg(sout, std::make_pair("sl", this->limits.stack));
|
||||
}
|
||||
|
||||
if (this->unsafe) {
|
||||
add_runp_arg(sout, "--unsafe");
|
||||
}
|
||||
if (this->allow_proc) {
|
||||
add_runp_arg(sout, "--allow-proc");
|
||||
}
|
||||
|
||||
if (!this->work_path.empty()) {
|
||||
add_runp_arg(sout, std::make_pair("work-path", this->work_path));
|
||||
}
|
||||
|
||||
add_runp_arg(sout, std::make_pair("add-readable", this->readable_file_names));
|
||||
add_runp_arg(sout, std::make_pair("add-writable", this->writable_file_names));
|
||||
|
||||
add_runp_arg(sout, this->program_name);
|
||||
add_runp_arg(sout, this->rest_args);
|
||||
|
||||
return sout.str();
|
||||
}
|
||||
|
||||
void gen_full_args() {
|
||||
// assume that current_path() == work_path
|
||||
|
||||
full_args.clear();
|
||||
full_args.push_back(program_name);
|
||||
full_args.insert(full_args.end(), rest_args.begin(),rest_args.end());
|
||||
|
||||
if (type == "java8" || type == "java11" || type == "java17") {
|
||||
full_args[0] = get_class_name_from_file(fs::path(full_args[0]) / ".main_class_name");
|
||||
|
||||
std::string jdk;
|
||||
if (type == "java8") {
|
||||
jdk = UOJ_OPEN_JDK8;
|
||||
} else if (type == "java11") {
|
||||
jdk = UOJ_OPEN_JDK11;
|
||||
} else { // if (type == "java17") {
|
||||
jdk = UOJ_OPEN_JDK17;
|
||||
}
|
||||
full_args.insert(full_args.begin(), {
|
||||
fs::canonical(fs::path(jdk) / "bin" / "java"), "-Xmx2048m", "-Xss1024m",
|
||||
"-XX:ActiveProcessorCount=1",
|
||||
"-classpath", program_name
|
||||
});
|
||||
} else if (type == "python2.7") {
|
||||
full_args.insert(full_args.begin(), {
|
||||
UOJ_PYTHON2_7, "-E", "-s", "-B"
|
||||
});
|
||||
} else if (type == "python3") {
|
||||
full_args.insert(full_args.begin(), {
|
||||
UOJ_PYTHON3, "-I", "-B"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace runp::interaction {
|
||||
struct pipe_config {
|
||||
int from, from_fd;
|
||||
int to, to_fd;
|
||||
std::string saving_file_name;
|
||||
|
||||
pipe_config() = default;
|
||||
pipe_config(int _from, int _from_fd, int _to, int _to_fd, const std::string &_saving_file_name = "")
|
||||
: from(_from), from_fd(_from_fd), to(_to), to_fd(_to_fd), saving_file_name(_saving_file_name) {}
|
||||
pipe_config(const std::string &str) {
|
||||
if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) {
|
||||
throw std::invalid_argument("bad init str for pipe");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct config {
|
||||
std::vector<std::string> cmds;
|
||||
std::vector<pipe_config> pipes;
|
||||
|
||||
std::string get_cmd() const {
|
||||
std::ostringstream sout;
|
||||
sout << escapeshellarg(run_path / "run_interaction");
|
||||
for (auto &cmd : cmds) {
|
||||
sout << " " << escapeshellarg(cmd);
|
||||
}
|
||||
for (auto &pipe : pipes) {
|
||||
sout << " " << "-p";
|
||||
sout << " " << pipe.from << ":" << pipe.from_fd;
|
||||
sout << "-" << pipe.to << ":" << pipe.to_fd;
|
||||
|
||||
if (!pipe.saving_file_name.empty()) {
|
||||
sout << " " << "-s";
|
||||
sout << " " << escapeshellarg(pipe.saving_file_name);
|
||||
}
|
||||
}
|
||||
return sout.str();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* @return interaction return value
|
||||
**/
|
||||
int run(const config &ric) {
|
||||
return execute(ric.get_cmd().c_str());
|
||||
}
|
||||
}
|
332
judger/uoj_judger/include/uoj_secure.h
Normal file
332
judger/uoj_judger/include/uoj_secure.h
Normal file
@ -0,0 +1,332 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define UOJ_NORETURN __declspec(noreturn)
|
||||
#elif defined __GNUC__
|
||||
# define UOJ_NORETURN __attribute__ ((noreturn))
|
||||
#else
|
||||
# define UOJ_NORETURN
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned u32;
|
||||
typedef unsigned long long u64;
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct sha256_t {
|
||||
u8 sum[32];
|
||||
|
||||
string to_str() {
|
||||
return string((char*)sum, 32);
|
||||
}
|
||||
};
|
||||
|
||||
inline u32 uoj_sha2_rotr(u32 x, int n) {
|
||||
return x >> n | x << (32 - n);
|
||||
}
|
||||
|
||||
void uoj_sha256_chunk(u8 *chunk, u32 *hs) {
|
||||
static const u32 k[] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
u32 w[64];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
w[i] = ((u32) chunk[i << 2 | 3])
|
||||
| ((u32) chunk[i << 2 | 2] << 8)
|
||||
| ((u32) chunk[i << 2 | 1] << 16)
|
||||
| ((u32) chunk[i << 2 ] << 24);
|
||||
}
|
||||
|
||||
for (int i = 16; i < 64; i++) {
|
||||
u32 s0 = uoj_sha2_rotr(w[i - 15], 7) ^ uoj_sha2_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
|
||||
u32 s1 = uoj_sha2_rotr(w[i - 2], 17) ^ uoj_sha2_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
||||
}
|
||||
|
||||
u32 a = hs[0], b = hs[1], c = hs[2], d = hs[3], e = hs[4], f = hs[5], g = hs[6], h = hs[7];
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
u32 s1 = uoj_sha2_rotr(e, 6) ^ uoj_sha2_rotr(e, 11) ^ uoj_sha2_rotr(e, 25);
|
||||
u32 ch = (e & f) ^ (~e & g);
|
||||
u32 temp1 = h + s1 + ch + k[i] + w[i];
|
||||
u32 s0 = uoj_sha2_rotr(a, 2) ^ uoj_sha2_rotr(a, 13) ^ uoj_sha2_rotr(a, 22);
|
||||
u32 maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
u32 temp2 = s0 + maj;
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + temp1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1 + temp2;
|
||||
}
|
||||
|
||||
hs[0] += a, hs[1] += b, hs[2] += c, hs[3] += d, hs[4] += e, hs[5] += f, hs[6] += g, hs[7] += h;
|
||||
}
|
||||
|
||||
sha256_t uoj_sha256(int n, u8 *m) {
|
||||
u32 hs[] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
|
||||
|
||||
u64 len = n * 8;
|
||||
|
||||
int r_n = 0;
|
||||
u8 r[128];
|
||||
for (int i = 0; i < n; i += 64) {
|
||||
if (i + 64 <= n) {
|
||||
uoj_sha256_chunk(m + i, hs);
|
||||
} else {
|
||||
for (int j = i; j < n; j++) {
|
||||
r[r_n++] = m[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r[r_n++] = 0x80;
|
||||
while ((r_n + 8) % 64 != 0) {
|
||||
r[r_n++] = 0;
|
||||
}
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
r[r_n++] = len >> (64 - i * 8);
|
||||
}
|
||||
|
||||
for (int i = 0; i < r_n; i += 64) {
|
||||
uoj_sha256_chunk(r + i, hs);
|
||||
}
|
||||
|
||||
sha256_t sum;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
sum.sum[i << 2 | j] = hs[i] >> (32 - (j + 1) * 8);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
sha256_t uoj_sha256(const string &m) {
|
||||
return uoj_sha256((int)m.length(), (u8*)m.data());
|
||||
}
|
||||
|
||||
sha256_t uoj_hmac(const string &k, const string &m) {
|
||||
string ki = k, ko = k;
|
||||
for (int i = 0; i < (int)k.length(); i++) {
|
||||
ki[i] ^= 0x36;
|
||||
ko[i] ^= 0x5c;
|
||||
}
|
||||
return uoj_sha256(ko + uoj_sha256(ki + m).to_str());
|
||||
}
|
||||
|
||||
class uoj_mt_rand_engine {
|
||||
static const int N = 312;
|
||||
static const int M = 156;
|
||||
static const int R = 31;
|
||||
static const u64 LM = (1llu << R) - 1;
|
||||
static const u64 UM = ~LM;
|
||||
static const u64 F = 6364136223846793005llu;
|
||||
|
||||
u64 mt[N];
|
||||
int index;
|
||||
|
||||
void init(u64 seed) {
|
||||
index = N;
|
||||
mt[0] = seed;
|
||||
for (int i = 1; i < N; i++) {
|
||||
mt[i] = F * (mt[i - 1] ^ (mt[i - 1] >> 62)) + i;
|
||||
}
|
||||
}
|
||||
|
||||
void twist() {
|
||||
for (int i = 0; i < N; i++) {
|
||||
u64 x = (mt[i] & UM) + (mt[(i + 1) % N] & LM);
|
||||
u64 xA = x >> 1;
|
||||
if (x & 1) {
|
||||
xA ^= 0xb5026f5aa96619e9llu;
|
||||
}
|
||||
mt[i] = mt[(i + M) % N] ^ xA;
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
uoj_mt_rand_engine(u64 seed) {
|
||||
init(seed);
|
||||
}
|
||||
uoj_mt_rand_engine(const string &s) {
|
||||
sha256_t sum = uoj_sha256(s);
|
||||
|
||||
u64 seed = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
seed = seed << 8 | sum.sum[i];
|
||||
|
||||
init(seed);
|
||||
}
|
||||
|
||||
u64 next() {
|
||||
if (index >= N) {
|
||||
twist();
|
||||
}
|
||||
|
||||
u64 y = mt[index];
|
||||
y ^= (y >> 29) & 0x5555555555555555llu;
|
||||
y ^= (y << 17) & 0x71d67fffeda60000llu;
|
||||
y ^= (y << 37) & 0xfff7eee000000000llu;
|
||||
y ^= y >> 43;
|
||||
|
||||
index++;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
string randstr(int n, string charset="0123456789abcdefghijklmnopqrstuvwxyz") {
|
||||
string s;
|
||||
for (int i = 0; i < n; i++) {
|
||||
s += charset[next() % charset.length()];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
class uoj_cipher {
|
||||
string key;
|
||||
|
||||
public:
|
||||
uoj_cipher() {}
|
||||
uoj_cipher(const string &_key) : key(_key) {}
|
||||
|
||||
void set_key(const string &_key) {
|
||||
key = _key;
|
||||
}
|
||||
|
||||
void encrypt(string &m) {
|
||||
uoj_mt_rand_engine rnd(key);
|
||||
|
||||
string hmac = uoj_hmac(key, m).to_str();
|
||||
|
||||
m.push_back(0x80);
|
||||
while ((m.length() + 32) % 512 != 0) {
|
||||
m.push_back(0x00);
|
||||
}
|
||||
|
||||
m += hmac;
|
||||
for (int i = 0; i < (int)m.length(); i += 8) {
|
||||
u64 r = rnd.next();
|
||||
for (int j = i; j < i + 4; j++) {
|
||||
m[j] = (u8)m[j] ^ (u8)r;
|
||||
r >>= 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool decrypt(string &m) {
|
||||
uoj_mt_rand_engine rnd(key);
|
||||
|
||||
if (m.empty() || m.length() % 512 != 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < (int)m.length(); i += 8) {
|
||||
u64 r = rnd.next();
|
||||
for (int j = i; j < i + 4; j++) {
|
||||
m[j] = (u8)m[j] ^ (u8)r;
|
||||
r >>= 16;
|
||||
}
|
||||
}
|
||||
string hmac = m.substr(m.length() - 32);
|
||||
int len = m.length() - 33;
|
||||
while (len >= 0 && (u8)m[len] != 0x80) {
|
||||
len--;
|
||||
}
|
||||
if (len < 0) {
|
||||
return false;
|
||||
}
|
||||
m.resize(len);
|
||||
if (uoj_hmac(key, m).to_str() != hmac) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class uoj_secure_io {
|
||||
FILE fake_f, true_outf;
|
||||
string input_m;
|
||||
|
||||
string key;
|
||||
uoj_cipher cipher;
|
||||
|
||||
public:
|
||||
istringstream in;
|
||||
ostringstream out;
|
||||
|
||||
uoj_secure_io() {
|
||||
srand(time(NULL));
|
||||
|
||||
const int BUFFER_SIZE = 1024;
|
||||
u8 buffer[BUFFER_SIZE + 1];
|
||||
while (!feof(stdin)) {
|
||||
int ret = fread(buffer, 1, BUFFER_SIZE, stdin);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
input_m.append((char *)buffer, ret);
|
||||
}
|
||||
fclose(stdin);
|
||||
|
||||
for (int i = 0; i < (int)sizeof(fake_f); i++)
|
||||
((u8*)&fake_f)[i] = rand();
|
||||
|
||||
memcpy(&true_outf, stdout, sizeof(FILE));
|
||||
memcpy(stdout, &fake_f, sizeof(FILE));
|
||||
}
|
||||
|
||||
void init_with_key(const string &_key) {
|
||||
cerr.tie(NULL);
|
||||
|
||||
key = _key;
|
||||
cipher.set_key(key);
|
||||
|
||||
if (!cipher.decrypt(input_m)) {
|
||||
end("Unauthorized input");
|
||||
}
|
||||
|
||||
in.str(input_m);
|
||||
}
|
||||
|
||||
string input() {
|
||||
return input_m;
|
||||
}
|
||||
|
||||
UOJ_NORETURN void end(string m) {
|
||||
memcpy(stdout, &true_outf, sizeof(FILE));
|
||||
|
||||
if (!out.str().empty()) {
|
||||
if (m.empty()) {
|
||||
m = out.str();
|
||||
} else {
|
||||
m = out.str() + m;
|
||||
}
|
||||
}
|
||||
|
||||
cipher.encrypt(m);
|
||||
fwrite(m.data(), 1, m.length(), stdout);
|
||||
|
||||
fclose(stdout);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
UOJ_NORETURN void end() {
|
||||
end("");
|
||||
}
|
||||
};
|
||||
}
|
@ -1,22 +1,20 @@
|
||||
#include "uoj_judger.h"
|
||||
#include "uoj_judger_v2.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
main_judger_init(argc, argv);
|
||||
RunResult res = run_program(
|
||||
(result_path + "/run_judger_result.txt").c_str(),
|
||||
"/dev/null",
|
||||
"/dev/null",
|
||||
"stderr",
|
||||
conf_run_limit("judger", 0, RL_JUDGER_DEFAULT),
|
||||
"--unsafe",
|
||||
conf_str("judger").c_str(),
|
||||
main_path.c_str(),
|
||||
work_path.c_str(),
|
||||
result_path.c_str(),
|
||||
data_path.c_str(),
|
||||
NULL);
|
||||
if (res.type != RS_AC) {
|
||||
end_judge_judgement_failed("Judgement Failed : Judger " + info_str(res));
|
||||
|
||||
runp::config rpc(conf_str("judger"), {
|
||||
main_path, work_path, result_path, data_path
|
||||
});
|
||||
rpc.result_file_name = result_path / "run_judger_result.txt";
|
||||
rpc.input_file_name = "/dev/null";
|
||||
rpc.output_file_name = "/dev/null";
|
||||
rpc.error_file_name = "stderr";
|
||||
rpc.limits = conf_run_limit("judger", 0, RL_JUDGER_DEFAULT);
|
||||
rpc.unsafe = true;
|
||||
runp::result res = runp::run(rpc);
|
||||
if (res.type != runp::RS_AC) {
|
||||
end_judge_judgment_failed("Judgment Failed : Judger " + runp::rstype_str(res.type));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
493
judger/uoj_judger/run/compile.cpp
Normal file
493
judger/uoj_judger/run/compile.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <stdexcept>
|
||||
#include <argp.h>
|
||||
|
||||
#include "uoj_run.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class language_not_supported_error : public std::invalid_argument {
|
||||
public:
|
||||
explicit language_not_supported_error()
|
||||
: std::invalid_argument("This language has not been supported yet.") {}
|
||||
};
|
||||
|
||||
class fail_to_read_src_error : public std::runtime_error {
|
||||
public:
|
||||
explicit fail_to_read_src_error(const std::string &what = "An error occurs when trying to read the source code.")
|
||||
: std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
class compile_error : public std::invalid_argument {
|
||||
public:
|
||||
explicit compile_error(const std::string &what)
|
||||
: std::invalid_argument(what) {}
|
||||
};
|
||||
|
||||
const std::vector<std::pair<const char *, const char *>> suffix_search_list = {
|
||||
{".code" , "" },
|
||||
{"20.cpp" , "C++20" },
|
||||
{"17.cpp" , "C++17" },
|
||||
{"14.cpp" , "C++14" },
|
||||
{"11.cpp" , "C++11" },
|
||||
{".cpp" , "C++" },
|
||||
{".c" , "C" },
|
||||
{".pas" , "Pascal" },
|
||||
{"2.7.py" , "Python2.7"},
|
||||
{".py" , "Python3" },
|
||||
{"7.java" , "Java7" },
|
||||
{"8.java" , "Java8" },
|
||||
{"11.java", "Java11" },
|
||||
{"14.java", "Java14" },
|
||||
{"17.java", "Java17" },
|
||||
};
|
||||
|
||||
struct compile_config {
|
||||
std::string name;
|
||||
std::string src;
|
||||
std::string lang = "auto";
|
||||
std::string opt;
|
||||
std::string implementer;
|
||||
std::string custom_compiler_path;
|
||||
std::vector<std::string> cinclude_dirs;
|
||||
|
||||
void auto_find_src() {
|
||||
if (!src.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto &p : suffix_search_list) {
|
||||
if (fs::is_regular_file(name + p.first)) {
|
||||
src = fs::canonical(name + p.first);
|
||||
if (lang == "auto") {
|
||||
lang = p.second;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
error_t compile_argp_parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
compile_config *config = (compile_config*)state->input;
|
||||
|
||||
try {
|
||||
switch (key) {
|
||||
case 's':
|
||||
config->src = arg;
|
||||
break;
|
||||
case 'i':
|
||||
config->implementer = arg;
|
||||
break;
|
||||
case 'l':
|
||||
config->lang = arg;
|
||||
break;
|
||||
case 'c':
|
||||
config->custom_compiler_path = arg;
|
||||
break;
|
||||
case 'I':
|
||||
config->cinclude_dirs.push_back(arg);
|
||||
break;
|
||||
case ARGP_KEY_ARG:
|
||||
config->name = arg;
|
||||
break;
|
||||
case ARGP_KEY_END:
|
||||
if (state->arg_num != 1) {
|
||||
argp_usage(state);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
argp_usage(state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
compile_config parse_args(int argc, char **argv) {
|
||||
argp_option argp_options[] = {
|
||||
{"src" , 's', "SOURCE_CODE" , 0, "set the path to source code" , 1},
|
||||
{"impl" , 'i', "IMPLEMENTER" , 0, "set the implementer name" , 2},
|
||||
{"lang" , 'l', "LANGUAGE" , 0, "set the language" , 3},
|
||||
{"custom" , 'c', "CUSTOM" , 0, "path to custom compilers (those are not placed in /usr/bin)", 4},
|
||||
{"cinclude", 'I', "DIRECTORY" , 0, "add the directory dir to the list of directories to be searched for header files during preprocessing (for C/C++)", 5},
|
||||
{0}
|
||||
};
|
||||
char argp_args_doc[] = "name";
|
||||
char argp_doc[] = "compile: a tool to compile programs";
|
||||
|
||||
argp compile_argp = {
|
||||
argp_options,
|
||||
compile_argp_parse_opt,
|
||||
argp_args_doc,
|
||||
argp_doc
|
||||
};
|
||||
|
||||
compile_config config;
|
||||
argp_parse(&compile_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config);
|
||||
|
||||
config.auto_find_src();
|
||||
if (config.src.empty() || !fs::is_regular_file(config.src)) {
|
||||
throw fail_to_read_src_error();
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
bool is_illegal_keyword(const std::string &name) {
|
||||
return name == "__asm" || name == "__asm__" || name == "asm";
|
||||
}
|
||||
|
||||
bool has_illegal_keywords_in_file(const std::string &src) {
|
||||
std::ifstream fin(src);
|
||||
if (!fin) {
|
||||
throw fail_to_read_src_error();
|
||||
}
|
||||
|
||||
const int L = 1 << 15;
|
||||
char buf[L];
|
||||
std::string key;
|
||||
|
||||
while (!fin.eof()) {
|
||||
fin.read(buf, L);
|
||||
int cnt = fin.gcount();
|
||||
for (char *p = buf; p != buf + cnt; p++) {
|
||||
char c = *p;
|
||||
if (isalnum(c) || c == '_') {
|
||||
if (key.size() < 20) {
|
||||
key += c;
|
||||
} else {
|
||||
if (is_illegal_keyword(key)) {
|
||||
return true;
|
||||
}
|
||||
key.erase(key.begin());
|
||||
key += c;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (is_illegal_keyword(key)) {
|
||||
return true;
|
||||
}
|
||||
key.clear();
|
||||
}
|
||||
}
|
||||
if (fin.bad()) {
|
||||
throw fail_to_read_src_error();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string get_java_main_class(const std::string &src) {
|
||||
std::ifstream fin(src);
|
||||
if (!fin) {
|
||||
throw fail_to_read_src_error();
|
||||
}
|
||||
|
||||
const int L = 1 << 15;
|
||||
char buf[L];
|
||||
std::string s;
|
||||
|
||||
int mode = 0;
|
||||
|
||||
while (!fin.eof()) {
|
||||
fin.read(buf, L);
|
||||
int cnt = fin.gcount();
|
||||
for (char *p = buf; p != buf + cnt; p++) {
|
||||
s += *p;
|
||||
switch (mode) {
|
||||
case 0:
|
||||
switch (*p) {
|
||||
case '/':
|
||||
mode = 1;
|
||||
break;
|
||||
case '\'':
|
||||
mode = 5;
|
||||
break;
|
||||
case '\"':
|
||||
mode = 6;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (*p) {
|
||||
case '/':
|
||||
mode = 2;
|
||||
s.pop_back();
|
||||
s.pop_back();
|
||||
break;
|
||||
case '*':
|
||||
mode = 3;
|
||||
s.pop_back();
|
||||
s.pop_back();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
s.pop_back();
|
||||
switch (*p) {
|
||||
case '\n':
|
||||
s += '\n';
|
||||
mode = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
s.pop_back();
|
||||
switch (*p) {
|
||||
case '*':
|
||||
mode = 4;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
s.pop_back();
|
||||
switch (*p) {
|
||||
case '/':
|
||||
s += ' ';
|
||||
mode = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
switch (*p) {
|
||||
case '\'':
|
||||
mode = 0;
|
||||
break;
|
||||
case '\\':
|
||||
mode = 7;
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
switch (*p) {
|
||||
case '\"':
|
||||
mode = 0;
|
||||
break;
|
||||
case '\\':
|
||||
mode = 8;
|
||||
break;
|
||||
}
|
||||
case 7:
|
||||
mode = 5;
|
||||
break;
|
||||
case 8:
|
||||
mode = 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fin.bad()) {
|
||||
throw fail_to_read_src_error();
|
||||
}
|
||||
}
|
||||
|
||||
bool valid[256];
|
||||
std::fill(valid, valid + 256, false);
|
||||
std::fill(valid + 'a', valid + 'z' + 1, true);
|
||||
std::fill(valid + 'A', valid + 'Z' + 1, true);
|
||||
std::fill(valid + '0', valid + '9' + 1, true);
|
||||
valid['.'] = true;
|
||||
valid['_'] = true;
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
for (size_t p = 0, np = 0; p < s.length(); p = np) {
|
||||
while (np < s.length() && valid[(unsigned char)s[np]]) {
|
||||
np++;
|
||||
}
|
||||
if (np == p) {
|
||||
np++;
|
||||
} else {
|
||||
tokens.push_back(s.substr(p, np - p));
|
||||
}
|
||||
}
|
||||
if (tokens.size() > 0 && tokens[0] == "package") {
|
||||
throw compile_error("Please don't specify the package.");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 1 < tokens.size(); i++) {
|
||||
if (tokens[i] == "class") {
|
||||
std::string name = tokens[i + 1];
|
||||
if (name.length() > 100) {
|
||||
throw compile_error("The name of the main class is too long.");
|
||||
}
|
||||
for (size_t k = 0; k < name.length(); k++) {
|
||||
if (!isalnum(name[k]) && name[k] != '_') {
|
||||
throw compile_error("The name of the main class should only contain letters, numbers and underscore sign.");
|
||||
}
|
||||
}
|
||||
if (!isalpha(name[0])) {
|
||||
throw compile_error("The name of the main class cannot begin with a number.");
|
||||
}
|
||||
return tokens[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
throw compile_error("Cannot find the main class.");
|
||||
}
|
||||
|
||||
int compile_cpp(const compile_config &conf, const std::string &std) {
|
||||
std::ostringstream sflags;
|
||||
spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE", "-std=" + std);
|
||||
for (auto dir : conf.cinclude_dirs) {
|
||||
add_spaced_out(sflags, "-I" + dir);
|
||||
}
|
||||
|
||||
if (conf.implementer.empty()) {
|
||||
return execute(
|
||||
UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name,
|
||||
"-x", "c++", conf.src
|
||||
);
|
||||
} else {
|
||||
return execute(
|
||||
UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name,
|
||||
conf.implementer + ".cpp", "-x", "c++", conf.src
|
||||
);
|
||||
}
|
||||
}
|
||||
int compile_c(const compile_config &conf) {
|
||||
std::ostringstream sflags;
|
||||
spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE");
|
||||
for (auto dir : conf.cinclude_dirs) {
|
||||
add_spaced_out(sflags, "-I" + dir);
|
||||
}
|
||||
|
||||
if (conf.implementer.empty()) {
|
||||
return execute(
|
||||
UOJ_GCC, sflags.str(), "-o", conf.name,
|
||||
"-x", "c", conf.src
|
||||
);
|
||||
} else {
|
||||
return execute(
|
||||
UOJ_GCC, sflags.str(), "-o", conf.name,
|
||||
conf.implementer + ".c", "-x", "c", conf.src
|
||||
);
|
||||
}
|
||||
}
|
||||
int compile_python2_7(const compile_config &conf) {
|
||||
if (!conf.implementer.empty()) {
|
||||
throw language_not_supported_error();
|
||||
}
|
||||
|
||||
std::string dfile = "__pycode__/" + conf.name + ".py";
|
||||
std::string compiler_code =
|
||||
"import py_compile\n"
|
||||
"import sys\n"
|
||||
"try:\n"
|
||||
" py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n"
|
||||
" sys.exit(0)\n"
|
||||
"except Exception as e:\n"
|
||||
" print e\n"
|
||||
" sys.exit(1)\n";
|
||||
return execute(UOJ_PYTHON2_7, "-E", "-s", "-B", "-O", "-c", escapeshellarg(compiler_code));
|
||||
}
|
||||
int compile_python3(const compile_config &conf) {
|
||||
if (!conf.implementer.empty()) {
|
||||
throw language_not_supported_error();
|
||||
}
|
||||
std::string dfile = "__pycode__/" + conf.name + ".py";
|
||||
std::string compiler_code =
|
||||
"import py_compile\n"
|
||||
"import sys\n"
|
||||
"try:\n"
|
||||
" py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n"
|
||||
" sys.exit(0)\n"
|
||||
"except Exception as e:\n"
|
||||
" print(e)\n"
|
||||
" sys.exit(1)\n";
|
||||
return execute(UOJ_PYTHON3, "-I", "-B", "-O", "-c", escapeshellarg(compiler_code));
|
||||
}
|
||||
int compile_java(const compile_config &conf, const std::string &jdk) {
|
||||
if (!conf.implementer.empty()) {
|
||||
throw language_not_supported_error();
|
||||
}
|
||||
|
||||
try {
|
||||
std::string main_class = get_java_main_class(conf.src);
|
||||
fs::remove_all(conf.name);
|
||||
fs::create_directory(conf.name);
|
||||
fs::copy_file(conf.src, fs::path(conf.name) / (main_class + ".java"));
|
||||
int ret = execute(
|
||||
"cd", conf.name,
|
||||
"&&", jdk + "/bin/javac", main_class + ".java"
|
||||
);
|
||||
fs::remove(fs::path(conf.name) / (main_class + ".java"));
|
||||
put_class_name_to_file(fs::path(conf.name) / ".main_class_name", main_class);
|
||||
return ret;
|
||||
} catch (std::system_error &e) {
|
||||
throw compile_error("System Error");
|
||||
}
|
||||
}
|
||||
int compile_pas(const compile_config &conf) {
|
||||
if (conf.implementer.empty()) {
|
||||
return execute(UOJ_FPC, conf.src, "-O2");
|
||||
} else {
|
||||
try {
|
||||
std::string unit_name = get_class_name_from_file(conf.name + ".unit_name");
|
||||
if (!unit_name.empty()) {
|
||||
fs::copy_file(conf.src, unit_name + ".pas");
|
||||
}
|
||||
int ret = execute(UOJ_FPC, conf.implementer + ".pas", "-o" + conf.name, "-O2");
|
||||
if (!unit_name.empty()) {
|
||||
fs::remove(unit_name + ".pas");
|
||||
}
|
||||
return ret;
|
||||
} catch (std::system_error &e) {
|
||||
throw compile_error("System Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int compile(const compile_config &conf) {
|
||||
if ((conf.lang.length() > 0 && conf.lang[0] == 'C') && has_illegal_keywords_in_file(conf.src)) {
|
||||
std::cerr << "Compile Failed: assembly language detected" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string lang = upgraded_lang(conf.lang);
|
||||
|
||||
if (lang == "C++" || lang == "C++14") {
|
||||
return compile_cpp(conf, "c++14");
|
||||
} else if (lang == "C++98") {
|
||||
return compile_cpp(conf, "c++98");
|
||||
} else if (lang == "C++03") {
|
||||
return compile_cpp(conf, "c++03");
|
||||
} else if (lang == "C++11") {
|
||||
return compile_cpp(conf, "c++11");
|
||||
} else if (lang == "C++17") {
|
||||
return compile_cpp(conf, "c++17");
|
||||
} else if (lang == "C++20") {
|
||||
return compile_cpp(conf, "c++20");
|
||||
} else if (lang == "C") {
|
||||
return compile_c(conf);
|
||||
} else if (lang == "Python2.7") {
|
||||
return compile_python2_7(conf);
|
||||
} else if (lang == "Python3") {
|
||||
return compile_python3(conf);
|
||||
} else if (lang == "Java8") {
|
||||
return compile_java(conf, UOJ_OPEN_JDK8);
|
||||
} else if (lang == "Java11") {
|
||||
return compile_java(conf, UOJ_OPEN_JDK11);
|
||||
} else if (lang == "Java17") {
|
||||
return compile_java(conf, UOJ_OPEN_JDK17);
|
||||
} else if (lang == "Pascal") {
|
||||
return compile_pas(conf);
|
||||
} else {
|
||||
throw language_not_supported_error();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
try {
|
||||
return compile(parse_args(argc, argv));
|
||||
} catch (std::exception &e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -16,62 +16,38 @@
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <argp.h>
|
||||
#include "uoj_env.h"
|
||||
#include "uoj_run.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct RunResult {
|
||||
int result;
|
||||
int ust;
|
||||
int usm;
|
||||
int exit_code;
|
||||
|
||||
RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
|
||||
: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
|
||||
if (result != RS_AC) {
|
||||
ust = -1, usm = -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct PipeConfig {
|
||||
int from, to;
|
||||
int from_fd, to_fd;
|
||||
|
||||
string saving_file_name; // empty for none
|
||||
|
||||
PipeConfig() {
|
||||
}
|
||||
PipeConfig(string str) {
|
||||
if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) {
|
||||
throw invalid_argument("bad init str for PipeConfig");
|
||||
}
|
||||
from -= 1;
|
||||
to -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct RunInteractionConfig {
|
||||
vector<string> cmds;
|
||||
vector<PipeConfig> pipes;
|
||||
};
|
||||
|
||||
struct RunCmdData {
|
||||
struct run_cmd_data {
|
||||
string cmd;
|
||||
pid_t pid;
|
||||
|
||||
vector<int> ipipes, opipes;
|
||||
};
|
||||
struct PipeData {
|
||||
PipeConfig config;
|
||||
struct pipe_data {
|
||||
runp::interaction::pipe_config config;
|
||||
int ipipefd[2], opipefd[2];
|
||||
thread io_thread;
|
||||
exception_ptr eptr;
|
||||
};
|
||||
|
||||
class RunInteraction {
|
||||
void write_all_or_throw(int fd, char *buf, int n) {
|
||||
int wcnt = 0;
|
||||
while (wcnt < n) {
|
||||
int ret = write(fd, buf + wcnt, n - wcnt);
|
||||
if (ret == -1) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
wcnt += ret;
|
||||
}
|
||||
}
|
||||
|
||||
class interaction_runner {
|
||||
private:
|
||||
vector<RunCmdData> cmds;
|
||||
vector<PipeData> pipes;
|
||||
vector<run_cmd_data> cmds;
|
||||
vector<pipe_data> pipes;
|
||||
|
||||
void prepare_fd() { // me
|
||||
for (int i = 0; i < (int)pipes.size(); i++) {
|
||||
@ -80,15 +56,21 @@ private:
|
||||
}
|
||||
}
|
||||
void prepare_fd_for_cmd(int id) {
|
||||
freopen("/dev/null", "r", stdin);
|
||||
freopen("/dev/null", "w", stdout);
|
||||
freopen("/dev/null", "w", stderr);
|
||||
if (freopen("/dev/null", "r", stdin) == NULL) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
if (freopen("/dev/null", "w", stdout) == NULL) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
if (freopen("/dev/null", "w", stderr) == NULL) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)pipes.size(); i++) {
|
||||
if (pipes[i].config.from == id) {
|
||||
if (pipes[i].config.from - 1 == id) {
|
||||
dup2(pipes[i].ipipefd[1], 128 + pipes[i].ipipefd[1]);
|
||||
}
|
||||
if (pipes[i].config.to == id) {
|
||||
if (pipes[i].config.to - 1 == id) {
|
||||
dup2(pipes[i].opipefd[0], 128 + pipes[i].opipefd[0]);
|
||||
}
|
||||
close(pipes[i].ipipefd[0]);
|
||||
@ -97,11 +79,11 @@ private:
|
||||
close(pipes[i].opipefd[1]);
|
||||
}
|
||||
for (int i = 0; i < (int)pipes.size(); i++) {
|
||||
if (pipes[i].config.from == id) {
|
||||
if (pipes[i].config.from - 1 == id) {
|
||||
dup2(128 + pipes[i].ipipefd[1], pipes[i].config.from_fd);
|
||||
close(128 + pipes[i].ipipefd[1]);
|
||||
}
|
||||
if (pipes[i].config.to == id) {
|
||||
if (pipes[i].config.to - 1 == id) {
|
||||
dup2(128 + pipes[i].opipefd[0], pipes[i].config.to_fd);
|
||||
close(128 + pipes[i].opipefd[0]);
|
||||
}
|
||||
@ -109,38 +91,54 @@ private:
|
||||
}
|
||||
|
||||
void wait_pipe_io(int pipe_id) {
|
||||
FILE *sf = NULL;
|
||||
if (!pipes[pipe_id].config.saving_file_name.empty())
|
||||
FILE *sf = nullptr;
|
||||
if (!pipes[pipe_id].config.saving_file_name.empty()) {
|
||||
sf = fopen(pipes[pipe_id].config.saving_file_name.c_str(), "w");
|
||||
|
||||
}
|
||||
int ifd = pipes[pipe_id].ipipefd[0];
|
||||
int ofd = pipes[pipe_id].opipefd[1];
|
||||
int sfd = sf ? fileno(sf) : -1;
|
||||
|
||||
FILE *inf = fdopen(ifd, "r");
|
||||
FILE *ouf = fdopen(ofd, "w");
|
||||
int iflags = fcntl(ifd, F_GETFL);
|
||||
|
||||
const int L = 4096;
|
||||
char buf[L];
|
||||
|
||||
int sbuf_len = 0;
|
||||
char sbuf[L * 2];
|
||||
|
||||
try {
|
||||
pipes[pipe_id].eptr = nullptr;
|
||||
|
||||
const int L = 4096;
|
||||
char buf[L];
|
||||
|
||||
while (true) {
|
||||
int c = fgetc(inf);
|
||||
if (c == EOF) {
|
||||
if (errno) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
int cnt1 = read(ifd, buf, 1);
|
||||
if (cnt1 == -1) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
if (cnt1 == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fputc(c, ouf) == EOF) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
fflush(ouf);
|
||||
fcntl(ifd, F_SETFL, iflags | O_NONBLOCK);
|
||||
int cnt2 = read(ifd, buf + 1, L - 1);
|
||||
fcntl(ifd, F_SETFL, iflags);
|
||||
|
||||
if (fputc(c, sf) == EOF) {
|
||||
throw system_error(errno, system_category());
|
||||
if (cnt2 == -1) {
|
||||
if (errno != EAGAIN) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
cnt2 = 0;
|
||||
}
|
||||
|
||||
write_all_or_throw(ofd, buf, cnt2 + 1);
|
||||
|
||||
if (sf) {
|
||||
memcpy(sbuf + sbuf_len, buf, cnt2 + 1);
|
||||
sbuf_len += cnt2 + 1;
|
||||
if (sbuf_len > L) {
|
||||
write_all_or_throw(sfd, sbuf, sbuf_len);
|
||||
sbuf_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception &e) {
|
||||
@ -148,12 +146,19 @@ private:
|
||||
pipes[pipe_id].eptr = current_exception();
|
||||
}
|
||||
|
||||
fclose(sf);
|
||||
fclose(inf);
|
||||
fclose(ouf);
|
||||
if (sf) {
|
||||
if (sbuf_len > 0) {
|
||||
write_all_or_throw(sfd, sbuf, sbuf_len);
|
||||
sbuf_len = 0;
|
||||
}
|
||||
fclose(sf);
|
||||
}
|
||||
|
||||
close(ifd);
|
||||
close(ofd);
|
||||
}
|
||||
public:
|
||||
RunInteraction(const RunInteractionConfig &config) {
|
||||
interaction_runner(const runp::interaction::config &config) {
|
||||
cmds.resize(config.cmds.size());
|
||||
for (int i = 0; i < (int)config.cmds.size(); i++) {
|
||||
cmds[i].cmd = config.cmds[i];
|
||||
@ -162,8 +167,8 @@ public:
|
||||
pipes.resize(config.pipes.size());
|
||||
for (int i = 0; i < (int)config.pipes.size(); i++) {
|
||||
pipes[i].config = config.pipes[i];
|
||||
cmds[pipes[i].config.from].opipes.push_back(i);
|
||||
cmds[pipes[i].config.to].ipipes.push_back(i);
|
||||
cmds[pipes[i].config.from - 1].opipes.push_back(i);
|
||||
cmds[pipes[i].config.to - 1].ipipes.push_back(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)pipes.size(); i++) {
|
||||
@ -175,8 +180,8 @@ public:
|
||||
cmds[i].pid = fork();
|
||||
if (cmds[i].pid == 0) {
|
||||
prepare_fd_for_cmd(i);
|
||||
system(cmds[i].cmd.c_str());
|
||||
exit(0);
|
||||
execl("/bin/sh", "sh", "-c", cmds[i].cmd.c_str(), NULL);
|
||||
exit(1); // exec failed, exit 1
|
||||
} else if (cmds[i].pid == -1) {
|
||||
throw system_error(errno, system_category());
|
||||
}
|
||||
@ -184,7 +189,7 @@ public:
|
||||
|
||||
prepare_fd();
|
||||
for (int i = 0; i < (int)pipes.size(); i++) {
|
||||
pipes[i].io_thread = thread(&RunInteraction::wait_pipe_io, this, i);
|
||||
pipes[i].io_thread = thread(&interaction_runner::wait_pipe_io, this, i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,12 +204,12 @@ public:
|
||||
};
|
||||
|
||||
error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *state) {
|
||||
RunInteractionConfig *config = (RunInteractionConfig*)state->input;
|
||||
runp::interaction::config *config = (runp::interaction::config*)state->input;
|
||||
|
||||
try {
|
||||
switch (key) {
|
||||
case 'p':
|
||||
config->pipes.push_back(PipeConfig(arg));
|
||||
config->pipes.push_back(runp::interaction::pipe_config(arg));
|
||||
break;
|
||||
case 's':
|
||||
if (config->pipes.empty()) {
|
||||
@ -230,7 +235,7 @@ error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *s
|
||||
return 0;
|
||||
}
|
||||
|
||||
RunInteractionConfig parse_args(int argc, char **argv) {
|
||||
runp::interaction::config parse_args(int argc, char **argv) {
|
||||
argp_option run_interaction_argp_options[] = {
|
||||
{"add-pipe" , 'p', "PIPE" , 0, "Add a pipe <from>:<fd>-<to>:<fd> (fd < 128)" , 1},
|
||||
{"save-pipe" , 's', "FILE" , 0, "Set last pipe saving file" , 2},
|
||||
@ -246,7 +251,7 @@ RunInteractionConfig parse_args(int argc, char **argv) {
|
||||
run_interaction_argp_doc
|
||||
};
|
||||
|
||||
RunInteractionConfig config;
|
||||
runp::interaction::config config;
|
||||
|
||||
argp_parse(&run_interaction_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config);
|
||||
|
||||
@ -256,7 +261,7 @@ RunInteractionConfig parse_args(int argc, char **argv) {
|
||||
int main(int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
RunInteraction ri(parse_args(argc, argv));
|
||||
interaction_runner ri(parse_args(argc, argv));
|
||||
ri.join();
|
||||
|
||||
return 0;
|
||||
|
@ -1,86 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/user.h>
|
||||
#include <fcntl.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <argp.h>
|
||||
#include "uoj_env.h"
|
||||
using namespace std;
|
||||
#include "run_program_sandbox.h"
|
||||
|
||||
struct RunResult {
|
||||
int result;
|
||||
int ust;
|
||||
int usm;
|
||||
int exit_code;
|
||||
|
||||
RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
|
||||
: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
|
||||
if (result != RS_AC) {
|
||||
ust = -1, usm = -1;
|
||||
}
|
||||
}
|
||||
enum RUN_EVENT_TYPE {
|
||||
ET_SKIP,
|
||||
ET_EXIT,
|
||||
ET_SIGNALED,
|
||||
ET_REAL_TLE,
|
||||
ET_USER_CPU_TLE,
|
||||
ET_MLE,
|
||||
ET_OLE,
|
||||
ET_SECCOMP_STOP,
|
||||
ET_SIGNAL_DELIVERY_STOP,
|
||||
ET_RESTART,
|
||||
};
|
||||
|
||||
struct RunProgramConfig
|
||||
{
|
||||
int time_limit;
|
||||
int real_time_limit;
|
||||
int memory_limit;
|
||||
int output_limit;
|
||||
int stack_limit;
|
||||
string input_file_name;
|
||||
string output_file_name;
|
||||
string error_file_name;
|
||||
string result_file_name;
|
||||
string work_path;
|
||||
string type;
|
||||
vector<string> extra_readable_files;
|
||||
vector<string> extra_writable_files;
|
||||
bool allow_proc;
|
||||
bool safe_mode;
|
||||
bool need_show_trace_details;
|
||||
struct run_event {
|
||||
RUN_EVENT_TYPE type;
|
||||
int pid = -1;
|
||||
rp_child_proc *cp;
|
||||
|
||||
string program_name;
|
||||
string program_basename;
|
||||
vector<string> argv;
|
||||
int sig = 0;
|
||||
int exitcode = 0;
|
||||
int pevent = 0;
|
||||
|
||||
int usertim = 0, usermem = 0;
|
||||
};
|
||||
|
||||
int put_result(string result_file_name, RunResult res) {
|
||||
FILE *f;
|
||||
if (result_file_name == "stdout") {
|
||||
f = stdout;
|
||||
} else if (result_file_name == "stderr") {
|
||||
f = stderr;
|
||||
} else {
|
||||
f = fopen(result_file_name.c_str(), "w");
|
||||
}
|
||||
fprintf(f, "%d %d %d %d\n", res.result, res.ust, res.usm, res.exit_code);
|
||||
if (f != stdout && f != stderr) {
|
||||
fclose(f);
|
||||
}
|
||||
if (res.result == RS_JGF) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char self_path[PATH_MAX + 1] = {};
|
||||
|
||||
#include "run_program_conf.h"
|
||||
|
||||
argp_option run_program_argp_options[] =
|
||||
{
|
||||
argp_option run_program_argp_options[] = {
|
||||
{"tl" , 'T', "TIME_LIMIT" , 0, "Set time limit (in second)" , 1},
|
||||
{"rtl" , 'R', "TIME_LIMIT" , 0, "Set real time limit (in second)" , 2},
|
||||
{"ml" , 'M', "MEMORY_LIMIT", 0, "Set memory limit (in mb)" , 3},
|
||||
@ -101,26 +46,24 @@ argp_option run_program_argp_options[] =
|
||||
{"add-writable-raw" , 506, "FILE" , 0, "Add a writable (don't transform to its real path)" , 15},
|
||||
{0}
|
||||
};
|
||||
error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
RunProgramConfig *config = (RunProgramConfig*)state->input;
|
||||
error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state) {
|
||||
runp::config *config = (runp::config*)state->input;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
switch (key) {
|
||||
case 'T':
|
||||
config->time_limit = atoi(arg);
|
||||
config->limits.time = atoi(arg);
|
||||
break;
|
||||
case 'R':
|
||||
config->real_time_limit = atoi(arg);
|
||||
config->limits.real_time = atoi(arg);
|
||||
break;
|
||||
case 'M':
|
||||
config->memory_limit = atoi(arg);
|
||||
config->limits.memory = atoi(arg);
|
||||
break;
|
||||
case 'O':
|
||||
config->output_limit = atoi(arg);
|
||||
config->limits.output = atoi(arg);
|
||||
break;
|
||||
case 'S':
|
||||
config->stack_limit = atoi(arg);
|
||||
config->limits.stack = atoi(arg);
|
||||
break;
|
||||
case 'i':
|
||||
config->input_file_name = arg;
|
||||
@ -132,10 +75,7 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
|
||||
config->error_file_name = arg;
|
||||
break;
|
||||
case 'w':
|
||||
config->work_path = realpath(arg);
|
||||
if (config->work_path.empty()) {
|
||||
argp_usage(state);
|
||||
}
|
||||
config->work_path = arg;
|
||||
break;
|
||||
case 'r':
|
||||
config->result_file_name = arg;
|
||||
@ -144,10 +84,10 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
|
||||
config->type = arg;
|
||||
break;
|
||||
case 500:
|
||||
config->extra_readable_files.push_back(realpath(arg));
|
||||
config->readable_file_names.push_back(realpath(arg));
|
||||
break;
|
||||
case 501:
|
||||
config->safe_mode = false;
|
||||
config->unsafe = true;
|
||||
break;
|
||||
case 502:
|
||||
config->need_show_trace_details = true;
|
||||
@ -156,18 +96,18 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
|
||||
config->allow_proc = true;
|
||||
break;
|
||||
case 504:
|
||||
config->extra_readable_files.push_back(arg);
|
||||
config->readable_file_names.push_back(arg);
|
||||
break;
|
||||
case 505:
|
||||
config->extra_writable_files.push_back(realpath(arg));
|
||||
config->writable_file_names.push_back(realpath_for_write(arg));
|
||||
break;
|
||||
case 506:
|
||||
config->extra_writable_files.push_back(arg);
|
||||
config->writable_file_names.push_back(arg);
|
||||
break;
|
||||
case ARGP_KEY_ARG:
|
||||
config->argv.push_back(arg);
|
||||
config->program_name = arg;
|
||||
for (int i = state->next; i < state->argc; i++) {
|
||||
config->argv.push_back(state->argv[i]);
|
||||
config->rest_args.push_back(state->argv[i]);
|
||||
}
|
||||
state->next = state->argc;
|
||||
break;
|
||||
@ -191,54 +131,59 @@ argp run_program_argp = {
|
||||
run_program_argp_doc
|
||||
};
|
||||
|
||||
RunProgramConfig run_program_config;
|
||||
|
||||
void parse_args(int argc, char **argv) {
|
||||
run_program_config.time_limit = 1;
|
||||
run_program_config.real_time_limit = -1;
|
||||
run_program_config.memory_limit = 256;
|
||||
run_program_config.output_limit = 64;
|
||||
run_program_config.stack_limit = 1024;
|
||||
run_program_config.limits.time = 1;
|
||||
run_program_config.limits.real_time = -1;
|
||||
run_program_config.limits.memory = 256;
|
||||
run_program_config.limits.output = 64;
|
||||
run_program_config.limits.stack = 1024;
|
||||
run_program_config.input_file_name = "stdin";
|
||||
run_program_config.output_file_name = "stdout";
|
||||
run_program_config.error_file_name = "stderr";
|
||||
run_program_config.work_path = "";
|
||||
run_program_config.result_file_name = "stdout";
|
||||
run_program_config.type = "default";
|
||||
run_program_config.safe_mode = true;
|
||||
run_program_config.unsafe = false;
|
||||
run_program_config.need_show_trace_details = false;
|
||||
run_program_config.allow_proc = false;
|
||||
|
||||
argp_parse(&run_program_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &run_program_config);
|
||||
|
||||
if (run_program_config.real_time_limit == -1)
|
||||
run_program_config.real_time_limit = run_program_config.time_limit + 2;
|
||||
run_program_config.stack_limit = min(run_program_config.stack_limit, run_program_config.memory_limit);
|
||||
runp::result::result_file_name = run_program_config.result_file_name;
|
||||
|
||||
if (!run_program_config.work_path.empty()) {
|
||||
if (chdir(run_program_config.work_path.c_str()) == -1) {
|
||||
exit(put_result(run_program_config.result_file_name, RS_JGF));
|
||||
}
|
||||
if (run_program_config.limits.real_time == -1) {
|
||||
run_program_config.limits.real_time = run_program_config.limits.time + 2;
|
||||
}
|
||||
run_program_config.limits.stack = min(run_program_config.limits.stack, run_program_config.limits.memory);
|
||||
|
||||
run_program_config.program_name = realpath(run_program_config.argv[0]);
|
||||
|
||||
// NOTE: program_name is the full path of the program, not just the file name (but can start with "./")
|
||||
if (run_program_config.work_path.empty()) {
|
||||
run_program_config.work_path = dirname(run_program_config.program_name);
|
||||
run_program_config.program_basename = basename(run_program_config.program_name);
|
||||
run_program_config.argv[0] = "./" + run_program_config.program_basename;
|
||||
|
||||
if (chdir(run_program_config.work_path.c_str()) == -1) {
|
||||
exit(put_result(run_program_config.result_file_name, RS_JGF));
|
||||
run_program_config.work_path = realpath(getcwd());
|
||||
if (!is_len_valid_path(run_program_config.work_path)) {
|
||||
// work path does not exist
|
||||
runp::result(runp::RS_JGF, "error code: WPDNE1").dump_and_exit();
|
||||
}
|
||||
} else {
|
||||
run_program_config.work_path = realpath(run_program_config.work_path);
|
||||
if (!is_len_valid_path(run_program_config.work_path) || chdir(run_program_config.work_path.c_str()) == -1) {
|
||||
// work path does not exist
|
||||
runp::result(runp::RS_JGF, "error code: WPDNE2").dump_and_exit();
|
||||
}
|
||||
}
|
||||
if (!is_len_valid_path(realpath(run_program_config.program_name))) {
|
||||
// invalid program name
|
||||
runp::result(runp::RS_JGF, "error code: INVPGN2").dump_and_exit();
|
||||
}
|
||||
if (!available_program_type_set.count(run_program_config.type)) {
|
||||
// invalid program type
|
||||
runp::result(runp::RS_JGF, "error code: INVPGT").dump_and_exit();
|
||||
}
|
||||
|
||||
if (run_program_config.type == "python2") {
|
||||
string pre[4] = {"/usr/bin/python2.7", "-E", "-s", "-B"};
|
||||
run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 4);
|
||||
} else if (run_program_config.type == "python3") {
|
||||
string pre[3] = {"/usr/bin/python3.8", "-I", "-B"};
|
||||
run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3);
|
||||
try {
|
||||
run_program_config.gen_full_args();
|
||||
} catch (exception &e) {
|
||||
// fail to generate full args
|
||||
runp::result(runp::RS_JGF, "error code: GFULARGS").dump_and_exit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,10 +200,20 @@ void set_limit(int r, int rcur, int rmax = -1) {
|
||||
exit(55);
|
||||
}
|
||||
}
|
||||
void run_child() {
|
||||
set_limit(RLIMIT_CPU, run_program_config.time_limit, run_program_config.real_time_limit);
|
||||
set_limit(RLIMIT_FSIZE, run_program_config.output_limit << 20);
|
||||
set_limit(RLIMIT_STACK, run_program_config.stack_limit << 20);
|
||||
|
||||
void set_user_cpu_time_limit(int tl) {
|
||||
struct itimerval val;
|
||||
val.it_value = {tl, 100 * 1000};
|
||||
val.it_interval = {0, 100 * 1000};
|
||||
setitimer(ITIMER_VIRTUAL, &val, NULL);
|
||||
}
|
||||
|
||||
[[noreturn]] void run_child() {
|
||||
setpgid(0, 0);
|
||||
|
||||
set_limit(RLIMIT_FSIZE, run_program_config.limits.output << 20);
|
||||
set_limit(RLIMIT_STACK, run_program_config.limits.stack << 20);
|
||||
// TODO: use https://man7.org/linux/man-pages/man3/vlimit.3.html to limit virtual memory
|
||||
|
||||
if (run_program_config.input_file_name != "stdin") {
|
||||
if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) {
|
||||
@ -310,76 +265,374 @@ void run_child() {
|
||||
setenv("SHELL", env_shell.c_str(), 1);
|
||||
}
|
||||
|
||||
char **program_c_argv = new char*[run_program_config.argv.size() + 1];
|
||||
for (size_t i = 0; i < run_program_config.argv.size(); i++) {
|
||||
program_c_argv[i] = new char[run_program_config.argv[i].size() + 1];
|
||||
strcpy(program_c_argv[i], run_program_config.argv[i].c_str());
|
||||
char** program_c_argv = new char*[run_program_config.full_args.size() + 1];
|
||||
for (size_t i = 0; i < run_program_config.full_args.size(); i++) {
|
||||
program_c_argv[i] = run_program_config.full_args[i].data();
|
||||
}
|
||||
program_c_argv[run_program_config.argv.size()] = NULL;
|
||||
program_c_argv[run_program_config.full_args.size()] = NULL;
|
||||
|
||||
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
|
||||
exit(16);
|
||||
}
|
||||
if (execv(program_c_argv[0], program_c_argv) == -1) {
|
||||
exit(17);
|
||||
kill(getpid(), SIGSTOP);
|
||||
if (!run_program_config.unsafe && !set_seccomp_bpf()) {
|
||||
exit(99);
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
set_user_cpu_time_limit(run_program_config.limits.time);
|
||||
execv(program_c_argv[0], program_c_argv);
|
||||
_exit(17);
|
||||
} else if (pid != -1) {
|
||||
int status;
|
||||
while (wait(&status) > 0);
|
||||
}
|
||||
exit(17);
|
||||
}
|
||||
|
||||
const int MaxNRPChildren = 50;
|
||||
struct rp_child_proc {
|
||||
pid_t pid;
|
||||
int mode;
|
||||
};
|
||||
int n_rp_children;
|
||||
// limit for the safe mode, an upper limit for the number of calls to fork/vfork/clone
|
||||
const size_t MAX_TOTAL_RP_CHILDREN = 100;
|
||||
size_t total_rp_children = 0;
|
||||
|
||||
struct timeval start_time;
|
||||
struct timeval end_time;
|
||||
pid_t rp_timer_pid;
|
||||
rp_child_proc rp_children[MaxNRPChildren];
|
||||
vector<rp_child_proc> rp_children;
|
||||
struct rusage *ruse0p = NULL;
|
||||
|
||||
bool has_real_TLE() {
|
||||
struct timeval elapsed;
|
||||
timersub(&end_time, &start_time, &elapsed);
|
||||
return elapsed.tv_sec >= run_program_config.limits.real_time;
|
||||
}
|
||||
|
||||
int rp_children_pos(pid_t pid) {
|
||||
for (int i = 0; i < n_rp_children; i++) {
|
||||
for (size_t i = 0; i < rp_children.size(); i++) {
|
||||
if (rp_children[i].pid == pid) {
|
||||
return i;
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int rp_children_add(pid_t pid) {
|
||||
if (n_rp_children == MaxNRPChildren) {
|
||||
return -1;
|
||||
}
|
||||
rp_children[n_rp_children].pid = pid;
|
||||
rp_children[n_rp_children].mode = -1;
|
||||
n_rp_children++;
|
||||
return 0;
|
||||
void rp_children_add(pid_t pid) {
|
||||
rp_child_proc rpc;
|
||||
rpc.pid = pid;
|
||||
rpc.flags = CPF_STARTUP | CPF_IGNORE_ONE_SIGSTOP;
|
||||
rp_children.push_back(rpc);
|
||||
}
|
||||
void rp_children_del(pid_t pid) {
|
||||
int new_n = 0;
|
||||
for (int i = 0; i < n_rp_children; i++) {
|
||||
size_t new_n = 0;
|
||||
for (size_t i = 0; i < rp_children.size(); i++) {
|
||||
if (rp_children[i].pid != pid) {
|
||||
rp_children[new_n++] = rp_children[i];
|
||||
}
|
||||
}
|
||||
n_rp_children = new_n;
|
||||
rp_children.resize(new_n);
|
||||
}
|
||||
|
||||
string get_usage_summary(struct rusage *rusep) {
|
||||
struct timeval elapsed;
|
||||
timersub(&end_time, &start_time, &elapsed);
|
||||
|
||||
ostringstream sout;
|
||||
struct timeval total_cpu;
|
||||
timeradd(&rusep->ru_utime, &rusep->ru_stime, &total_cpu);
|
||||
|
||||
sout << "[statistics]" << endl;
|
||||
sout << "user CPU / total CPU / elapsed real time: ";
|
||||
sout << rusep->ru_utime.tv_sec * 1000 + rusep->ru_utime.tv_usec / 1000 << "ms / ";
|
||||
sout << total_cpu.tv_sec * 1000 + total_cpu.tv_usec / 1000 << "ms / ";
|
||||
sout << elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000 << "ms." << endl;
|
||||
sout << "max RSS: " << rusep->ru_maxrss << "kb." << endl;
|
||||
sout << "total number of threads: " << total_rp_children + 1 << "." << endl;
|
||||
sout << "voluntary / total context switches: " << rusep->ru_nvcsw << " / " << rusep->ru_nvcsw + rusep->ru_nivcsw << ".";
|
||||
|
||||
return sout.str();
|
||||
}
|
||||
|
||||
void stop_child(pid_t pid) {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
void stop_all() {
|
||||
kill(rp_timer_pid, SIGKILL);
|
||||
for (int i = 0; i < n_rp_children; i++) {
|
||||
kill(rp_children[i].pid, SIGKILL);
|
||||
|
||||
void stop_all(runp::result res) {
|
||||
struct rusage tmp, ruse, *rusep = ruse0p;
|
||||
|
||||
kill(rp_timer_pid, SIGKILL);
|
||||
killpg(rp_children[0].pid, SIGKILL);
|
||||
|
||||
// in case some process changes its pgid
|
||||
for (auto &rpc : rp_children) {
|
||||
kill(rpc.pid, SIGKILL);
|
||||
}
|
||||
|
||||
int stat;
|
||||
while (true) {
|
||||
pid_t pid = wait4(-1, &stat, __WALL, &tmp);
|
||||
// cerr << "stop_all: wait " << pid << endl;
|
||||
if (pid < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else if (errno == ECHILD) {
|
||||
break;
|
||||
} else {
|
||||
res.dump_and_exit();
|
||||
}
|
||||
}
|
||||
|
||||
if (pid != rp_timer_pid && pid != rp_children[0].pid) {
|
||||
if (res.type != runp::RS_AC) {
|
||||
if (rp_children.size() >= 2 && pid == rp_children[1].pid) {
|
||||
ruse = tmp;
|
||||
rusep = &ruse;
|
||||
}
|
||||
} else if (rp_children.size() >= 2 && pid != rp_children[1].pid) {
|
||||
res = runp::result(runp::RS_RE, "main thread exited before others");
|
||||
}
|
||||
}
|
||||
|
||||
// it is possible that a newly created process hasn't been logged into rp_children
|
||||
// kill it for safty
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
|
||||
if (rusep) {
|
||||
res.extra += "\n";
|
||||
res.extra += get_usage_summary(rusep);
|
||||
}
|
||||
|
||||
res.dump_and_exit();
|
||||
}
|
||||
|
||||
run_event next_event() {
|
||||
static struct rusage ruse;
|
||||
static pid_t prev_pid = -1;
|
||||
run_event e;
|
||||
|
||||
int stat = 0;
|
||||
|
||||
e.pid = wait4(-1, &stat, __WALL, &ruse);
|
||||
const int wait_errno = errno;
|
||||
gettimeofday(&end_time, NULL);
|
||||
|
||||
ruse0p = NULL;
|
||||
if (e.pid < 0) {
|
||||
if (wait_errno == EINTR) {
|
||||
e.type = ET_SKIP;
|
||||
return e;
|
||||
}
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: WT4FAL")); // wait4 failed
|
||||
}
|
||||
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
if (prev_pid != e.pid) {
|
||||
cerr << "----------" << e.pid << "----------" << endl;
|
||||
}
|
||||
prev_pid = e.pid;
|
||||
}
|
||||
|
||||
if (e.pid == rp_timer_pid) {
|
||||
e.type = WIFEXITED(stat) || WIFSIGNALED(stat) ? ET_REAL_TLE : ET_SKIP;
|
||||
return e;
|
||||
}
|
||||
|
||||
if (has_real_TLE()) {
|
||||
e.type = ET_REAL_TLE;
|
||||
return e;
|
||||
}
|
||||
|
||||
int p = rp_children_pos(e.pid);
|
||||
if (p == -1) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "new_proc %lld\n", (long long int)e.pid);
|
||||
}
|
||||
rp_children_add(e.pid);
|
||||
p = (int)rp_children.size() - 1;
|
||||
}
|
||||
|
||||
e.cp = rp_children.data() + p;
|
||||
ruse0p = p == 1 ? &ruse : NULL;
|
||||
|
||||
if (p >= 1) {
|
||||
e.usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000;
|
||||
e.usermem = ruse.ru_maxrss;
|
||||
if (e.usertim > run_program_config.limits.time * 1000) {
|
||||
e.type = ET_USER_CPU_TLE;
|
||||
return e;
|
||||
}
|
||||
if (e.usermem > run_program_config.limits.memory * 1024) {
|
||||
e.type = ET_MLE;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
if (WIFEXITED(stat)) {
|
||||
if (p == 0) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: ZROEX")); // the 0th child process exited unexpectedly
|
||||
}
|
||||
e.type = ET_EXIT;
|
||||
e.exitcode = WEXITSTATUS(stat);
|
||||
return e;
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(stat)) {
|
||||
if (p == 0) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: ZROSIG")); // the 0th child process signaled unexpectedly
|
||||
}
|
||||
e.type = ET_SIGNALED;
|
||||
e.sig = WTERMSIG(stat);
|
||||
return e;
|
||||
}
|
||||
|
||||
if (!WIFSTOPPED(stat)) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: NSTOP")); // expected WIFSTOPPED, but it is not
|
||||
}
|
||||
|
||||
e.sig = WSTOPSIG(stat);
|
||||
e.pevent = (unsigned)stat >> 16;
|
||||
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "sig : %s\n", strsignal(e.sig));
|
||||
}
|
||||
|
||||
if (e.cp->flags & CPF_STARTUP) {
|
||||
int ptrace_opt = PTRACE_O_EXITKILL;
|
||||
if (p == 0 || !run_program_config.unsafe) {
|
||||
ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK;
|
||||
}
|
||||
if (!run_program_config.unsafe) {
|
||||
ptrace_opt |= PTRACE_O_TRACESECCOMP;
|
||||
}
|
||||
if (ptrace(PTRACE_SETOPTIONS, e.pid, NULL, ptrace_opt) == -1) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: PTRCFAL")); // ptrace failed
|
||||
}
|
||||
e.cp->flags &= ~CPF_STARTUP;
|
||||
}
|
||||
|
||||
switch (e.sig) {
|
||||
case SIGTRAP:
|
||||
switch (e.pevent) {
|
||||
case 0:
|
||||
case PTRACE_EVENT_CLONE:
|
||||
case PTRACE_EVENT_FORK:
|
||||
case PTRACE_EVENT_VFORK:
|
||||
e.sig = 0;
|
||||
e.type = ET_RESTART;
|
||||
return e;
|
||||
case PTRACE_EVENT_SECCOMP:
|
||||
e.sig = 0;
|
||||
e.type = ET_SECCOMP_STOP;
|
||||
return e;
|
||||
default:
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: PTRCSIG")); // unknown ptrace signal
|
||||
}
|
||||
case SIGSTOP:
|
||||
if (e.cp->flags & CPF_IGNORE_ONE_SIGSTOP) {
|
||||
e.sig = 0;
|
||||
e.type = ET_RESTART;
|
||||
e.cp->flags &= ~CPF_IGNORE_ONE_SIGSTOP;
|
||||
} else {
|
||||
e.type = ET_SIGNAL_DELIVERY_STOP;
|
||||
}
|
||||
return e;
|
||||
case SIGVTALRM:
|
||||
// use rusage as the only standard for user CPU time TLE
|
||||
// if the program reaches this line... then something goes wrong (rusage says no TLE, but timer says TLE)
|
||||
// just ignore it and wait for another period
|
||||
e.sig = 0;
|
||||
e.type = ET_RESTART;
|
||||
return e;
|
||||
case SIGXFSZ:
|
||||
e.type = ET_OLE;
|
||||
return e;
|
||||
default:
|
||||
e.type = ET_SIGNAL_DELIVERY_STOP;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
RunResult trace_children() {
|
||||
void dispatch_event(run_event&& e) {
|
||||
auto restart_op = PTRACE_CONT;
|
||||
|
||||
switch (e.type) {
|
||||
case ET_SKIP:
|
||||
return;
|
||||
case ET_REAL_TLE:
|
||||
stop_all(runp::result(runp::RS_TLE, "elapsed real time limit exceeded: >" + to_string(run_program_config.limits.real_time) + "s"));
|
||||
case ET_USER_CPU_TLE:
|
||||
stop_all(runp::result(runp::RS_TLE, "user CPU time limit exceeded: >" + to_string(run_program_config.limits.time) + "s"));
|
||||
case ET_MLE:
|
||||
stop_all(runp::result(runp::RS_MLE, "max RSS >" + to_string(run_program_config.limits.memory) + "MB"));
|
||||
case ET_OLE:
|
||||
stop_all(runp::result(runp::RS_OLE, "output limit exceeded: >" + to_string(run_program_config.limits.output) + "MB"));
|
||||
case ET_EXIT:
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "exit : %d\n", e.exitcode);
|
||||
}
|
||||
if (rp_children[0].flags & CPF_STARTUP) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: CPCMDER1")); // rp_children mode error
|
||||
} else if (rp_children.size() < 2 || (rp_children[1].flags & CPF_STARTUP)) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: CPCMDER2")); // rp_children mode error
|
||||
} else {
|
||||
if (e.cp == rp_children.data() + 1) {
|
||||
stop_all(runp::result(runp::RS_AC, "exit with code " + to_string(e.exitcode), e.usertim, e.usermem, e.exitcode));
|
||||
} else {
|
||||
rp_children_del(e.pid);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case ET_SIGNALED:
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "sig exit : %s\n", strsignal(e.sig));
|
||||
}
|
||||
if (e.cp == rp_children.data() + 1) {
|
||||
stop_all(runp::result(runp::RS_RE, string("process terminated by signal: ") + strsignal(e.sig)));
|
||||
} else {
|
||||
rp_children_del(e.pid);
|
||||
}
|
||||
return;
|
||||
|
||||
case ET_SECCOMP_STOP:
|
||||
if (e.cp != rp_children.data() + 0 && !run_program_config.unsafe) {
|
||||
if (!e.cp->check_safe_syscall()) {
|
||||
if (e.cp->suspicious) {
|
||||
stop_all(runp::result(runp::RS_DGS, e.cp->error));
|
||||
} else {
|
||||
stop_all(runp::result(runp::RS_RE, e.cp->error));
|
||||
}
|
||||
}
|
||||
if (e.cp->try_to_create_new_process) {
|
||||
total_rp_children++;
|
||||
if (total_rp_children > MAX_TOTAL_RP_CHILDREN) {
|
||||
stop_all(runp::result(runp::RS_DGS, "the limit on the amount of child processes is exceeded"));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ET_SIGNAL_DELIVERY_STOP:
|
||||
break;
|
||||
|
||||
case ET_RESTART:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ptrace(restart_op, e.pid, NULL, e.sig) < 0) {
|
||||
if (errno != ESRCH) {
|
||||
stop_all(runp::result(runp::RS_JGF, "error code: PTRESFAL")); // ptrace restart failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void trace_children() {
|
||||
rp_timer_pid = fork();
|
||||
if (rp_timer_pid == -1) {
|
||||
stop_all();
|
||||
return RunResult(RS_JGF);
|
||||
runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed
|
||||
} else if (rp_timer_pid == 0) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = run_program_config.real_time_limit;
|
||||
ts.tv_nsec = 0;
|
||||
ts.tv_sec = run_program_config.limits.real_time;
|
||||
ts.tv_nsec = 100 * 1000000;
|
||||
nanosleep(&ts, NULL);
|
||||
exit(0);
|
||||
}
|
||||
@ -388,185 +641,30 @@ RunResult trace_children() {
|
||||
cerr << "timerpid " << rp_timer_pid << endl;
|
||||
}
|
||||
|
||||
pid_t prev_pid = -1;
|
||||
while (true) {
|
||||
int stat = 0;
|
||||
int sig = 0;
|
||||
struct rusage ruse;
|
||||
|
||||
pid_t pid = wait4(-1, &stat, __WALL, &ruse);
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
if (prev_pid != pid) {
|
||||
cerr << "----------" << pid << "----------" << endl;
|
||||
}
|
||||
prev_pid = pid;
|
||||
}
|
||||
if (pid == rp_timer_pid) {
|
||||
if (WIFEXITED(stat) || WIFSIGNALED(stat)) {
|
||||
stop_all();
|
||||
return RunResult(RS_TLE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int p = rp_children_pos(pid);
|
||||
if (p == -1) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "new_proc %lld\n", (long long int)pid);
|
||||
}
|
||||
if (rp_children_add(pid) == -1) {
|
||||
stop_child(pid);
|
||||
stop_all();
|
||||
return RunResult(RS_DGS);
|
||||
}
|
||||
p = n_rp_children - 1;
|
||||
}
|
||||
|
||||
int usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000;
|
||||
int usermem = ruse.ru_maxrss;
|
||||
if (usertim > run_program_config.time_limit * 1000) {
|
||||
stop_all();
|
||||
return RunResult(RS_TLE);
|
||||
}
|
||||
if (usermem > run_program_config.memory_limit * 1024) {
|
||||
stop_all();
|
||||
return RunResult(RS_MLE);
|
||||
}
|
||||
|
||||
if (WIFEXITED(stat)) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "exit : %d\n", WEXITSTATUS(stat));
|
||||
}
|
||||
if (rp_children[0].mode == -1) {
|
||||
stop_all();
|
||||
return RunResult(RS_JGF, -1, -1, WEXITSTATUS(stat));
|
||||
} else {
|
||||
if (pid == rp_children[0].pid) {
|
||||
stop_all();
|
||||
return RunResult(RS_AC, usertim, usermem, WEXITSTATUS(stat));
|
||||
} else {
|
||||
rp_children_del(pid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(stat)) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "sig exit : %d\n", WTERMSIG(stat));
|
||||
}
|
||||
if (pid == rp_children[0].pid) {
|
||||
switch(WTERMSIG(stat)) {
|
||||
case SIGXCPU: // nearly impossible
|
||||
stop_all();
|
||||
return RunResult(RS_TLE);
|
||||
case SIGXFSZ:
|
||||
stop_all();
|
||||
return RunResult(RS_OLE);
|
||||
default:
|
||||
stop_all();
|
||||
return RunResult(RS_RE);
|
||||
}
|
||||
} else {
|
||||
rp_children_del(pid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (WIFSTOPPED(stat)) {
|
||||
sig = WSTOPSIG(stat);
|
||||
|
||||
if (rp_children[p].mode == -1) {
|
||||
if ((p == 0 && sig == SIGTRAP) || (p != 0 && sig == SIGSTOP)) {
|
||||
if (p == 0) {
|
||||
int ptrace_opt = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD;
|
||||
if (run_program_config.safe_mode) {
|
||||
ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK;
|
||||
ptrace_opt |= PTRACE_O_TRACEEXEC;
|
||||
}
|
||||
if (ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_opt) == -1) {
|
||||
stop_all();
|
||||
return RunResult(RS_JGF);
|
||||
}
|
||||
}
|
||||
sig = 0;
|
||||
}
|
||||
rp_children[p].mode = 0;
|
||||
} else if (sig == (SIGTRAP | 0x80)) {
|
||||
if (rp_children[p].mode == 0) {
|
||||
if (run_program_config.safe_mode) {
|
||||
if (!check_safe_syscall(pid, run_program_config.need_show_trace_details)) {
|
||||
stop_all();
|
||||
return RunResult(RS_DGS);
|
||||
}
|
||||
}
|
||||
rp_children[p].mode = 1;
|
||||
} else {
|
||||
if (run_program_config.safe_mode) {
|
||||
on_syscall_exit(pid, run_program_config.need_show_trace_details);
|
||||
}
|
||||
rp_children[p].mode = 0;
|
||||
}
|
||||
|
||||
sig = 0;
|
||||
} else if (sig == SIGTRAP) {
|
||||
switch ((stat >> 16) & 0xffff) {
|
||||
case PTRACE_EVENT_CLONE:
|
||||
case PTRACE_EVENT_FORK:
|
||||
case PTRACE_EVENT_VFORK:
|
||||
sig = 0;
|
||||
break;
|
||||
case PTRACE_EVENT_EXEC:
|
||||
rp_children[p].mode = 1;
|
||||
sig = 0;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
stop_all();
|
||||
return RunResult(RS_JGF);
|
||||
}
|
||||
}
|
||||
|
||||
if (sig != 0) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "sig : %d\n", sig);
|
||||
}
|
||||
}
|
||||
|
||||
switch(sig) {
|
||||
case SIGXCPU:
|
||||
stop_all();
|
||||
return RunResult(RS_TLE);
|
||||
case SIGXFSZ:
|
||||
stop_all();
|
||||
return RunResult(RS_OLE);
|
||||
}
|
||||
}
|
||||
|
||||
ptrace(PTRACE_SYSCALL, pid, NULL, sig);
|
||||
dispatch_event(next_event());
|
||||
}
|
||||
}
|
||||
|
||||
RunResult run_parent(pid_t pid) {
|
||||
init_conf(run_program_config);
|
||||
|
||||
n_rp_children = 0;
|
||||
|
||||
rp_children_add(pid);
|
||||
return trace_children();
|
||||
}
|
||||
int main(int argc, char **argv) {
|
||||
self_path[readlink("/proc/self/exe", self_path, PATH_MAX)] = '\0';
|
||||
parse_args(argc, argv);
|
||||
try {
|
||||
fs::path self_path = fs::read_symlink("/proc/self/exe");
|
||||
runp::run_path = self_path.parent_path();
|
||||
} catch (exception &e) {
|
||||
runp::result(runp::RS_JGF, "error code: PTHFAL2").dump_and_exit(); // path failed
|
||||
}
|
||||
|
||||
parse_args(argc, argv);
|
||||
init_conf();
|
||||
|
||||
gettimeofday(&start_time, NULL);
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
return put_result(run_program_config.result_file_name, RS_JGF);
|
||||
runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed
|
||||
} else if (pid == 0) {
|
||||
run_child();
|
||||
} else {
|
||||
return put_result(run_program_config.result_file_name, run_parent(pid));
|
||||
rp_children_add(pid);
|
||||
trace_children();
|
||||
}
|
||||
return put_result(run_program_config.result_file_name, RS_JGF);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
760
judger/uoj_judger/run/run_program_sandbox.h
Normal file
760
judger/uoj_judger/run/run_program_sandbox.h
Normal file
@ -0,0 +1,760 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <unistd.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <argp.h>
|
||||
#include <seccomp.h>
|
||||
#include "uoj_run.h"
|
||||
|
||||
enum EX_CHECK_TYPE : unsigned {
|
||||
ECT_NONE = 0,
|
||||
ECT_CNT = 1,
|
||||
ECT_FILE_OP = 1 << 1, // it is a file operation
|
||||
ECT_END_AT = 1 << 2, // this file operation ends with "at" (e.g., openat)
|
||||
ECT_FILEAT_OP = ECT_FILE_OP | ECT_END_AT, // it is a file operation ended with "at"
|
||||
ECT_FILE_W = 1 << 3, // intend to write
|
||||
ECT_FILE_R = 1 << 4, // intend to read
|
||||
ECT_FILE_S = 1 << 5, // intend to stat
|
||||
ECT_CHECK_OPEN_FLAGS = 1 << 6, // check flags to determine whether it is to read/write (for open and openat)
|
||||
ECT_FILE2_W = 1 << 7, // intend to write (2nd file)
|
||||
ECT_FILE2_R = 1 << 8, // intend to read (2nd file)
|
||||
ECT_FILE2_S = 1 << 9, // intend to stat (2nd file)
|
||||
ECT_CLONE_THREAD = 1 << 10, // for clone(). Check that clone is making a non-suspicious thread
|
||||
ECT_KILL_SIG0_ALLOWED = 1 << 11, // forbid kill but killing with sig0 is allowed
|
||||
};
|
||||
|
||||
struct syscall_info {
|
||||
EX_CHECK_TYPE extra_check;
|
||||
int max_cnt;
|
||||
bool should_soft_ban = false;
|
||||
bool is_kill = false;
|
||||
|
||||
syscall_info()
|
||||
: extra_check(ECT_CNT), max_cnt(0) {}
|
||||
syscall_info(unsigned extra_check, int max_cnt)
|
||||
: extra_check((EX_CHECK_TYPE)extra_check), max_cnt(max_cnt) {}
|
||||
|
||||
static syscall_info unlimited() {
|
||||
return syscall_info(ECT_NONE, -1);
|
||||
}
|
||||
|
||||
static syscall_info count_based(int max_cnt) {
|
||||
return syscall_info(ECT_CNT, max_cnt);
|
||||
}
|
||||
|
||||
static syscall_info with_extra_check(unsigned extra_check, int max_cnt = -1) {
|
||||
if (max_cnt != -1) {
|
||||
extra_check |= ECT_CNT;
|
||||
}
|
||||
return syscall_info(extra_check, max_cnt);
|
||||
}
|
||||
|
||||
static syscall_info kill_type_syscall(unsigned extra_check = ECT_CNT, int max_cnt = 0) {
|
||||
if (max_cnt != -1) {
|
||||
extra_check |= ECT_CNT;
|
||||
}
|
||||
syscall_info res(extra_check, max_cnt);
|
||||
res.is_kill = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
static syscall_info soft_ban() {
|
||||
syscall_info res(ECT_CNT, 0);
|
||||
res.should_soft_ban = true;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
#include "run_program_conf.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std;
|
||||
|
||||
typedef unsigned long long int reg_val_t;
|
||||
#define REG_SYSCALL orig_rax
|
||||
#define REG_RET rax
|
||||
#define REG_ARG0 rdi
|
||||
#define REG_ARG1 rsi
|
||||
#define REG_ARG2 rdx
|
||||
#define REG_ARG3 rcx
|
||||
|
||||
enum CHILD_PROC_FLAG : unsigned {
|
||||
CPF_STARTUP = 1u << 0,
|
||||
CPF_IGNORE_ONE_SIGSTOP = 1u << 2
|
||||
};
|
||||
|
||||
struct rp_child_proc {
|
||||
pid_t pid;
|
||||
|
||||
unsigned flags;
|
||||
|
||||
struct user_regs_struct reg = {};
|
||||
int syscall = -1;
|
||||
string error;
|
||||
bool suspicious = false;
|
||||
bool try_to_create_new_process = false;
|
||||
|
||||
void set_error_for_suspicious(const string &error);
|
||||
void set_error_for_kill();
|
||||
void soft_ban_syscall(int set_no);
|
||||
bool check_safe_syscall();
|
||||
bool check_file_permission(const string &op, const string &fn, char mode);
|
||||
};
|
||||
|
||||
const size_t MAX_PATH_LEN = 512;
|
||||
const uint64_t MAX_FD_ID = 1 << 20;
|
||||
|
||||
const string INVALID_PATH(PATH_MAX + 8, 'X');
|
||||
const string EMPTY_PATH_AFTER_FD = "?empty_path_after_fd";
|
||||
|
||||
runp::config run_program_config;
|
||||
|
||||
set<string> writable_file_name_set;
|
||||
set<string> readable_file_name_set;
|
||||
set<string> statable_file_name_set;
|
||||
set<string> soft_ban_file_name_set;
|
||||
|
||||
syscall_info syscall_info_set[N_SYSCALL];
|
||||
|
||||
pid_t get_tgid_from_pid(pid_t pid) {
|
||||
ifstream fin("/proc/" + to_string(pid) + "/status");
|
||||
string key;
|
||||
while (fin >> key) {
|
||||
if (key == "Tgid:") {
|
||||
pid_t tgid;
|
||||
if (fin >> tgid) {
|
||||
return tgid;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool is_len_valid_path(const string &path) {
|
||||
return !path.empty() && path.size() <= MAX_PATH_LEN;
|
||||
}
|
||||
|
||||
string path_or_len_invalid(const string &path) {
|
||||
return is_len_valid_path(path) ? path : INVALID_PATH;
|
||||
}
|
||||
|
||||
string basename(const string &path) {
|
||||
if (!is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
size_t p = path.rfind('/');
|
||||
if (p == string::npos) {
|
||||
return path;
|
||||
} else {
|
||||
return path.substr(p + 1); // can be empty, e.g., path = "abc/"
|
||||
}
|
||||
}
|
||||
string dirname(const string &path) {
|
||||
if (!is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
size_t p = path.rfind('/');
|
||||
if (p == string::npos) {
|
||||
return INVALID_PATH;
|
||||
} else {
|
||||
return path.substr(0, p); // can be empty, e.g., path = "/abc"
|
||||
}
|
||||
}
|
||||
string realpath(const string &path) {
|
||||
if (!is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
static char real[PATH_MAX + 1] = {};
|
||||
if (realpath(path.c_str(), real) == NULL) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
return path_or_len_invalid(real);
|
||||
}
|
||||
string realpath_for_write(const string &path) {
|
||||
string real = realpath(path);
|
||||
if (!is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
|
||||
string b = basename(path);
|
||||
if (!is_len_valid_path(b) || b == "." || b == "..") {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
real = realpath(dirname(path));
|
||||
if (!is_len_valid_path(real)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
return path_or_len_invalid(real + "/" + b);
|
||||
}
|
||||
string readlink(const string &path) {
|
||||
if (!is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
static char buf[MAX_PATH_LEN + 1];
|
||||
ssize_t n = readlink(path.c_str(), buf, MAX_PATH_LEN + 1);
|
||||
if (n > (ssize_t)MAX_PATH_LEN) {
|
||||
return INVALID_PATH;
|
||||
} else {
|
||||
buf[n] = '\0';
|
||||
return path_or_len_invalid(buf);
|
||||
}
|
||||
}
|
||||
string getcwd() {
|
||||
char cwd[MAX_PATH_LEN + 1];
|
||||
if (getcwd(cwd, MAX_PATH_LEN) == NULL) {
|
||||
return INVALID_PATH;
|
||||
} else {
|
||||
return path_or_len_invalid(cwd);
|
||||
}
|
||||
}
|
||||
string getcwdp(pid_t pid) {
|
||||
return realpath("/proc/" + (pid == 0 ? "self" : to_string(pid)) + "/cwd");
|
||||
}
|
||||
string abspath(const string &path, pid_t pid, int fd = AT_FDCWD) {
|
||||
static int depth = 0;
|
||||
if (depth == 10 || !is_len_valid_path(path)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
|
||||
vector<string> lv;
|
||||
for (string cur = path; is_len_valid_path(cur); cur = dirname(cur)) {
|
||||
lv.push_back(basename(cur));
|
||||
}
|
||||
reverse(lv.begin(), lv.end());
|
||||
|
||||
string pos;
|
||||
if (path[0] == '/') {
|
||||
pos = "/";
|
||||
} else if (fd == AT_FDCWD) {
|
||||
pos = getcwdp(pid);
|
||||
} else {
|
||||
depth++;
|
||||
pos = abspath("/proc/self/fd/" + to_string(fd), pid);
|
||||
depth--;
|
||||
}
|
||||
if (!is_len_valid_path(pos)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
|
||||
struct stat stat_buf;
|
||||
bool reachable = true;
|
||||
for (auto &v : lv) {
|
||||
if (reachable) {
|
||||
if (lstat(pos.c_str(), &stat_buf) < 0 || !S_ISDIR(stat_buf.st_mode)) {
|
||||
reachable = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (reachable) {
|
||||
if (v == ".") {
|
||||
continue;
|
||||
} else if (v == "..") {
|
||||
pos = dirname(pos);
|
||||
if (pos.empty()) {
|
||||
pos = "/";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (v.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (pos.back() != '/') {
|
||||
pos += '/';
|
||||
}
|
||||
pos += v;
|
||||
if (pos.size() > MAX_PATH_LEN) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
|
||||
if (reachable) {
|
||||
string realpos;
|
||||
if (pos == "/proc/self") {
|
||||
realpos = "/proc/" + to_string(get_tgid_from_pid(pid));
|
||||
} else if (pos == "/proc/thread-self") {
|
||||
realpos = "/proc/" + to_string(get_tgid_from_pid(pid)) + "/" + to_string(pid);
|
||||
} else {
|
||||
if (lstat(pos.c_str(), &stat_buf) < 0) {
|
||||
reachable = false;
|
||||
continue;
|
||||
}
|
||||
if (!S_ISLNK(stat_buf.st_mode)) {
|
||||
continue;
|
||||
}
|
||||
realpos = readlink(pos);
|
||||
if (!is_len_valid_path(realpos)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
if (realpos[0] != '/') {
|
||||
realpos = dirname(pos) + "/" + realpos;
|
||||
}
|
||||
}
|
||||
|
||||
depth++;
|
||||
realpos = abspath(realpos, pid);
|
||||
depth--;
|
||||
if (!is_len_valid_path(realpos)) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
pos = realpos;
|
||||
}
|
||||
}
|
||||
|
||||
return path_or_len_invalid(pos);
|
||||
}
|
||||
string getfdp(pid_t pid, int fd) {
|
||||
if (fd == AT_FDCWD) {
|
||||
return getcwdp(pid);
|
||||
} else {
|
||||
return abspath("/proc/self/fd/" + to_string(fd), pid);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_in_set_smart(string name, const set<string> &s) {
|
||||
if (name.size() > MAX_PATH_LEN) {
|
||||
return false;
|
||||
}
|
||||
if (s.count(name)) {
|
||||
return true;
|
||||
}
|
||||
int level;
|
||||
for (level = 0; !name.empty(); name = dirname(name), level++) {
|
||||
if (level == 1 && s.count(name + "/*")) {
|
||||
return true;
|
||||
}
|
||||
if (s.count(name + "/")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (level == 1 && s.count("/*")) {
|
||||
return true;
|
||||
}
|
||||
if (s.count("/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool is_writable_file(string name) {
|
||||
if (name == "/") {
|
||||
return writable_file_name_set.count("system_root");
|
||||
}
|
||||
return is_in_set_smart(name, writable_file_name_set);
|
||||
}
|
||||
inline bool is_readable_file(const string &name) {
|
||||
if (name == "/") {
|
||||
return readable_file_name_set.count("system_root");
|
||||
}
|
||||
return is_in_set_smart(name, readable_file_name_set);
|
||||
}
|
||||
inline bool is_statable_file(const string &name) {
|
||||
if (name == "/") {
|
||||
return statable_file_name_set.count("system_root");
|
||||
}
|
||||
return is_in_set_smart(name, statable_file_name_set);
|
||||
}
|
||||
inline bool is_soft_ban_file(const string &name) {
|
||||
if (name == "/") {
|
||||
return soft_ban_file_name_set.count("system_root");
|
||||
}
|
||||
return is_in_set_smart(name, soft_ban_file_name_set);
|
||||
}
|
||||
|
||||
void add_file_permission(const string &file_name, char mode) {
|
||||
if (file_name.empty()) {
|
||||
return;
|
||||
}
|
||||
if (mode == 'w') {
|
||||
writable_file_name_set.insert(file_name);
|
||||
} else if (mode == 'r') {
|
||||
readable_file_name_set.insert(file_name);
|
||||
} else if (mode == 's') {
|
||||
statable_file_name_set.insert(file_name);
|
||||
}
|
||||
if (file_name == "system_root") {
|
||||
return;
|
||||
}
|
||||
for (string name = dirname(file_name); !name.empty(); name = dirname(name)) {
|
||||
statable_file_name_set.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
void init_conf() {
|
||||
const runp::config &config = run_program_config;
|
||||
add_file_permission(config.work_path, 'r');
|
||||
add_file_permission(config.work_path + "/", 's');
|
||||
if (folder_program_type_set.count(config.type)) {
|
||||
add_file_permission(realpath(config.program_name) + "/", 'r');
|
||||
} else {
|
||||
add_file_permission(realpath(config.program_name), 'r');
|
||||
}
|
||||
|
||||
vector<string> loads;
|
||||
loads.push_back("default");
|
||||
if (config.allow_proc) {
|
||||
loads.push_back("allow_proc");
|
||||
}
|
||||
if (config.type != "default") {
|
||||
loads.push_back(config.type);
|
||||
}
|
||||
|
||||
for (string type : loads) {
|
||||
if (allowed_syscall_list.count(type)) {
|
||||
for (const auto &kv : allowed_syscall_list[type]) {
|
||||
syscall_info_set[kv.first] = kv.second;
|
||||
}
|
||||
}
|
||||
if (soft_ban_file_name_list.count(type)) {
|
||||
for (const auto &name : soft_ban_file_name_list[type]) {
|
||||
soft_ban_file_name_set.insert(name);
|
||||
}
|
||||
}
|
||||
if (statable_file_name_list.count(type)) {
|
||||
for (const auto &name : statable_file_name_list[type]) {
|
||||
add_file_permission(name, 's');
|
||||
}
|
||||
}
|
||||
if (readable_file_name_list.count(type)) {
|
||||
for (const auto &name : readable_file_name_list[type]) {
|
||||
add_file_permission(name, 'r');
|
||||
}
|
||||
}
|
||||
if (writable_file_name_list.count(type)) {
|
||||
for (const auto &name : writable_file_name_list[type]) {
|
||||
add_file_permission(name, 'w');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &name : config.readable_file_names) {
|
||||
add_file_permission(name, 'r');
|
||||
}
|
||||
for (const auto &name : config.writable_file_names) {
|
||||
add_file_permission(name, 'w');
|
||||
}
|
||||
|
||||
if (config.type == "python2.7" || config.type == "python3") {
|
||||
soft_ban_file_name_set.insert(dirname(realpath(config.program_name)) + "/__pycode__/");
|
||||
} else if (config.type == "compiler") {
|
||||
add_file_permission(config.work_path + "/", 'w');
|
||||
}
|
||||
|
||||
readable_file_name_set.insert(writable_file_name_set.begin(), writable_file_name_set.end());
|
||||
statable_file_name_set.insert(readable_file_name_set.begin(), readable_file_name_set.end());
|
||||
}
|
||||
|
||||
string read_string_from_addr(reg_val_t addr, pid_t pid) {
|
||||
int max_len = MAX_PATH_LEN + sizeof(reg_val_t);
|
||||
char res[max_len + 1], *ptr = res;
|
||||
while (ptr != res + max_len) {
|
||||
*(reg_val_t*)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
|
||||
for (size_t i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) {
|
||||
if (*ptr == 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
res[max_len] = 0;
|
||||
return res;
|
||||
}
|
||||
string read_abspath_from_addr(reg_val_t addr, pid_t pid) {
|
||||
string p = read_string_from_addr(addr, pid);
|
||||
string a = abspath(p, pid);
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "path : %s -> %s\n", p.c_str(), is_len_valid_path(a) ? a.c_str() : "INVALID!");
|
||||
}
|
||||
return a;
|
||||
}
|
||||
string read_abspath_from_fd_and_addr(reg_val_t fd, reg_val_t addr, pid_t pid) {
|
||||
if (fd > MAX_FD_ID && (int)fd != AT_FDCWD) {
|
||||
return INVALID_PATH;
|
||||
}
|
||||
string p = read_string_from_addr(addr, pid);
|
||||
string a;
|
||||
if (p.empty()) {
|
||||
// this case is tricky
|
||||
// if p is empty, in the following cases, Linux will understand the path as the path of fd:
|
||||
// newfstatat + AT_EMPTY_PATH, linkat + AT_EMPTY_PATH, execveat + AT_EMPTY_PATH, readlinkat
|
||||
// otherwise, the syscall will return with an error
|
||||
// since fd is already opened, the program should have the permission to do the things listed above
|
||||
// (no read -> write conversion, no deletion, no chmod, etc.)
|
||||
// we just report this special case. the program will skip the permission check later
|
||||
a = EMPTY_PATH_AFTER_FD;
|
||||
} else {
|
||||
a = abspath(p, pid, (int)fd);
|
||||
}
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "path : %d, %s -> %s\n", (int)fd, p.c_str(), is_len_valid_path(a) ? a.c_str() : "INVALID!");
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
bool set_seccomp_bpf() {
|
||||
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0));
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
for (int no : supported_soft_ban_errno_list) {
|
||||
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(no), SYSCALL_SOFT_BAN_MASK | no, 0) < 0) {
|
||||
throw system_error();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < N_SYSCALL; i++) {
|
||||
if (syscall_info_set[i].extra_check == ECT_NONE) {
|
||||
if (syscall_info_set[i].should_soft_ban) {
|
||||
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), i, 0) < 0) {
|
||||
throw system_error();
|
||||
}
|
||||
} else {
|
||||
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, i, 0) < 0) {
|
||||
throw system_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
seccomp_load(ctx);
|
||||
} catch (system_error &e) {
|
||||
seccomp_release(ctx);
|
||||
return false;
|
||||
}
|
||||
seccomp_release(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rp_child_proc::set_error_for_suspicious(const string &error) {
|
||||
this->suspicious = true;
|
||||
this->error = "suspicious system call invoked: " + error;
|
||||
}
|
||||
|
||||
void rp_child_proc::set_error_for_kill() {
|
||||
this->suspicious = false;
|
||||
reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1;
|
||||
this->error = "signal sent via " + syscall_name[this->syscall] + ": ";
|
||||
if (sig != (unsigned)sig) {
|
||||
this->error += "Unknown signal " + to_string(sig);
|
||||
} else {
|
||||
this->error += strsignal((int)sig);
|
||||
}
|
||||
}
|
||||
|
||||
void rp_child_proc::soft_ban_syscall(int set_no = EPERM) {
|
||||
this->reg.REG_SYSCALL = SYSCALL_SOFT_BAN_MASK | set_no;
|
||||
ptrace(PTRACE_SETREGS, pid, NULL, &this->reg);
|
||||
}
|
||||
|
||||
bool rp_child_proc::check_file_permission(const string &op, const string &fn, char mode) {
|
||||
string real_fn;
|
||||
if (!fn.empty()) {
|
||||
real_fn = mode == 'w' ? realpath_for_write(fn) : realpath(fn);
|
||||
}
|
||||
if (!is_len_valid_path(real_fn)) {
|
||||
// path invalid or file not found
|
||||
// ban this syscall softly
|
||||
this->soft_ban_syscall(ENOENT);
|
||||
return true;
|
||||
}
|
||||
|
||||
string path_proc_self = "/proc/" + to_string(get_tgid_from_pid(this->pid));
|
||||
if (real_fn.compare(0, path_proc_self.size() + 1, path_proc_self + "/") == 0) {
|
||||
real_fn = "/proc/self" + real_fn.substr(path_proc_self.size());
|
||||
} else if (real_fn == path_proc_self) {
|
||||
real_fn = "/proc/self";
|
||||
}
|
||||
|
||||
bool ok;
|
||||
switch (mode) {
|
||||
case 'w':
|
||||
ok = is_writable_file(real_fn);
|
||||
break;
|
||||
case 'r':
|
||||
ok = is_readable_file(real_fn);
|
||||
break;
|
||||
case 's':
|
||||
ok = is_statable_file(real_fn);
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "check file permission %s : %s\n", op.c_str(), real_fn.c_str());
|
||||
fprintf(stderr, "[readable]\n");
|
||||
for (auto s: readable_file_name_set) {
|
||||
cerr << s << endl;
|
||||
}
|
||||
fprintf(stderr, "[writable]\n");
|
||||
for (auto s: writable_file_name_set) {
|
||||
cerr << s << endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_soft_ban_file(real_fn)) {
|
||||
this->soft_ban_syscall(EACCES);
|
||||
return true;
|
||||
} else {
|
||||
this->set_error_for_suspicious("intended to access a file without permission: " + op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool rp_child_proc::check_safe_syscall() {
|
||||
ptrace(PTRACE_GETREGS, pid, NULL, ®);
|
||||
|
||||
int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff;
|
||||
if (cur_instruction != 0x050f) {
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "informal syscall %d\n", cur_instruction);
|
||||
}
|
||||
this->set_error_for_suspicious("incorrect opcode " + to_string(cur_instruction));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 > (long long int)reg.REG_SYSCALL || (long long int)reg.REG_SYSCALL >= N_SYSCALL) {
|
||||
this->set_error_for_suspicious(to_string(reg.REG_SYSCALL));
|
||||
return false;
|
||||
}
|
||||
syscall = (int)reg.REG_SYSCALL;
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "[syscall %s]\n", syscall_name[syscall].c_str());
|
||||
}
|
||||
this->try_to_create_new_process = syscall == __NR_fork || syscall == __NR_clone || syscall == __NR_clone3 || syscall == __NR_vfork;
|
||||
|
||||
auto &cursc = syscall_info_set[syscall];
|
||||
|
||||
if (cursc.extra_check & ECT_CNT) {
|
||||
if (cursc.max_cnt == 0) {
|
||||
if (cursc.should_soft_ban) {
|
||||
this->soft_ban_syscall();
|
||||
return true;
|
||||
} else {
|
||||
if (cursc.is_kill) {
|
||||
this->set_error_for_kill();
|
||||
} else {
|
||||
this->set_error_for_suspicious(syscall_name[syscall]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cursc.max_cnt--;
|
||||
}
|
||||
|
||||
if (cursc.extra_check & ECT_KILL_SIG0_ALLOWED) {
|
||||
reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1;
|
||||
if (sig != 0) {
|
||||
this->set_error_for_kill();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursc.extra_check & ECT_FILE_OP) {
|
||||
string fn;
|
||||
if (cursc.extra_check & ECT_END_AT) {
|
||||
fn = read_abspath_from_fd_and_addr(reg.REG_ARG0, reg.REG_ARG1, pid);
|
||||
} else {
|
||||
fn = read_abspath_from_addr(reg.REG_ARG0, pid);
|
||||
}
|
||||
|
||||
string textop = syscall_name[syscall];
|
||||
char mode = 'w';
|
||||
if (cursc.extra_check & ECT_CHECK_OPEN_FLAGS) {
|
||||
reg_val_t flags = cursc.extra_check & ECT_END_AT ? reg.REG_ARG2 : reg.REG_ARG1;
|
||||
switch (flags & O_ACCMODE) {
|
||||
case O_RDONLY:
|
||||
if ((flags & O_CREAT) == 0 && (flags & O_EXCL) == 0 && (flags & O_TRUNC) == 0) {
|
||||
textop += " (for read)";
|
||||
mode = 'r';
|
||||
} else {
|
||||
textop += " (for read & write)";
|
||||
}
|
||||
break;
|
||||
case O_WRONLY:
|
||||
textop += " (for write)";
|
||||
break;
|
||||
case O_RDWR:
|
||||
textop += " (for read & write)";
|
||||
break;
|
||||
default:
|
||||
textop += " (with invalid flags)";
|
||||
break;
|
||||
}
|
||||
} else if (cursc.extra_check & ECT_FILE_S) {
|
||||
mode = 's';
|
||||
} else if (cursc.extra_check & ECT_FILE_R) {
|
||||
mode = 'r';
|
||||
} else if (cursc.extra_check & ECT_FILE_W) {
|
||||
mode = 'w';
|
||||
} // else, error!
|
||||
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str());
|
||||
}
|
||||
if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cursc.extra_check & ECT_FILE2_S) {
|
||||
mode = 's';
|
||||
} else if (cursc.extra_check & ECT_FILE2_R) {
|
||||
mode = 'r';
|
||||
} else if (cursc.extra_check & ECT_FILE2_W) {
|
||||
mode = 'w';
|
||||
} else {
|
||||
mode = '?';
|
||||
}
|
||||
if (mode != '?') {
|
||||
if (cursc.extra_check & ECT_END_AT) {
|
||||
fn = read_abspath_from_fd_and_addr(reg.REG_ARG2, reg.REG_ARG3, pid);
|
||||
} else {
|
||||
fn = read_abspath_from_addr(reg.REG_ARG1, pid);
|
||||
}
|
||||
if (run_program_config.need_show_trace_details) {
|
||||
fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str());
|
||||
}
|
||||
if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursc.extra_check & ECT_CLONE_THREAD) {
|
||||
reg_val_t flags = reg.REG_ARG0;
|
||||
if (!(flags & CLONE_THREAD)) {
|
||||
this->set_error_for_suspicious("intended to create a new process");
|
||||
return false;
|
||||
}
|
||||
auto standard_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND;
|
||||
standard_flags |= CLONE_SYSVSEM | CLONE_SETTLS |CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
|
||||
if (!(flags & standard_flags)) {
|
||||
this->set_error_for_suspicious("intended to create a non-standard thread");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG CLONE_ADDFLAG
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg) &&\
|
||||
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF &&\
|
||||
apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev &&\
|
||||
yes | pecl install yaml &&\
|
||||
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1A10946ED858A0DF &&\
|
||||
echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu jammy main" | tee /etc/apt/sources.list.d/ondrej-php.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C &&\
|
||||
apt-get update --allow-unauthenticated &&\
|
||||
apt-get install -y --allow-unauthenticated -o Dpkg::Options::="--force-overwrite" libv8 php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd libseccomp-dev git vim ntp zip unzip curl wget libapache2-mod-xsendfile mysql-server php-pear cmake fp-compiler re2c libv8-7.5-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk &&\
|
||||
git clone https://github.com/phpv8/v8js.git --depth=1 -b 2.1.2 /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master &&\
|
||||
phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd -
|
||||
|
||||
|
@ -512,18 +512,26 @@ function echoSubmissionContent($submission, $requirement) {
|
||||
case 'C++':
|
||||
case 'C++11':
|
||||
case 'C++17':
|
||||
case 'C++20':
|
||||
case 'C++98':
|
||||
$sh_class = 'sh_cpp';
|
||||
case 'C++03':
|
||||
$sh_class = 'sh_cpp language-cpp';
|
||||
break;
|
||||
case 'Python2':
|
||||
case 'Python2.7':
|
||||
case 'Python3':
|
||||
$sh_class = 'sh_python';
|
||||
$sh_class = 'sh_python language-python';
|
||||
break;
|
||||
case 'Java8':
|
||||
case 'Java11':
|
||||
case 'Java17':
|
||||
$sh_class = 'sh_java language-java';
|
||||
break;
|
||||
case 'C':
|
||||
$sh_class = 'sh_c';
|
||||
$sh_class = 'sh_c language-c';
|
||||
break;
|
||||
case 'Pascal':
|
||||
$sh_class = 'sh_pascal';
|
||||
$sh_class = 'sh_pascal language-pascal';
|
||||
break;
|
||||
default:
|
||||
$sh_class = '';
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
global $uojSupportedLanguages, $uojMainJudgerWorkPath;
|
||||
$uojSupportedLanguages = array('C', 'C++', 'C++11', 'C++17', 'C++98', 'Pascal', 'Python2', 'Python3');
|
||||
$uojSupportedLanguages = array('C', 'C++', 'C++98', 'C++03', 'C++11', 'C++17', 'C++20', 'Pascal', 'Python2.7', 'Python3', 'Java8', 'Java11', 'Java17');
|
||||
$uojMainJudgerWorkPath = "/opt/uoj/judger/uoj_judger";
|
||||
|
||||
function authenticateJudger() {
|
||||
|
@ -212,8 +212,8 @@
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['hljs'])): ?>
|
||||
<?= HTML::css_link('/css/highlightjs.github.min.css') ?>
|
||||
<?= HTML::js_src('/js/highlightjs.min.js') ?>
|
||||
<?= HTML::css_link('/css/highlightjs.github.min.css?v=11.6.0-20221005') ?>
|
||||
<?= HTML::js_src('/js/highlightjs.min.js?v=11.6.0-20221005') ?>
|
||||
<script>$(document).ready(function() { hljs.highlightAll(); });</script>
|
||||
<?php endif ?>
|
||||
|
||||
|
@ -18,11 +18,12 @@ getAptPackage(){
|
||||
# Update apt sources and install
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg)
|
||||
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF
|
||||
apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev
|
||||
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1A10946ED858A0DF
|
||||
echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu jammy main" | tee /etc/apt/sources.list.d/ondrej-php.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C
|
||||
apt-get update --allow-unauthenticated
|
||||
apt-get install -y --allow-unauthenticated -o Dpkg::Options::="--force-overwrite" libv8 php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd libseccomp-dev git vim ntp zip unzip curl wget libapache2-mod-xsendfile mysql-server php-pear cmake fp-compiler re2c libv8-7.5-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
|
||||
# Install PHP extensions
|
||||
yes | pecl install yaml
|
||||
git clone https://github.com/phpv8/v8js.git --depth=1 -b 4c026f3fb291797c109adcabda6aeba6491fe44f /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master
|
||||
git clone https://github.com/phpv8/v8js.git --depth=1 -b 2.1.2 /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master
|
||||
phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd -
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ UOJEOF
|
||||
#define UOJ_JUDGER_PYTHON3_VERSION "3.6"
|
||||
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
|
||||
UOJEOF
|
||||
make runner -j$(($(nproc) + 1)) && cd /opt/uoj/web
|
||||
make all -j$(($(nproc) + 1)) && cd /opt/uoj/web
|
||||
}
|
||||
|
||||
dockerInitProgress() {
|
||||
|
39
web/js/highlightjs.min.js
vendored
39
web/js/highlightjs.min.js
vendored
@ -444,4 +444,41 @@ aliases:["dpr","dfm","pas","pascal"],case_insensitive:!0,keywords:r,
|
||||
illegal:/"|\$[G-Zg-z]|\/\*|<\/|\|/,contains:[n,i,e.NUMBER_MODE,{
|
||||
className:"number",relevance:0,variants:[{begin:"\\$[0-9A-Fa-f]+"},{
|
||||
begin:"&[0-7]+"},{begin:"%[01]+"}]},s,c,t].concat(a)}}})()
|
||||
;hljs.registerLanguage("delphi",e)})();
|
||||
;hljs.registerLanguage("delphi",e)})();/*! `java` grammar compiled for Highlight.js 11.6.0 */
|
||||
(()=>{var e=(()=>{"use strict"
|
||||
;var e="\\.([0-9](_*[0-9])*)",a="[0-9a-fA-F](_*[0-9a-fA-F])*",n={
|
||||
className:"number",variants:[{
|
||||
begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
|
||||
},{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{
|
||||
begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{
|
||||
begin:`\\b0[xX]((${a})\\.?|(${a})?\\.(${a}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
|
||||
},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${a})[lL]?\\b`},{
|
||||
begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],
|
||||
relevance:0};function s(e,a,n){return-1===n?"":e.replace(a,(t=>s(e,a,n-1)))}
|
||||
return e=>{
|
||||
const a=e.regex,t="[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",i=t+s("(?:<"+t+"~~~(?:\\s*,\\s*"+t+"~~~)*>)?",/~~~/g,2),r={
|
||||
keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed"],
|
||||
literal:["false","true","null"],
|
||||
type:["char","boolean","long","float","int","byte","short","double"],
|
||||
built_in:["super","this"]},l={className:"meta",begin:"@"+t,contains:[{
|
||||
begin:/\(/,end:/\)/,contains:["self"]}]},c={className:"params",begin:/\(/,
|
||||
end:/\)/,keywords:r,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0}
|
||||
;return{name:"Java",aliases:["jsp"],keywords:r,illegal:/<\/|#/,
|
||||
contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,
|
||||
relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{
|
||||
begin:/import java\.[a-z]+\./,keywords:"import",relevance:2
|
||||
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/,
|
||||
className:"string",contains:[e.BACKSLASH_ESCAPE]
|
||||
},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{
|
||||
match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,t],className:{
|
||||
1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{
|
||||
begin:[a.concat(/(?!else)/,t),/\s+/,t,/\s+/,/=(?!=)/],className:{1:"type",
|
||||
3:"variable",5:"operator"}},{begin:[/record/,/\s+/,t],className:{1:"keyword",
|
||||
3:"title.class"},contains:[c,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{
|
||||
beginKeywords:"new throw return else",relevance:0},{
|
||||
begin:["(?:"+i+"\\s+)",e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{
|
||||
2:"title.function"},keywords:r,contains:[{className:"params",begin:/\(/,
|
||||
end:/\)/,keywords:r,relevance:0,
|
||||
contains:[l,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,n,e.C_BLOCK_COMMENT_MODE]
|
||||
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},n,l]}}})()
|
||||
;hljs.registerLanguage("java",e)})();
|
||||
|
@ -606,15 +606,22 @@ function get_codemirror_mode(lang) {
|
||||
case 'C++':
|
||||
case 'C++11':
|
||||
case 'C++17':
|
||||
case 'C++20':
|
||||
case 'C++98':
|
||||
case 'C++03':
|
||||
return 'text/x-c++src';
|
||||
case 'C':
|
||||
return 'text/x-csrc';
|
||||
case 'Python2':
|
||||
case 'Python2.7':
|
||||
case 'Python3':
|
||||
return 'text/x-python';
|
||||
case 'Pascal':
|
||||
return 'text/x-pascal';
|
||||
case 'Java8':
|
||||
case 'Java11':
|
||||
case 'Java17':
|
||||
return 'text/x-java';
|
||||
case 'text':
|
||||
return 'text/plain';
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user