diff --git a/judger/Dockerfile b/judger/Dockerfile index 48e5152..693a8c2 100644 --- a/judger/Dockerfile +++ b/judger/Dockerfile @@ -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 diff --git a/judger/install.sh b/judger/install.sh index 32e6a4f..b667e1e 100644 --- a/judger/install.sh +++ b/judger/install.sh @@ -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 <uoj_judger/include/uoj_work_path.h <\\x%02x' % 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) diff --git a/judger/uoj_judger/Makefile b/judger/uoj_judger/Makefile index b69f50f..b849a24 100644 --- a/judger/uoj_judger/Makefile +++ b/judger/uoj_judger/Makefile @@ -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) diff --git a/judger/uoj_judger/builtin/judger/judger.cpp b/judger/uoj_judger/builtin/judger/judger.cpp index 118e15b..7253f7c 100644 --- a/judger/uoj_judger/builtin/judger/judger.cpp +++ b/judger/uoj_judger/builtin/judger/judger.cpp @@ -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 compiled; + static set 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 subtasks; - map 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 points; - minScore[t] = 100; - - vector 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::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) { diff --git a/judger/uoj_judger/include/uoj_env.h b/judger/uoj_judger/include/uoj_env.h deleted file mode 100644 index 11f9661..0000000 --- a/judger/uoj_judger/include/uoj_env.h +++ /dev/null @@ -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) - diff --git a/judger/uoj_judger/include/uoj_judger.h b/judger/uoj_judger/include/uoj_judger.h index a034a3d..d449242 100644 --- a/judger/uoj_judger/include/uoj_judger.h +++ b/judger/uoj_judger/include/uoj_judger.h @@ -17,26 +17,104 @@ #include #include +#include #include -#include "uoj_env.h" +#include "uoj_secure.h" +#include "uoj_run.h" + +namespace fs = std::filesystem; using namespace std; -/*========================== execute ====================== */ +/*========================== string ====================== */ -string escapeshellarg(const string &arg) { - string res = "'"; - for (size_t i = 0; i < arg.size(); i++) { - if (arg[i] == '\'') { - res += "'\\''"; - } else { - res += arg[i]; +template +inline string vtos(const T &v) { + ostringstream sout; + sout << v; + return sout.str(); +} + +inline string htmlspecialchars(const string &s) { + string r; + for (int i = 0; i < (int)s.length(); i++) { + switch (s[i]) { + case '&' : r += "&"; break; + case '<' : r += "<"; break; + case '>' : r += ">"; break; + case '"' : r += """; break; + case '\0': r += "\\0"; break; + default : r += s[i]; break; } } - res += "'"; - return res; + return r; } +/*========================== random ====================== */ + +inline string gen_token() { + u64 seed = time(NULL); + FILE *f = fopen("/dev/urandom", "r"); + if (f) { + for (int i = 0; i < 8; i++) + seed = seed << 8 | (u8)fgetc(f); + fclose(f); + } + uoj_mt_rand_engine rnd(seed); + return rnd.randstr(64); +} + +/*========================== crypto ====================== */ + +inline string file_get_contents(const string &name) { + string out; + FILE *f = fopen(name.c_str(), "r"); + if (!f) { + return out; + } + + const int BUFFER_SIZE = 1024; + u8 buffer[BUFFER_SIZE + 1]; + while (!feof(f)) { + int ret = fread(buffer, 1, BUFFER_SIZE, f); + if (ret < 0) { + break; + } + out.append((char *)buffer, ret); + } + fclose(f); + return out; +} +inline bool file_put_contents(const string &name, const string &m) { + FILE *f = fopen(name.c_str(), "w"); + if (!f) { + return false; + } + int c = fwrite(m.data(), 1, m.length(), f); + fclose(f); + return c == (int)m.length(); +} + +inline bool file_encrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + cipher.encrypt(m); + return file_put_contents(fo, m); +} +inline bool file_decrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + if (cipher.decrypt(m)) { + file_put_contents(fo, m); + return true; + } else { + file_put_contents(fo, "Unauthorized output"); + return false; + } +} + +/*========================== execute ====================== */ + string realpath(const string &path) { char real[PATH_MAX + 1]; if (realpath(path.c_str(), real) == NULL) { @@ -45,46 +123,40 @@ string realpath(const string &path) { return real; } - -int execute(const char *cmd) { - return system(cmd); -} - -int executef(const char *fmt, ...) { - const int MaxL = 512; - char cmd[MaxL]; - va_list ap; - va_start(ap, fmt); - int res = vsnprintf(cmd, MaxL, fmt, ap); - if (res < 0 || res >= MaxL) { - return -1; - } - res = execute(cmd); - va_end(ap); - return res; -} - /*======================== execute End ==================== */ /*========================= file ====================== */ -string file_preview(const string &name, const size_t &len = 100) { +string file_preview(const string &name, const int &len = 100) { FILE *f = fopen(name.c_str(), "r"); if (f == NULL) { return ""; } + string res = ""; - int c; - while (c = fgetc(f), c != EOF && res.size() < len + 4) { - res += c; - } - if (res.size() > len + 3) { - res.resize(len); - res += "..."; + if (len == -1) { + int c; + while (c = fgetc(f), c != EOF) { + res += c; + } + } else { + int c; + while (c = fgetc(f), c != EOF && (int)res.size() < len + 4) { + res += c; + } + if ((int)res.size() > len + 3) { + res.resize(len); + res += "..."; + } } fclose(f); return res; } +int file_size(const string &name) { + struct stat st; + stat(name.c_str(), &st); + return (int)st.st_size; +} void file_hide_token(const string &name, const string &token) { executef("cp %s %s.bak", name.c_str(), name.c_str()); @@ -108,44 +180,117 @@ void file_hide_token(const string &name, const string &token) { fclose(rf); fclose(wf); } +void file_replace_tokens(const string &name, const string &token, const string &new_token) { + string esc_name = escapeshellarg(name); + string esc_token = escapeshellarg(token); + string esc_new_token = escapeshellarg(new_token); + executef("sed -i s/%s/%s/g %s", esc_token.c_str(), esc_new_token.c_str(), esc_name.c_str()); +} +void file_copy(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + executef("cp %s %s -f", esc_a.c_str(), esc_b.c_str()); // the most cubao implementation in the world +} +void file_move(const string &a, const string &b) { // move a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + executef("mv %s %s", esc_a.c_str(), esc_b.c_str()); // the most cubao implementation in the world +} /*======================= file End ==================== */ /*====================== parameter ==================== */ -struct RunLimit { - int time; - int real_time; - int memory; - int output; - - RunLimit() { - } - RunLimit(const int &_time, const int &_memory, const int &_output) - : time(_time), memory(_memory), output(_output), real_time(-1) { - } -}; +typedef runp::limits_t RunLimit; +typedef runp::result RunResult; const RunLimit RL_DEFAULT = RunLimit(1, 256, 64); -const RunLimit RL_JUDGER_DEFAULT = RunLimit(600, 1024, 128); +const RunLimit RL_GENERATOR_DEFAULT = RunLimit(2, 512, 64); +const RunLimit RL_JUDGER_DEFAULT = RunLimit(600, 2048, 128); // 2048 = 2GB. change it if needed const RunLimit RL_CHECKER_DEFAULT = RunLimit(5, 256, 64); const RunLimit RL_INTERACTOR_DEFAULT = RunLimit(1, 256, 64); const RunLimit RL_VALIDATOR_DEFAULT = RunLimit(5, 256, 64); -const RunLimit RL_MARKER_DEFAULT = RunLimit(5, 256, 64); const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 512, 64); -struct PointInfo { +struct InfoBlock { + string title; + string content; + int orig_size; + + static InfoBlock empty(const string &title) { + InfoBlock info; + info.title = title; + info.content = ""; + info.orig_size = -1; + return info; + } + static InfoBlock from_string(const string &title, const string &content) { + InfoBlock info; + info.title = title; + info.content = content; + info.orig_size = -1; + return info; + } + + static InfoBlock from_file(const string &title, const string &name) { + InfoBlock info; + info.title = title; + info.content = file_preview(name); + info.orig_size = -1; + return info; + } + static InfoBlock from_file_with_size(const string &title, const string &name) { + InfoBlock info; + info.title = title; + info.content = file_preview(name); + info.orig_size = file_size(name); + return info; + } + + string to_str() const { + if (title == "in") { + return "" + htmlspecialchars(content) + ""; + } + if (title == "out") { + return "" + htmlspecialchars(content) + ""; + } + if (title == "res") { + return "" + htmlspecialchars(content) + ""; + } + + string str = ""; + + return str; + } +}; + +int scale_score(int scr100, int full) { + return scr100 * full / 100; +} + +struct PointInfo { + static bool show_in; + static bool show_out; + static bool show_res; + int num; int scr; int ust, usm; string info, in, out, res; + bool use_li; + vector li; + PointInfo(const int &_num, const int &_scr, - const int &_ust, const int &_usm, const string &_info, - const string &_in, const string &_out, const string &_res) + const int &_ust, const int &_usm, const string &_info = "default") : num(_num), scr(_scr), - ust(_ust), usm(_usm), info(_info), - in(_in), out(_out), res(_res) { + ust(_ust), usm(_usm), info(_info) { + use_li = true; if (info == "default") { if (scr == 0) { info = "Wrong Answer"; @@ -156,8 +301,64 @@ struct PointInfo { } } } + + PointInfo(const int &_num, const int &_scr, + const int &_ust, const int &_usm, const string &_info, + const string &_in, const string &_out, const string &_res) + : num(_num), scr(_scr), + ust(_ust), usm(_usm), info(_info), + in(_in), out(_out), res(_res) { + use_li = false; + if (info == "default") { + if (scr == 0) { + info = "Wrong Answer"; + } else if (scr == 100) { + info = "Accepted"; + } else { + info = "Acceptable Answer"; + } + } + } + + friend inline ostream& operator<<(ostream &out, const PointInfo &info) { + out << "" << endl; + if (!info.use_li) { + if (PointInfo::show_in) { + out << "" << htmlspecialchars(info.in) << "" << endl; + } + if (PointInfo::show_out) { + out << "" << htmlspecialchars(info.out) << "" << endl; + } + if (PointInfo::show_res) { + out << "" << htmlspecialchars(info.res) << "" << endl; + } + } else { + for (const auto &b : info.li) { + if (b.title == "in" && !PointInfo::show_in) { + continue; + } + if (b.title == "out" && !PointInfo::show_out) { + continue; + } + if (b.title == "res" && !PointInfo::show_res) { + continue; + } + out << b.to_str() << endl; + } + } + out << "" << endl; + return out; + } }; +bool PointInfo::show_in = true; +bool PointInfo::show_out = true; +bool PointInfo::show_res = true; + struct CustomTestInfo { int ust, usm; string info, exp, out; @@ -169,29 +370,118 @@ struct CustomTestInfo { } }; -struct RunResult { - int type; - int ust, usm; - int exit_code; +struct SubtaskMetaInfo { + int num; + vector points_id; + string subtask_type; + string subtask_used_time_type; + vector subtask_dependencies; + int full_score; - static RunResult failed_result() { - RunResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; - return res; - } - - static RunResult from_file(const string &file_name) { - RunResult res; - FILE *fres = fopen(file_name.c_str(), "r"); - if (fres == NULL || fscanf(fres, "%d %d %d %d", &res.type, &res.ust, &res.usm, &res.exit_code) != 4) { - return RunResult::failed_result(); - } - fclose(fres); - return res; + inline bool is_ordinary() { + return subtask_type == "packed" && subtask_used_time_type == "sum"; } }; + +struct SubtaskInfo { + SubtaskMetaInfo meta; + + bool passed = true; + bool early_stop = false; + int scr, ust = 0, usm = 0; + string info = "Accepted"; + int unrescaled_min_score = 100; + vector points; + + SubtaskInfo() = default; + SubtaskInfo(const SubtaskMetaInfo &meta) : meta(meta) { + scr = meta.full_score; + } + + inline void update_stats(int _ust, int _usm) { + if (_ust >= 0) { + if (meta.subtask_used_time_type == "max") { + ust = max(ust, _ust); + } else { + ust += _ust; + } + } + if (_usm >= 0) { + usm = max(usm, _usm); + } + } + + inline bool resolve_dependencies(const map &subtasks) { + for (const auto &p : meta.subtask_dependencies) { + const auto &dep = subtasks.at(p); + if (meta.subtask_type == "packed") { + if (!dep.passed) { + passed = false; + scr = 0; + } + } else if (meta.subtask_type == "min") { + unrescaled_min_score = min(unrescaled_min_score, dep.unrescaled_min_score); + scr = scale_score(unrescaled_min_score, meta.full_score); + if (!dep.passed) { + passed = false; + info = "Acceptable Answer"; + } + } + if (scr == 0) { + return false; + } + } + return true; + } + + inline void add_point_info(PointInfo po) { + unrescaled_min_score = min(unrescaled_min_score, po.scr); + update_stats(po.ust, po.usm); + + if (meta.subtask_type == "packed") { + if (po.scr != 100) { + passed = false; + early_stop = true; + po.scr = points.empty() ? 0 : -meta.full_score; + scr = 0; + info = po.info; + } else { + po.scr = points.empty() ? meta.full_score : 0; + scr = meta.full_score; + } + } else if (meta.subtask_type == "min") { + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, meta.full_score); + if (po.scr <= scr) { + scr = po.scr; + info = po.info; + if (meta.full_score != 0 && scr == 0) { + early_stop = true; + } + } + } + points.push_back(po); + } + + friend inline ostream& operator<<(ostream &out, const SubtaskInfo &st_info) { + out << "" << endl; + for (const auto &info : st_info.points) { + out << info; + } + out << "" << endl; + return out; + } +}; + struct RunCheckerResult { int type; int ust, usm; @@ -204,7 +494,7 @@ struct RunCheckerResult { res.ust = rres.ust; res.usm = rres.usm; - if (rres.type != RS_AC) { + if (rres.type != runp::RS_AC) { res.scr = 0; } else { FILE *fres = fopen(file_name.c_str(), "r"); @@ -232,11 +522,11 @@ struct RunCheckerResult { static RunCheckerResult failed_result() { RunCheckerResult res; - res.type = RS_JGF; + res.type = runp::RS_JGF; res.ust = -1; res.usm = -1; res.scr = 0; - res.info = "Checker Judgement Failed"; + res.info = "Checker Judgment Failed"; return res; } }; @@ -248,30 +538,28 @@ struct RunValidatorResult { static RunValidatorResult failed_result() { RunValidatorResult res; - res.type = RS_JGF; + res.type = runp::RS_JGF; res.ust = -1; res.usm = -1; res.succeeded = 0; - res.info = "Validator Judgement Failed"; + res.info = "Validator Judgment Failed"; return res; } }; -struct RunCompilerResult { - int type; - int ust, usm; +struct run_compiler_result { + runp::RS_TYPE type; bool succeeded; string info; - static RunCompilerResult failed_result() { - RunCompilerResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; + static run_compiler_result failed_result() { + run_compiler_result res; + res.type = runp::RS_JGF; res.succeeded = false; res.info = "Compile Failed"; return res; } }; +typedef run_compiler_result RunCompilerResult; // see also: run_simple_interaction struct RunSimpleInteractionResult { @@ -289,7 +577,6 @@ int tot_time = 0; int max_memory = 0; int tot_score = 0; ostringstream details_out; -//vector points_info; map config; /*==================== parameter End ================== */ @@ -346,7 +633,16 @@ int conf_int(const string &key, int num, const int &val) { int conf_int(const string &key) { return conf_int(key, 0); } +string conf_file_name_with_num(string s, int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str(s + "_pre", s) << abs(num) << "." << conf_str(s + "_suf", "txt"); + return name.str(); +} string conf_input_file_name(int num) { + // return conf_file_name_with_num("input", num): ostringstream name; if (num < 0) { name << "ex_"; @@ -355,6 +651,7 @@ string conf_input_file_name(int num) { return name.str(); } string conf_output_file_name(int num) { + // return conf_file_name_with_num("output", num): ostringstream name; if (num < 0) { name << "ex_"; @@ -362,19 +659,55 @@ string conf_output_file_name(int num) { name << conf_str("output_pre", "output") << abs(num) << "." << conf_str("output_suf", "txt"); return name.str(); } -RunLimit conf_run_limit(string pre, const int &num, const RunLimit &val) { +runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) { if (!pre.empty()) { pre += "_"; } - RunLimit limit; - limit.time = conf_int(pre + "time_limit", num, val.time); - limit.memory = conf_int(pre + "memory_limit", num, val.memory); - limit.output = conf_int(pre + "output_limit", num, val.output); - return limit; + runp::limits_t limits; + limits.time = conf_int(pre + "time_limit", num, val.time); + limits.memory = conf_int(pre + "memory_limit", num, val.memory); + limits.output = conf_int(pre + "output_limit", num, val.output); + limits.real_time = conf_int(pre + "real_time_limit", num, val.real_time); + limits.stack = conf_int(pre + "stack_limit", num, val.stack); + return limits; } -RunLimit conf_run_limit(const int &num, const RunLimit &val) { +runp::limits_t conf_run_limit(const int &num, const RunLimit &val) { return conf_run_limit("", num, val); } +SubtaskMetaInfo conf_subtask_meta_info(string pre, const int &num) { + if (!pre.empty()) { + pre += "_"; + } + + int nT = conf_int(pre + "n_subtasks", 0); + + SubtaskMetaInfo meta; + meta.num = num; + + int startI = conf_int(pre + "subtask_end", num - 1, 0) + 1; + int endI = num < nT ? conf_int(pre + "subtask_end", num, 0) : conf_int(pre + "n_tests", 10); + for (int i = startI; i <= endI; i++) { + meta.points_id.push_back(i); + } + + meta.subtask_type = conf_str(pre + "subtask_type", num, "packed"); + meta.subtask_used_time_type = conf_str(pre + "subtask_used_time_type", num, "sum"); + meta.full_score = conf_int(pre + "subtask_score", num, 100 / nT); + if (conf_str("subtask_dependence", num, "none") == "many") { + string cur = "subtask_dependence_" + vtos(num); + int p = 1; + while (conf_int(cur, p, 0) != 0) { + meta.subtask_dependencies.push_back(conf_int(cur, p, 0)); + p++; + } + } else if (conf_int("subtask_dependence", num, 0) != 0) { + meta.subtask_dependencies.push_back(conf_int("subtask_dependence", num, 0)); + } + return meta; +} +SubtaskMetaInfo conf_subtask_meta_info(const int &num) { + return conf_subtask_meta_info("", num); +} void conf_add(const string &key, const string &val) { if (config.count(key)) return; config[key] = val; @@ -391,38 +724,8 @@ bool conf_is(const string &key, const string &val) { /*====================== info print =================== */ -template -inline string vtos(const T &v) { - ostringstream sout; - sout << v; - return sout.str(); -} - -inline string htmlspecialchars(const string &s) { - string r; - for (int i = 0; i < (int)s.length(); i++) { - switch (s[i]) { - case '&' : r += "&"; break; - case '<' : r += "<"; break; - case '>' : r += ">"; break; - case '"' : r += """; break; - case '\0': r += "\\0"; break; - default : r += s[i]; break; - } - } - return r; -} - inline string info_str(int id) { - switch (id) { - case RS_MLE: return "Memory Limit Exceeded"; - case RS_TLE: return "Time Limit Exceeded"; - case RS_OLE: return "Output Limit Exceeded"; - case RS_RE : return "Runtime Error"; - case RS_DGS: return "Dangerous Syscalls"; - case RS_JGF: return "Judgement Failed"; - default : return "Unknown Result"; - } + return runp::rstype_str((runp::RS_TYPE)id); } inline string info_str(const RunResult &p) { return info_str(p.type); @@ -430,32 +733,18 @@ inline string info_str(const RunResult &p) { void add_point_info(const PointInfo &info, bool update_tot_score = true) { if (info.num >= 0) { - if(info.ust >= 0) { + if (info.ust >= 0) { tot_time += info.ust; } - if(info.usm >= 0) { + if (info.usm >= 0) { max_memory = max(max_memory, info.usm); } } if (update_tot_score) { - tot_score += info.scr; + tot_score += info.scr; } - details_out << "" << endl; - if (conf_str("show_in", "on") == "on") { - details_out << "" << htmlspecialchars(info.in) << "" << endl; - } - if (conf_str("show_out", "on") == "on") { - details_out << "" << htmlspecialchars(info.out) << "" << endl; - } - if (conf_str("show_res", "on") == "on") { - details_out << "" << htmlspecialchars(info.res) << "" << endl; - } - details_out << "" << endl; + details_out << info; } void add_custom_test_info(const CustomTestInfo &info) { if(info.ust >= 0) { @@ -474,15 +763,15 @@ void add_custom_test_info(const CustomTestInfo &info) { details_out << "" << htmlspecialchars(info.out) << "" << endl; details_out << "" << endl; } -void add_subtask_info(const int &num, const int &scr, const string &info, const vector &points) { - details_out << "" << endl; - tot_score += scr; - for (vector::const_iterator it = points.begin(); it != points.end(); it++) { - add_point_info(*it, false); +void add_subtask_info(const SubtaskInfo &st_info) { + if (st_info.ust >= 0) { + tot_time += st_info.ust; } - details_out << "" << endl; + if (st_info.usm >= 0) { + max_memory = max(max_memory, st_info.usm); + } + tot_score += st_info.scr; + details_out << st_info; } void end_judge_ok() { FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); @@ -498,11 +787,11 @@ void end_judge_ok() { } void end_judge_judgement_failed(const string &info) { FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "error Judgement Failed\n"); + fprintf(fres, "error Judgment Failed\n"); fprintf(fres, "details\n"); fprintf(fres, "%s\n", htmlspecialchars(info).c_str()); fclose(fres); - exit(1); + exit(0); } void end_judge_compile_error(const RunCompilerResult &res) { FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); @@ -547,6 +836,7 @@ bool report_judge_status_f(const char *fmt, ...) { struct RunProgramConfig { vector readable_file_names; // other than stdin + vector writable_file_names; // other than stdout, stderr string result_file_name; string input_file_name; string output_file_name; @@ -584,17 +874,14 @@ struct RunProgramConfig { va_end(vl); } - void set_submission_program_name(string name) { - string lang = conf_str(string(name) + "_language"); - type = "default"; - string program_name = name; - if (lang == "Python2") { - type = "python2"; - } else if (lang == "Python3") { - type = "python3"; + void set_submission_program_name(const string &name) { + string lang = conf_str(name + "_language"); + if (lang.empty()) { + type = conf_str(name + "_run_type", "default"); + } else { + type = runp::get_type_from_lang(lang); } - - set_argv(program_name.c_str(), NULL); + set_argv(name.c_str(), NULL); } string get_cmd() const { @@ -610,9 +897,20 @@ struct RunProgramConfig { << " " << "--type=" << type << " " << "--work-path=" << work_path /*<< " " << "--show-trace-details"*/; + + if (limit.real_time != -1) { + sout << " " << "--rtl=" << limit.real_time; + } + if (limit.stack != -1) { + sout << " " << "--sl=" << limit.stack; + } + for (vector::const_iterator it = readable_file_names.begin(); it != readable_file_names.end(); it++) { sout << " " << "--add-readable=" << escapeshellarg(*it); } + for (vector::const_iterator it = writable_file_names.begin(); it != writable_file_names.end(); it++) { + sout << " " << "--add-writable=" << escapeshellarg(*it); + } for (vector::const_iterator it = argv.begin(); it != argv.end(); it++) { sout << " " << escapeshellarg(*it); } @@ -620,41 +918,8 @@ struct RunProgramConfig { } }; -struct PipeConfig { - int from, to; - int from_fd, to_fd; - - string saving_file_name; - - PipeConfig() { - } - PipeConfig(int _from, int _from_fd, int _to, int _to_fd, const string &_saving_file_name = "") - : from(_from), from_fd(_from_fd), to(_to), to_fd(_to_fd), saving_file_name(_saving_file_name) { - } -}; -struct RunInteractionConfig { - vector cmds; - vector pipes; - - string get_cmd() const { - ostringstream sout; - sout << main_path << "/run/run_interaction"; - for (int i = 0; i < (int)cmds.size(); i++) { - sout << " " << escapeshellarg(cmds[i]); - } - for (int i = 0; i < (int)pipes.size(); i++) { - sout << " " << "-p"; - sout << " " << pipes[i].from << ":" << pipes[i].from_fd; - sout << "-" << pipes[i].to << ":" << pipes[i].to_fd; - - if (!pipes[i].saving_file_name.empty()) { - sout << " " << "-s"; - sout << " " << escapeshellarg(pipes[i].saving_file_name); - } - } - return sout.str(); - } -}; +typedef runp::interaction::pipe_config PipeConfig; +typedef runp::interaction::config RunInteractionConfig; // @deprecated // will be removed in the future @@ -679,14 +944,21 @@ RunResult vrun_program( sout << " " << escapeshellarg(*it); } - if (execute(sout.str().c_str()) != 0) { + if (execute(sout.str()) != 0) { return RunResult::failed_result(); } return RunResult::from_file(run_program_result_file_name); } RunResult run_program(const RunProgramConfig &rpc) { - if (execute(rpc.get_cmd().c_str()) != 0) { + if (execute(rpc.get_cmd()) != 0) { + return RunResult::failed_result(); + } + return RunResult::from_file(rpc.result_file_name); +} + +RunResult run_program(const runp::config &rpc) { + if (execute(rpc.get_cmd()) != 0) { return RunResult::failed_result(); } return RunResult::from_file(rpc.result_file_name); @@ -694,7 +966,7 @@ RunResult run_program(const RunProgramConfig &rpc) { // @return interaction return value int run_interaction(const RunInteractionConfig &ric) { - return execute(ric.get_cmd().c_str()); + return runp::interaction::run(ric); } RunResult run_program( @@ -728,6 +1000,7 @@ RunValidatorResult run_validator( "/dev/null", (string(result_path) + "/validator_error.txt").c_str(), limit, + ("--type=" + conf_str("val_run_type", "default")).c_str(), program_name.c_str(), NULL); @@ -736,7 +1009,7 @@ RunValidatorResult run_validator( res.ust = ret.ust; res.usm = ret.usm; - if (ret.type != RS_AC || ret.exit_code != 0) { + if (ret.type != runp::RS_AC || ret.exit_code != 0) { res.succeeded = false; res.info = file_preview(result_path + "/validator_error.txt"); } else { @@ -751,14 +1024,15 @@ RunCheckerResult run_checker( const string &output_file_name, const string &answer_file_name) { RunResult ret = run_program( - (string(result_path) + "/run_checker_result.txt").c_str(), + (result_path + "/run_checker_result.txt").c_str(), "/dev/null", "/dev/null", - (string(result_path) + "/checker_error.txt").c_str(), + (result_path + "/checker_error.txt").c_str(), limit, ("--add-readable=" + input_file_name).c_str(), ("--add-readable=" + output_file_name).c_str(), ("--add-readable=" + answer_file_name).c_str(), + ("--type=" + conf_str("chk_run_type", "default")).c_str(), program_name.c_str(), realpath(input_file_name).c_str(), realpath(output_file_name).c_str(), @@ -768,36 +1042,25 @@ RunCheckerResult run_checker( return RunCheckerResult::from_file(result_path + "/checker_error.txt", ret); } -RunCompilerResult run_compiler(const char *path, ...) { - vector argv; - argv.push_back("--type=compiler"); - argv.push_back(string("--work-path=") + path); - va_list vl; - va_start(vl, path); - for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { - argv.push_back(arg); - } - va_end(vl); +template +run_compiler_result run_compiler(runp::config rpc) { + rpc.result_file_name = result_path + "/run_compiler_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "stderr"; + rpc.error_file_name = result_path + "/compiler_result.txt"; + rpc.type = "compiler"; - RunResult ret = vrun_program( - (result_path + "/run_compiler_result.txt").c_str(), - "/dev/null", - "stderr", - (result_path + "/compiler_result.txt").c_str(), - RL_COMPILER_DEFAULT, - argv); - RunCompilerResult res; + runp::result ret = run_program(rpc); + run_compiler_result res; res.type = ret.type; - res.ust = ret.ust; - res.usm = ret.usm; - res.succeeded = ret.type == RS_AC && ret.exit_code == 0; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; if (!res.succeeded) { - if (ret.type == RS_AC) { - res.info = file_preview(result_path + "/compiler_result.txt", 500); - } else if (ret.type == RS_JGF) { + if (ret.type == runp::RS_AC) { + res.info = file_preview(result_path + "/compiler_result.txt", 10240); + } else if (ret.type == runp::RS_JGF) { res.info = "No Comment"; } else { - res.info = "Compiler " + info_str(ret.type); + res.info = "Compiler " + runp::rstype_str(ret.type); } } return res; @@ -817,26 +1080,56 @@ RunResult run_submission_program( rpc.set_submission_program_name(name); RunResult res = run_program(rpc); - if (res.type == RS_AC && res.exit_code != 0) { - res.type = RS_RE; + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; } return res; } -void prepare_interactor() { - static bool prepared = false; - if (prepared) { +/** + * prepare_run_standard_program(): do some preparation operations for the std. + * For the current version, it only moves the std to the work dir if it has not been done yet. + * + * @param prepared if it is not -1, set the internal variable as if the preparation operations have been done or not (depending on prepared is non-zero or not) + */ +void prepare_run_standard_program(int prepared = -1) { + static int _prepared = 0; + if (prepared != -1) { + _prepared = prepared; + } + if (_prepared) { + return; + } + string data_path_std = data_path + "/std"; + string work_path_std = work_path + "/std"; + executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); + _prepared = 1; +} + +/** + * prepare_interactor(): do some preparation operations for the interactor. + * For the current version, it only moves the interactor to the work dir if it has not been done yet. + * + * @param prepared if it is not -1, set the internal variable as if the preparation operations have been done or not (depending on prepared is non-zero or not) + */ +void prepare_interactor(int prepared = -1) { + static int _prepared = 0; + if (prepared != -1) { + _prepared = prepared; + } + if (_prepared) { return; } string data_path_std = data_path + "/interactor"; string work_path_std = work_path + "/interactor"; executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); - conf_add("interactor_language", "C++"); - prepared = true; + _prepared = 1; } -// simple: prog <---> interactor <---> data -RunSimpleInteractionResult run_simple_interation( +/** + * @brief simple: prog <---> interactor <---> data + */ +RunSimpleInteractionResult run_simple_interaction( const string &input_file_name, const string &answer_file_name, const string &real_input_file_name, @@ -862,12 +1155,11 @@ RunSimpleInteractionResult run_simple_interation( irpc.output_file_name = "stdout"; irpc.error_file_name = result_path + "/interactor_error.txt"; irpc.limit = ilimit; - irpc.set_submission_program_name("interactor"); - irpc.argv.push_back(input_file_name); - irpc.argv.push_back("/dev/stdin"); - irpc.argv.push_back(answer_file_name); + irpc.set_argv("interactor", input_file_name.c_str(), "/dev/stdin", answer_file_name.c_str(), NULL); + irpc.type = conf_str("interactor_run_type", "default"); - irpc.limit.real_time = rpc.limit.real_time = rpc.limit.time + irpc.limit.time + 1; + rpc.limit.real_time = rpc.limit.time + irpc.limit.time + 1; + irpc.limit.real_time = rpc.limit.real_time + 1; // one more second to prevent interactor from TLE RunInteractionConfig ric; ric.cmds.push_back(rpc.get_cmd()); @@ -883,16 +1175,16 @@ RunSimpleInteractionResult run_simple_interation( RunResult res = RunResult::from_file(rpc.result_file_name); RunCheckerResult ires = RunCheckerResult::from_file(irpc.error_file_name, RunResult::from_file(irpc.result_file_name)); - if (res.type == RS_AC && res.exit_code != 0) { - res.type = RS_RE; + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; } - if (ires.type == RS_JGF) { - ires.info = "Interactor Judgement Failed"; + if (ires.type == runp::RS_JGF) { + ires.info = "Interactor Judgment Failed"; } - if (ires.type == RS_TLE) { - ires.type = RS_AC; - res.type = RS_TLE; + if (ires.type == runp::RS_TLE) { + ires.type = runp::RS_AC; + res.type = runp::RS_TLE; } rires.res = res; @@ -900,17 +1192,6 @@ RunSimpleInteractionResult run_simple_interation( return rires; } -void prepare_run_standard_program() { - static bool prepared = false; - if (prepared) { - return; - } - string data_path_std = data_path + "/std"; - string work_path_std = work_path + "/std"; - executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); - conf_add("std_language", "C++"); - prepared = true; -} // @deprecated // will be removed in the future @@ -933,194 +1214,33 @@ RunResult run_standard_program( /*======================== compile ==================== */ -bool is_illegal_keyword(const string &name) { - if (name == "__asm" || name == "__asm__" || name == "asm") - return true; - return false; +run_compiler_result compile(const string &name) { + string lang = conf_str(name + "_language", "auto"); + runp::config rpc(main_path + "/run/compile", { + "--custom", main_path + "/run/runtime", + "--lang", lang, + name + }); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); } -bool has_illegal_keywords_in_file(const string &name) { - FILE *f = fopen(name.c_str(), "r"); - - int c; - string key; - while ((c = fgetc(f)) != EOF) - { - if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 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(); - } +run_compiler_result compile_with_implementer(const string &name, const string &implementer = "implementer") { + string lang = conf_str(name + "_language", "auto"); + if (conf_has(name + "_unit_name")) { + file_put_contents(work_path + "/" + name + ".unit_name", conf_str(name + "_unit_name")); } - if (is_illegal_keyword(key)) - return true; - fclose(f); - return false; + runp::config rpc(main_path + "/run/compile", { + "--custom", main_path + "/run/runtime", + "--impl", implementer, "--lang", lang, + name + }); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); } -RunCompilerResult compile_c(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/gcc", "-o", name.c_str(), "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_pas(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/fpc", (name + ".code").c_str(), "-O2", NULL); -} -RunCompilerResult compile_cpp(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_cpp11(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL); -} -RunCompilerResult compile_cpp17(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++17", NULL); -} -RunCompilerResult compile_cpp98(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++98", NULL); -} -RunCompilerResult compile_python2(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python2.7", "-E", "-s", "-B", "-O", "-c", - ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print e\n sys.exit(1)").c_str(), NULL); -} -RunCompilerResult compile_python3(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python3.8", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print(e)\n sys.exit(1)").c_str(), NULL); -} - -RunCompilerResult compile(const char *name) { - string lang = conf_str(string(name) + "_language"); - - if ((lang == "C++" || lang == "C++11" || lang == "C++17" || lang == "C++98" || lang == "C") && has_illegal_keywords_in_file(work_path + "/" + name + ".code")) - { - RunCompilerResult res; - res.type = RS_DGS; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Compile Failed"; - return res; - } - - if (lang == "C++") { - return compile_cpp(name); - } - if (lang == "C++11") { - return compile_cpp11(name); - } - if (lang == "C++17") { - return compile_cpp17(name); - } - if (lang == "C++98") { - return compile_cpp98(name); - } - if (lang == "Python2") { - return compile_python2(name); - } - if (lang == "Python3") { - return compile_python3(name); - } - if (lang == "C") { - return compile_c(name); - } - if (lang == "Pascal") { - return compile_pas(name); - } - - RunCompilerResult res = RunCompilerResult::failed_result(); - res.info = "This language is not supported yet."; - return res; -} - -RunCompilerResult compile_c_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/gcc", "-o", name.c_str(), "implementer.c", "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_pas_with_implementer(const string &name, const string &path = work_path) { - executef("cp %s %s", (path + "/" + name + ".code").c_str(), (path + "/" + conf_str(name + "_unit_name") + ".pas").c_str()); - return run_compiler(path.c_str(), - "/usr/bin/fpc", "implementer.pas", ("-o" + name).c_str(), "-O2", NULL); -} -RunCompilerResult compile_cpp_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++14", NULL); -} -RunCompilerResult compile_cpp98_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++98", NULL); -} -RunCompilerResult compile_cpp11_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL); -} -RunCompilerResult compile_cpp17_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++17", NULL); -} -/* -RunCompilerResult compile_python2(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python2.7", "-E", "-s", "-B", "-O", "-c", - ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print e\n sys.exit(1)").c_str(), NULL); -} -RunCompilerResult compile_python3(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python3.8", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print(e)\n sys.exit(1)").c_str(), NULL); -} -*/ -RunCompilerResult compile_with_implementer(const char *name) { - string lang = conf_str(string(name) + "_language"); - - if (has_illegal_keywords_in_file(work_path + "/" + name + ".code")) - { - RunCompilerResult res; - res.type = RS_DGS; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Compile Failed"; - return res; - } - - if (lang == "C++") { - return compile_cpp_with_implementer(name); - } - if (lang == "C++11") { - return compile_cpp11_with_implementer(name); - } - if (lang == "C++17") { - return compile_cpp17_with_implementer(name); - } - if (lang == "C++98") { - return compile_cpp98_with_implementer(name); - } - if (lang == "C") { - return compile_c_with_implementer(name); - } - if (lang == "Pascal") { - return compile_pas_with_implementer(name); - } - - RunCompilerResult res = RunCompilerResult::failed_result(); - res.info = "This language is not supported yet."; - return res; +run_compiler_result compile_submission_program(const string &name) { + return !conf_is("with_implementer", "on") ? compile(name) : compile_with_implementer(name); } /*====================== compile End ================== */ @@ -1128,16 +1248,14 @@ RunCompilerResult compile_with_implementer(const char *name) { /*====================== test ================== */ struct TestPointConfig { - int submit_answer; - - int validate_input_before_test; + int submit_answer = -1; + int validate_input_before_test = -1; + int disable_program_input = -1; string input_file_name; string output_file_name; string answer_file_name; - - TestPointConfig() - : submit_answer(-1), validate_input_before_test(-1) { - } + RunLimit limit = RunLimit(-1, -1, -1); + string checker; void auto_complete(int num) { if (submit_answer == -1) { @@ -1146,6 +1264,9 @@ struct TestPointConfig { if (validate_input_before_test == -1) { validate_input_before_test = conf_is("validate_input_before_test", "on"); } + if (disable_program_input == -1) { + disable_program_input = conf_str("disable_program_input", num, "off") == "on"; + } if (input_file_name.empty()) { input_file_name = data_path + "/" + conf_input_file_name(num); } @@ -1155,6 +1276,12 @@ struct TestPointConfig { if (answer_file_name.empty()) { answer_file_name = data_path + "/" + conf_output_file_name(num); } + if (limit.time == -1) { + limit = conf_run_limit(num, RL_DEFAULT); + } + if (checker.empty()) { + checker = conf_str("checker"); + } } }; @@ -1166,7 +1293,7 @@ PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = T tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), conf_str("validator")); - if (val_ret.type != RS_AC) { + if (val_ret.type != runp::RS_AC) { return PointInfo(num, 0, -1, -1, "Validator " + info_str(val_ret.type), file_preview(tpc.input_file_name), "", @@ -1183,62 +1310,76 @@ PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = T RunResult pro_ret; if (!tpc.submit_answer) { pro_ret = run_submission_program( - tpc.input_file_name.c_str(), + tpc.disable_program_input ? "/dev/null" : tpc.input_file_name.c_str(), tpc.output_file_name.c_str(), - conf_run_limit(num, RL_DEFAULT), + tpc.limit, name); if (conf_has("token")) { file_hide_token(tpc.output_file_name, conf_str("token", "")); } - if (pro_ret.type != RS_AC) { + if (pro_ret.type != runp::RS_AC) { return PointInfo(num, 0, -1, -1, info_str(pro_ret.type), file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), ""); } } else { - pro_ret.type = RS_AC; + pro_ret.type = runp::RS_AC; pro_ret.ust = -1; pro_ret.usm = -1; pro_ret.exit_code = 0; } - RunCheckerResult chk_ret = run_checker( - conf_run_limit("checker", num, RL_CHECKER_DEFAULT), - conf_str("checker"), - tpc.input_file_name, - tpc.output_file_name, - tpc.answer_file_name); - if (chk_ret.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - "Checker " + info_str(chk_ret.type), + if (tpc.checker == "nonempty") { + string usrout = file_preview(tpc.output_file_name); + if (usrout == "") { + return PointInfo(num, 0, -1, -1, + "default", + file_preview(tpc.input_file_name), usrout, + "wrong answer empty file\n"); + } else { + return PointInfo(num, 100, -1, -1, + "default", + file_preview(tpc.input_file_name), usrout, + "ok nonempty file\n"); + } + } else { + RunCheckerResult chk_ret = run_checker( + conf_run_limit("checker", num, RL_CHECKER_DEFAULT), + tpc.checker, + tpc.input_file_name, + tpc.output_file_name, + tpc.answer_file_name); + if (chk_ret.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, + "Checker " + info_str(chk_ret.type), + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + ""); + } + return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, + "default", file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), - ""); + chk_ret.info); } - - return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, - "default", - file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), - chk_ret.info); } else { string real_output_file_name = tpc.output_file_name + ".real_input.txt"; string real_input_file_name = tpc.output_file_name + ".real_output.txt"; - RunSimpleInteractionResult rires = run_simple_interation( + RunSimpleInteractionResult rires = run_simple_interaction( tpc.input_file_name, tpc.answer_file_name, real_input_file_name, real_output_file_name, - conf_run_limit(num, RL_DEFAULT), + tpc.limit, conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), name); - if (rires.ires.type != RS_AC) { + if (rires.ires.type != runp::RS_AC) { return PointInfo(num, 0, -1, -1, "Interactor " + info_str(rires.ires.type), file_preview(real_input_file_name), file_preview(real_output_file_name), ""); } - if (rires.res.type != RS_AC) { + if (rires.res.type != runp::RS_AC) { return PointInfo(num, 0, -1, -1, info_str(rires.res.type), file_preview(real_input_file_name), file_preview(real_output_file_name), @@ -1260,7 +1401,7 @@ PointInfo test_hack_point(const string &name, TestPointConfig tpc) { tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), conf_str("validator")); - if (val_ret.type != RS_AC) { + if (val_ret.type != runp::RS_AC) { return PointInfo(0, 0, -1, -1, "Validator " + info_str(val_ret.type), file_preview(tpc.input_file_name), "", @@ -1284,7 +1425,7 @@ PointInfo test_hack_point(const string &name, TestPointConfig tpc) { conf_run_limit("standard", 0, default_std_run_limit), "std", rpc); - if (std_ret.type != RS_AC) { + if (std_ret.type != runp::RS_AC) { return PointInfo(0, 0, -1, -1, "Standard Program " + info_str(std_ret.type), file_preview(tpc.input_file_name), "", @@ -1298,7 +1439,7 @@ PointInfo test_hack_point(const string &name, TestPointConfig tpc) { rpc.result_file_name = result_path + "/run_standard_program.txt"; string real_output_file_name = tpc.answer_file_name; string real_input_file_name = tpc.output_file_name + ".real_output.txt"; - RunSimpleInteractionResult rires = run_simple_interation( + RunSimpleInteractionResult rires = run_simple_interaction( tpc.input_file_name, tpc.answer_file_name, real_input_file_name, @@ -1308,13 +1449,13 @@ PointInfo test_hack_point(const string &name, TestPointConfig tpc) { "std", rpc); - if (rires.ires.type != RS_AC) { + if (rires.ires.type != runp::RS_AC) { return PointInfo(0, 0, -1, -1, "Interactor " + info_str(rires.ires.type) + " (Standard Program)", file_preview(real_input_file_name), "", ""); } - if (rires.res.type != RS_AC) { + if (rires.res.type != runp::RS_AC) { return PointInfo(0, 0, -1, -1, "Standard Program " + info_str(rires.res.type), file_preview(real_input_file_name), "", @@ -1327,8 +1468,12 @@ PointInfo test_hack_point(const string &name, TestPointConfig tpc) { return po; } -CustomTestInfo ordinary_custom_test(const string &name) { - RunLimit lim = conf_run_limit(0, RL_DEFAULT); +struct CustomTestConfig { + RunLimit base_limit = conf_run_limit(0, RL_DEFAULT); +}; + +CustomTestInfo ordinary_custom_test(const string &name, CustomTestConfig ctc = CustomTestConfig()) { + RunLimit lim = ctc.base_limit; lim.time += 2; string input_file_name = work_path + "/input.txt"; @@ -1343,21 +1488,132 @@ CustomTestInfo ordinary_custom_test(const string &name) { file_hide_token(output_file_name, conf_str("token", "")); } string info; - if (pro_ret.type == RS_AC) { + if (pro_ret.type == runp::RS_AC) { info = "Success"; } else { info = info_str(pro_ret.type); } string exp; - if (pro_ret.type == RS_TLE) { + if (pro_ret.type == runp::RS_TLE) { exp = "

[time limit: " + vtos(lim.time) + "s]

"; } return CustomTestInfo(pro_ret.ust, pro_ret.usm, info, exp, file_preview(output_file_name, 2048)); } -int scale_score(int scr100, int full) { - return scr100 * full / 100; +// classifications of tests +// primary tests = main tests + extra tests +// sample test data points are usually some of the extra test data points + +struct PrimaryDataTestConfig { + bool disable_ex_tests = false; +}; + +template +bool main_data_test(TP test_point_func) { + int n = conf_int("n_tests", 10); + int nT = conf_int("n_subtasks", 0); + + bool passed = true; + if (nT == 0) { // OI + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = test_point_func(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_subtask_meta_info(1).is_ordinary()) { // ACM + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = test_point_func(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); + } + } + } else { // subtask + map subtasks; + for (int t = 1; t <= nT; t++) { + SubtaskInfo st_info(conf_subtask_meta_info(t)); + if (!st_info.resolve_dependencies(subtasks)) { + st_info.info = "Skipped"; + } else { + for (int i : st_info.meta.points_id) { + report_judge_status_f("Judging Test #%d of Subtask #%d", i, t); + PointInfo po = test_point_func(i); + st_info.add_point_info(po); + if (st_info.early_stop) { + break; + } + } + } + subtasks[t] = st_info; + passed = passed && st_info.passed; + add_subtask_info(st_info); + } + } + if (passed) { + tot_score = 100; + } + return passed; +} + +template +bool ex_data_test(TP test_point_func) { + int m = conf_int("n_ex_tests", 0); + if (m > 0) { + for (int i = 1; i <= m; i++) { + report_judge_status_f("Judging Extra Test #%d", i); + PointInfo po = test_point_func(-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); + return false; + } + } + PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", ""); + add_point_info(po); + } + return true; +} + +template +bool primary_data_test(TP test_point_func, const PrimaryDataTestConfig &pdtc = PrimaryDataTestConfig()) { + bool passed = main_data_test(test_point_func); + if (passed && !pdtc.disable_ex_tests) { + passed = ex_data_test(test_point_func); + } + return passed; +} + +template +bool sample_data_test(TP test_point_func) { + 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_func(-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; + } + return passed; } /*====================== test End ================== */ @@ -1365,24 +1621,12 @@ int scale_score(int scr100, int full) { /*======================= conf init =================== */ void main_judger_init(int argc, char **argv) { - main_path = UOJ_WORK_PATH; - work_path = main_path + "/work"; - result_path = string(UOJ_RESULT_PATH); - load_config(work_path + "/submission.conf"); - problem_id = conf_int("problem_id"); - data_path = string(UOJ_DATA_PATH) + "/" + conf_str("problem_id"); - load_config(data_path + "/problem.conf"); - - executef("cp %s/require/* %s 2>/dev/null", data_path.c_str(), work_path.c_str()); - - if (conf_is("use_builtin_judger", "on")) { - config["judger"] = string(UOJ_WORK_PATH) + "/builtin/judger/judger"; - } else { - config["judger"] = data_path + "/judger"; - } + cerr << "main_judger_init is deprecated. use uoj_judger_v2 instead!" << endl; + exit(1); } void judger_init(int argc, char **argv) { if (argc != 5) { + cerr << "judger: argc != 5" << endl; exit(1); } main_path = argv[1]; @@ -1392,6 +1636,16 @@ void judger_init(int argc, char **argv) { load_config(work_path + "/submission.conf"); problem_id = conf_int("problem_id"); load_config(data_path + "/problem.conf"); + runp::run_path = main_path + "/run"; + + PointInfo::show_in = conf_str("show_in", "on") == "on"; + PointInfo::show_out = conf_str("show_out", "on") == "on"; + PointInfo::show_res = conf_str("show_res", "on") == "on"; + + if (chdir(work_path.c_str()) != 0) { + cerr << "invalid work path" << endl; + exit(1); + } if (config.count("use_builtin_checker")) { config["checker"] = main_path + "/builtin/checker/" + config["use_builtin_checker"]; diff --git a/judger/uoj_judger/include/uoj_judger_v2.h b/judger/uoj_judger/include/uoj_judger_v2.h new file mode 100644 index 0000000..e8f1598 --- /dev/null +++ b/judger/uoj_judger/include/uoj_judger_v2.h @@ -0,0 +1,2478 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "uoj_secure.h" +#include "uoj_run.h" + +namespace fs = std::filesystem; +using namespace std; + +/*========================== string ====================== */ + +inline string htmlspecialchars(const string &s) { + string r; + for (int i = 0; i < (int)s.length(); i++) { + switch (s[i]) { + case '&' : r += "&"; break; + case '<' : r += "<"; break; + case '>' : r += ">"; break; + case '"' : r += """; break; + case '\0': r += "\\0"; break; + default : r += s[i]; break; + } + } + return r; +} + +template +inline string list_to_string(const T &list) { + ostringstream sout; + sout << "{"; + bool is_first = false; + for (auto &t : list) { + if (!is_first) { + sout << ", "; + } + sout << t; + } + sout << "}"; + return sout.str(); +} + +/*========================== random ====================== */ + +inline string gen_token() { + u64 seed = time(NULL); + FILE *f = fopen("/dev/urandom", "r"); + if (f) { + for (int i = 0; i < 8; i++) + seed = seed << 8 | (u8)fgetc(f); + fclose(f); + } + uoj_mt_rand_engine rnd(seed); + return rnd.randstr(64); +} + +/*========================== crypto ====================== */ + +inline string file_get_contents(const string &name) { + string out; + FILE *f = fopen(name.c_str(), "r"); + if (!f) { + return out; + } + + const int BUFFER_SIZE = 1024; + u8 buffer[BUFFER_SIZE + 1]; + while (!feof(f)) { + int ret = fread(buffer, 1, BUFFER_SIZE, f); + if (ret < 0) { + break; + } + out.append((char *)buffer, ret); + } + fclose(f); + return out; +} +inline bool file_put_contents(const string &name, const string &m) { + FILE *f = fopen(name.c_str(), "w"); + if (!f) { + return false; + } + int c = fwrite(m.data(), 1, m.length(), f); + fclose(f); + return c == (int)m.length(); +} + +inline bool file_encrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + cipher.encrypt(m); + return file_put_contents(fo, m); +} +inline bool file_decrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + if (cipher.decrypt(m)) { + file_put_contents(fo, m); + return true; + } else { + file_put_contents(fo, "Unauthorized output"); + return false; + } +} + +/*========================= file ====================== */ + +string file_preview(const string &name, const int &len = 100) { + FILE *f = fopen(name.c_str(), "r"); + if (f == NULL) { + return ""; + } + + string res = ""; + if (len == -1) { + int c; + while (c = fgetc(f), c != EOF) { + res += c; + } + } else { + int c; + while (c = fgetc(f), c != EOF && (int)res.size() < len + 4) { + res += c; + } + if ((int)res.size() > len + 3) { + res.resize(len); + res += "..."; + } + } + fclose(f); + return res; +} +/* +void file_hide_token(const string &name, const string &token) { + executef("cp %s %s.bak", name.c_str(), name.c_str()); + + FILE *rf = fopen((name + ".bak").c_str(), "r"); + FILE *wf = fopen(name.c_str(), "w"); + int c; + for (int i = 0; i <= (int)token.length(); i++) + { + c = fgetc(rf); + if (c != (i < (int)token.length() ? token[i] : '\n')) + { + fprintf(wf, "Unauthorized output\n"); + fclose(rf); + fclose(wf); + return; + } + } + while (c = fgetc(rf), c != EOF) { + fputc(c, wf); + } + fclose(rf); + fclose(wf); +}*/ +int file_hide_token(const string &token, const string &in, const string &out) { + ifstream fin(in); + ofstream fout(out); + + if (!fin || !fout) { + return -1; + } + + string target = token + "\n"; + int L = max(1 << 15, (int)target.length()); + char buf[L]; + + fin.read(buf, target.length()); + if (fin.bad()) { + return -1; + } + if (!(fin.good() && equal(target.begin(), target.end(), buf))) { + fout << "???" << endl; + return 1; + } + + while (!fin.eof()) { + fin.read(buf, L); + fout.write(buf, fin.gcount()); + if (fin.bad()) { + return -1; + } + } + return 0; +} +bool file_replace_tokens(const string &name, const string &token, const string &new_token) { + string esc_name = escapeshellarg(name); + string esc_token = escapeshellarg(token); + string esc_new_token = escapeshellarg(new_token); + return executef("sed -i s/%s/%s/g %s", esc_token.c_str(), esc_new_token.c_str(), esc_name.c_str()) == 0; +} +bool file_copy(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("cp %s %s -f 2>/dev/null", esc_a.c_str(), esc_b.c_str()) == 0; // the most cubao implementation in the world +} +bool file_hardlink(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("cp %s %s -lf 2>/dev/null", esc_a.c_str(), esc_b.c_str()) == 0; // the most cubao implementation in the world +} +bool file_move(const string &a, const string &b) { // move a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("mv %s %s", esc_a.c_str(), esc_b.c_str()) == 0; // the most cubao implementation in the world +} + +/*======================= file End ==================== */ + +/*====================== parameter ==================== */ + +const runp::limits_t RL_DEFAULT(1, 256, 64); +const runp::limits_t RL_JUDGER_DEFAULT(600, 10 * 1024, 128); // 10GB. change it if needed +const runp::limits_t RL_CHECKER_DEFAULT(5, 256, 64); +const runp::limits_t RL_INTERACTOR_DEFAULT(1, 256, 64); +const runp::limits_t RL_VALIDATOR_DEFAULT(5, 256, 64); +const runp::limits_t RL_TRANSFORMER_DEFAULT(30, 512, 256); +const runp::limits_t RL_COMPILER_DEFAULT(15, 512, 64); + +struct InfoBlock { + string title; + string content; + int orig_size; + + static InfoBlock empty(const string &title) { + InfoBlock info; + info.title = title; + info.content = ""; + info.orig_size = -1; + return info; + } + static InfoBlock from_string(const string &title, const string &content) { + InfoBlock info; + info.title = title; + info.content = content; + info.orig_size = -1; + return info; + } + static InfoBlock from_file(const string &title, const string &name, int preview_size = 100) { + InfoBlock info; + info.title = title; + info.content = file_preview(name, preview_size); + info.orig_size = -1; + return info; + } + static InfoBlock from_file_with_size(const string &title, const string &name, int preview_size = 100) { + InfoBlock info; + info.title = title; + info.content = file_preview(name, preview_size); + info.orig_size = fs::file_size(name); + return info; + } + + string to_str() const { + if (title == "res") { + return "" + htmlspecialchars(content) + ""; + } + + string str = ""; + + return str; + } +}; + +struct PointInfo { + int num; + int scr = 0; + int ust = -1, usm = -1; + string info, res; + + vector li; + + static PointInfo extra_test_passed() { + PointInfo po; + po.num = -1; + po.info = "Extra Test Passed"; + return po; + } + + void set_info(const string &info) { + if (info == "default") { + if (scr == 0) { + this->info = "Wrong Answer"; + } else if (scr == 100) { + this->info = "Accepted"; + } else { + this->info = "Acceptable Answer"; + } + } else { + this->info = info; + } + } +}; + +/*struct CustomTestInfo { + int ust, usm; + string info, exp, out; + + CustomTestInfo(const int &_ust, const int &_usm, const string &_info, + const string &_exp, const string &_out) + : ust(_ust), usm(_usm), info(_info), + exp(_exp), out(_out) { + } +};*/ + +struct run_checker_result { + runp::RS_TYPE type; + int scr; + string info; + + static run_checker_result from_file(const string &file_name, const runp::result &rres) { + run_checker_result res; + res.type = rres.type; + + if (rres.type != runp::RS_AC) { + res.scr = 0; + } else { + FILE *fres = fopen(file_name.c_str(), "r"); + char type[21]; + if (fres == NULL || fscanf(fres, "%20s", type) != 1) { + return run_checker_result::failed_result(); + } + if (strcmp(type, "ok") == 0) { + res.scr = 100; + } else if (strcmp(type, "points") == 0) { + double d; + if (fscanf(fres, "%lf", &d) != 1) { + return run_checker_result::failed_result(); + } else { + res.scr = (int)floor(100 * d + 0.5); + } + } else { + res.scr = 0; + } + fclose(fres); + } + res.info = file_preview(file_name); + return res; + } + + static run_checker_result failed_result() { + run_checker_result res; + res.type = runp::RS_JGF; + res.scr = 0; + res.info = "Checker Failed"; + return res; + } +}; +struct run_validator_result { + runp::RS_TYPE type; + bool succeeded; + string info; + + static run_validator_result failed_result() { + run_validator_result res; + res.type = runp::RS_JGF; + res.succeeded = 0; + res.info = "Validator Failed"; + return res; + } +}; +struct run_compiler_result { + runp::RS_TYPE type; + bool succeeded; + string info; + + static run_compiler_result failed_result() { + run_compiler_result res; + res.type = runp::RS_JGF; + res.succeeded = false; + res.info = "Compile Failed"; + return res; + } +}; +struct run_transformer_result { + runp::RS_TYPE type; + bool succeeded; + string info; +}; + +// see also: run_simple_interaction +struct run_simple_interaction_result { + runp::result res; // prog + run_checker_result ires; // interactor +}; + +fs::path main_path; +fs::path work_path; +fs::path data_path; +fs::path result_path; + +int tot_time = 0; +int max_memory = 0; +int tot_score = 0; +string uoj_errcode; // empty means no error +ostringstream details_out; +map uconfig; + +void uoj_error(const string &_uoj_errcode) { + if (uoj_errcode.empty()) { + uoj_errcode = _uoj_errcode; + } +} + +class jgf_error : exception { +public: + string code; +protected: + string info; +public: + explicit jgf_error(const string &code, const string &info = "") : code(code), info(info) { + if (this->info.empty()) { + this->info = runp::rstype_str(runp::RS_JGF); + } + } + + virtual const char* what() const noexcept override { + return this->info.c_str(); + } +}; + +/*==================== parameter End ================== */ + +/*====================== config set =================== */ + +void print_config() { + for (auto &p : uconfig) { + cerr << p.first << " = " << p.second << endl; + } +} +void load_config(const string &filename) { + ifstream fin(filename.c_str()); + if (!fin) { + return; + } + string key, val; + while (fin >> key >> val) { + uconfig[key] = val; + } +} +string conf_str(const string &key, int num, const string &val) { + ostringstream sout; + sout << key << "_" << num; + if (uconfig.count(sout.str()) == 0) { + return val; + } + return uconfig[sout.str()]; +} +string conf_str(const string &key, const string &val) { + if (uconfig.count(key) == 0) { + return val; + } + return uconfig[key]; +} +string conf_str(const string &key) { + return conf_str(key, ""); +} +int conf_int(const string &key, const int &val) { + if (uconfig.count(key) == 0) { + return val; + } + return atoi(uconfig[key].c_str()); +} +int conf_int(const string &key, int num, const int &val) { + ostringstream sout; + sout << key << "_" << num; + if (uconfig.count(sout.str()) == 0) { + return conf_int(key, val); + } + return atoi(uconfig[sout.str()].c_str()); +} +int conf_int(const string &key) { + return conf_int(key, 0); +} +string conf_file_name_with_num(string s, int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str(s + "_pre", s) << abs(num) << "." << conf_str(s + "_suf", "txt"); + return name.str(); +} +string conf_input_file_name(int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str("input_pre", "input") << abs(num) << "." << conf_str("input_suf", "txt"); + return name.str(); +} +string conf_output_file_name(int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str("output_pre", "output") << abs(num) << "." << conf_str("output_suf", "txt"); + return name.str(); +} +runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) { + if (!pre.empty()) { + pre += "_"; + } + runp::limits_t limits; + limits.time = conf_int(pre + "time_limit", num, val.time); + limits.memory = conf_int(pre + "memory_limit", num, val.memory); + limits.output = conf_int(pre + "output_limit", num, val.output); + limits.real_time = conf_int(pre + "real_time_limit", num, val.real_time); + limits.stack = conf_int(pre + "stack_limit", num, val.real_time); + return limits; +} +runp::limits_t conf_run_limit(const int &num, const runp::limits_t &val) { + return conf_run_limit("", num, val); +} +void conf_add(const string &key, const string &val) { + if (uconfig.count(key)) { + return; + } + uconfig[key] = val; +} +bool conf_has(const string &key) { + return uconfig.count(key); +} +bool conf_is(const string &key, const string &val) { + return uconfig.count(key) && uconfig[key] == val; +} + +template +void map_add(map &m, const initializer_list> &vs) { + for (const auto &v : vs) { + if (!m.count(v.first)) { + m[v.first] = v.second; + } + } +} + +/*==================== config set End ================= */ + +/*====================== info print =================== */ + +void add_point_info(const PointInfo &info, bool update_tot_score = true) { + if (info.num >= 0) { + if(info.ust >= 0) { + tot_time += info.ust; + } + if(info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + } + if (update_tot_score) { + tot_score += info.scr; + } + + details_out << "" << endl; + + for (const InfoBlock &b : info.li) { + if (b.title == "input" && conf_str("show_in", "on") != "on") { + continue; + } + if (b.title == "output" && conf_str("show_out", "on") != "on") { + continue; + } + if (conf_str("show_" + b.title, "on") != "on") { + continue; + } + details_out << b.to_str() << endl; + } + if (conf_str("show_res", "on") == "on") { + details_out << "" << htmlspecialchars(info.res) << "" << endl; + } + details_out << "" << endl; +} +void add_custom_test_info(const PointInfo &info) { + if(info.ust >= 0) { + tot_time += info.ust; + } + if(info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + + details_out << "" << endl; + for (const InfoBlock &b : info.li) { + if (b.title == "input" && conf_str("show_in", "off") != "on") { + continue; + } + if (b.title == "output" && conf_str("show_out", "on") != "on") { + continue; + } + details_out << b.to_str() << endl; + } + if (conf_str("show_res", "on") == "on") { + details_out << "" << htmlspecialchars(info.res) << "" << endl; + } + details_out << "" << endl; +} +void add_subtask_info(const int &num, const int &scr, const string &info, const vector &points) { + details_out << "" << endl; + tot_score += scr; + for (const PointInfo &point : points) { + add_point_info(point, false); + } + details_out << "" << endl; +} +[[noreturn]] void end_judge_ok() { + ofstream fres(result_path / "result.txt"); + if (uoj_errcode.empty()) { + fres << "score " << tot_score << "\n"; + fres << "time " << tot_time << "\n"; + fres << "memory " << max_memory << "\n"; + } else { + fres << "error Judgment Failed\n"; + } + fres << "details\n"; + if (uoj_errcode.empty()) { + fres << "\n"; + } else { + fres << "\n"; + } + fres << details_out.str(); + fres << "\n"; + fres.close(); + exit(uoj_errcode.empty() && fres ? 0 : 1); +} +[[noreturn]] void end_judge_judgment_failed(const string &info) { + ofstream fres(result_path / "result.txt"); + fres << "error Judgment Failed\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(info) << "\n"; + fres.close(); + exit(0); +} +[[noreturn]] void end_judge_compile_error(const run_compiler_result &res) { + ofstream fres(result_path / "result.txt"); + fres << "error Compile Error\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(res.info) << "\n"; + fres.close(); + exit(0); +} + +void report_judge_status(const char *status) { + FILE *f = fopen((result_path / "cur_status.txt").c_str(), "a"); + if (f == NULL) { + return; + } + if (flock(fileno(f), LOCK_EX) != -1) { + if (ftruncate(fileno(f), 0) != -1) { + fprintf(f, "%s\n", status); + fflush(f); + } + flock(fileno(f), LOCK_UN); + } + fclose(f); +} +bool report_judge_status_f(const char *fmt, ...) { + const int MaxL = 512; + char status[MaxL]; + va_list ap; + va_start(ap, fmt); + int res = vsnprintf(status, MaxL, fmt, ap); + if (res < 0 || res >= MaxL) { + return false; + } + report_judge_status(status); + va_end(ap); + return true; +} + +/*==================== info print End ================= */ + +/*========================== run ====================== */ + +// namespace for run_program +namespace runp { + // internal programs for run_program + namespace internal { + result nonempty(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 3) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + ofstream ferr(rpc.error_file_name); + + if (fs::file_size(rpc.rest_args[1]) > 0) { + ferr << "ok nonempty file" << endl; + } else { + ferr << "wrong answer empty file" << endl; + } + res.exit_code = 0; + res.type = RS_AC; + return res; + } + + result cp(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 2) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + if (file_hardlink(rpc.rest_args[0], rpc.rest_args[1]) || file_copy(rpc.rest_args[0], rpc.rest_args[1])) { + res.exit_code = 0; + res.type = RS_AC; + } else { + res.exit_code = -1; + res.type = RS_RE; + } + return res; + } + + result hide_token(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 2) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + int ret = file_hide_token(conf_str("token", ""), rpc.rest_args[0], rpc.rest_args[1]); + if (ret == -1) { + res.exit_code = -1; + res.type = RS_RE; + } else { + res.type = RS_AC; + res.exit_code = ret; + if (res.exit_code != 0) { + ofstream ferr(rpc.error_file_name); + ferr << "Invalid Output" << endl; + } + } + return res; + } + + map> call_table = { + {"nonempty", nonempty}, + {"cp", cp}, + {"hide_token", hide_token} + }; + } + + result run(const config &rpc) { + if (rpc.type == "internal" && internal::call_table.count(rpc.program_name)) { + return internal::call_table[rpc.program_name](rpc); + } else if (rpc.program_name.empty()) { + throw jgf_error("RPFALEM"); // run_program failed, because program is empty + } + string cmd = rpc.get_cmd(); + if (execute(rpc.get_cmd()) != 0) { + uoj_error("RPFAL"); // run_program failed + return result::failed_result(); + } + return result::from_file(rpc.result_file_name); + } +} + +// for all run_xxx functions, rpc is passed +// rpc should set program name, args, type, limits, readable/writable files +// run_xxx will fill in in/out/err/res file names, and may reset args, readable files, etc. +run_validator_result run_validator(runp::config rpc, const string &input_file_name) { + rpc.result_file_name = result_path / "run_validator_result.txt"; + rpc.input_file_name = input_file_name; + rpc.output_file_name = "/dev/null"; + rpc.error_file_name = result_path / "validator_error.txt"; + runp::result ret = runp::run(rpc); + + run_validator_result res; + res.type = ret.type; + if (ret.type != runp::RS_AC || ret.exit_code != 0) { + res.succeeded = false; + res.info = file_preview(rpc.error_file_name); + } else { + res.succeeded = true; + } + return res; +} + +run_checker_result run_checker(runp::config rpc, + const string &input_file_name, + const string &output_file_name, + const string &answer_file_name) { + rpc.result_file_name = result_path / "run_checker_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "/dev/null"; + rpc.error_file_name = result_path / "checker_error.txt"; + rpc.readable_file_names.insert(rpc.readable_file_names.end(), { + input_file_name, output_file_name, answer_file_name + }); + rpc.rest_args = { + fs::canonical(input_file_name), + fs::canonical(output_file_name), + fs::canonical(answer_file_name) + }; + return run_checker_result::from_file( + rpc.error_file_name, + runp::run(rpc) + ); +} + +template +run_compiler_result run_compiler(runp::config rpc) { + rpc.result_file_name = result_path / "run_compiler_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "stderr"; + rpc.error_file_name = result_path / "compiler_result.txt"; + rpc.type = "compiler"; + + runp::result ret = runp::run(rpc); + run_compiler_result res; + res.type = ret.type; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; + if (!res.succeeded) { + if (ret.type == runp::RS_AC) { + res.info = file_preview(result_path / "compiler_result.txt", 10240); + } else if (ret.type == runp::RS_JGF) { + res.info = "No Comment"; + } else { + res.info = "Compiler " + runp::rstype_str(ret.type); + } + } + return res; +} + +runp::result run_submission_program(runp::config rpc, const string &input_file_name, const string &output_file_name) { + rpc.result_file_name = result_path / "run_submission_program.txt"; + rpc.input_file_name = input_file_name; + rpc.output_file_name = output_file_name; + rpc.error_file_name = "/dev/null"; + + runp::result res = runp::run(rpc); + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + return res; +} + +void prepare_interactor() { + static bool prepared = false; + if (prepared) { + return; + } + string data_path_std = data_path / "interactor"; + string work_path_std = work_path / "interactor"; + executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); + conf_add("interactor_language", "C++"); + prepared = true; +} + +// simple: prog <---> interactor <---> data +run_simple_interaction_result run_simple_interaction( + const string &input_file_name, + const string &answer_file_name, + const string &real_input_file_name, + const string &real_output_file_name, + runp::config rpc, + runp::config irpc) { + prepare_interactor(); + + rpc.result_file_name = result_path / "run_submission_program.txt"; + rpc.input_file_name = "stdin"; + rpc.output_file_name = "stdout"; + rpc.error_file_name = "/dev/null"; + + irpc.result_file_name = result_path / "run_interactor_program.txt"; + irpc.readable_file_names.push_back(input_file_name); + irpc.readable_file_names.push_back(answer_file_name); + irpc.input_file_name = "stdin"; + irpc.output_file_name = "stdout"; + irpc.error_file_name = result_path / "interactor_error.txt"; + irpc.program_name = data_path / "interactor"; + irpc.rest_args = { + input_file_name, + "/dev/stdin", + answer_file_name + }; + irpc.limits.real_time = rpc.limits.real_time = rpc.limits.time + irpc.limits.time + 1; + + runp::interaction::config ric; + ric.cmds.push_back(rpc.get_cmd()); + ric.cmds.push_back(irpc.get_cmd()); + + // from:fd, to:fd + ric.pipes.push_back(runp::interaction::pipe_config(2, 1, 1, 0, real_input_file_name)); + ric.pipes.push_back(runp::interaction::pipe_config(1, 1, 2, 0, real_output_file_name)); + + runp::interaction::run(ric); + + run_simple_interaction_result rires; + runp::result res = runp::result::from_file(rpc.result_file_name); + run_checker_result ires = run_checker_result::from_file(irpc.error_file_name, runp::result::from_file(irpc.result_file_name)); + + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + + if (ires.type == runp::RS_JGF) { + ires.info = "Interactor Judgment Failed"; + } + if (ires.type == runp::RS_TLE) { + ires.type = runp::RS_AC; + res.type = runp::RS_TLE; + } + + rires.res = res; + rires.ires = ires; + return rires; +} + +run_transformer_result transform_file(runp::config rpc, const string &in, const string &out) { + run_transformer_result res; + if (in == out) { + res.type = runp::RS_AC; + res.succeeded = true; + } else { + rpc.result_file_name = result_path / "run_trans_result.txt"; + rpc.input_file_name = in; + rpc.output_file_name = out; + rpc.error_file_name = result_path / "trans_error.txt"; + runp::result ret = runp::run(rpc); + res.type = ret.type; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; + if (!res.succeeded) { + res.info = file_preview(rpc.error_file_name); + } + } + return res; +} + +/*======================== run End ==================== */ + +/*======================== compile ==================== */ + +run_compiler_result compile(const string &name) { + string lang = conf_str(name + "_language"); + runp::config rpc(main_path / "run" / "compile", { + "--custom", main_path / "run" / "runtime", + "--lang", lang, + name + }); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +run_compiler_result compile_with_implementer(const string &name, const string &implementer = "implementer") { + string lang = conf_str(name + "_language"); + if (conf_has(name + "_unit_name")) { + file_put_contents(work_path / (name + ".unit_name"), conf_str(name + "_unit_name")); + } + runp::config rpc(main_path / "run" / "compile", { + "--custom", main_path / "run" / "runtime", + "--impl", implementer, "--lang", lang, + name + }); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +/*====================== compile End ================== */ + +/*====================== test ================== */ + +/* +struct TestPointConfig { + int submit_answer; + + int validate_input_before_test; + string input_file_name; + string output_file_name; + string answer_file_name; + + TestPointConfig() + : submit_answer(-1), validate_input_before_test(-1) { + } + + void auto_complete(int num) { + if (submit_answer == -1) { + submit_answer = conf_is("submit_answer", "on"); + } + if (validate_input_before_test == -1) { + validate_input_before_test = conf_is("validate_input_before_test", "on"); + } + if (input_file_name.empty()) { + input_file_name = data_path + "/" + conf_input_file_name(num); + } + if (output_file_name.empty()) { + output_file_name = work_path + "/" + conf_output_file_name(num); + } + if (answer_file_name.empty()) { + answer_file_name = data_path + "/" + conf_output_file_name(num); + } + } +}; +*/ + +/* + base class for testing one point + + for each member function, it returns false only if an internal error + occurs (due to OS, File System, or problem setter). + po.info stores the information that will be shown to the user the + first function returning false is responsible to set po.info and set + uoj_error with an error code. + + if an error occurs due to the user (e.g., the user's program RE), + po.info is set while the member function does not have to return false. + To check errors regardless its origin, use check(), which returns + false if po.info is non-empty and po.scr != 100. +*/ +class TestPoint { +public: + typedef int Author; + static const int AUTHOR_USERBIT = 1 << 8; + static const char TITLE_DISABLED[]; + static const char AUTHOR_NONAME[]; + + enum { + AUTHOR_SETTER = 0, + AUTHOR_STD + }; + enum { + AUTHOR_USER = AUTHOR_USERBIT, + AUTHOR_USER2 + }; + + int num = 0; + int validate_input_before_test = -1; + map fname; + map ftitle; + map fpreview_size; + map fauthor; + map author_name { + {AUTHOR_SETTER, "Problem Setter"}, + {AUTHOR_STD, "Standard Program"}, + {AUTHOR_USER, AUTHOR_NONAME} + }; + map program; + + PointInfo test(); + PointInfo hack_test(); + PointInfo custom_test(); + +private: + void config_sanity_check(); + +protected: + PointInfo po; + + fs::path get_fname(const string &fkey, const string &default_val = ""); + string get_basename(const string &key); + int get_fpreview_size(const string &fkey, int default_val = 100); + + runp::config get_program(const string &id); + runp::config search_program(const initializer_list &ids); + + void set_res_if_empty(const string &res); + void set_info_if_empty(const string &info); + void set_info_if_empty(const string &info, Author author); + bool add_info_block(const string &fkey); + + bool check(); + bool prepare_io(); + bool preprocess_input(const string &input); + bool postprocess_output(const string &output); + bool validate(const string &fkey); + bool encode(const string &input); + bool encrypt(const string &input); + bool decrypt(const string &output); + bool decode(const string &output); + bool grade(const string &input, const string &output, const string &answer); + bool run_custom(const string &id, const string &input, const string &output); + + virtual void complete_basic_config(); + virtual void complete_config(); + virtual void complete_hack_config(); + virtual void complete_custom_test_config(); + + virtual bool generate_answer(const string &input, const string &answer); + + virtual bool _test(); + virtual bool _hack_test(); + virtual bool _custom_test(); +}; + +const char TestPoint::TITLE_DISABLED[] = "disabled"; +const char TestPoint::AUTHOR_NONAME[] = "noname"; + +PointInfo TestPoint::test() { + try { + complete_basic_config(); + complete_config(); + config_sanity_check(); + _test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + return po; +} + +PointInfo TestPoint::hack_test() { + try { + complete_basic_config(); + complete_hack_config(); + config_sanity_check(); + _hack_test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + if (uoj_errcode.empty()) { + po.scr = po.scr != 100; + } + return po; +} + +PointInfo TestPoint::custom_test() { + try { + complete_basic_config(); + complete_custom_test_config(); + config_sanity_check(); + _custom_test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + return po; +} + +void TestPoint::config_sanity_check() { + if (validate_input_before_test == -1) { + throw jgf_error("CFGERR1"); // config error + } + for (const auto &ft : ftitle) { + if (!fname.count(ft.first)) { + throw jgf_error("CFGERR2"); // config error + } + } + for (const auto &fp : fpreview_size) { + if (!fname.count(fp.first)) { + throw jgf_error("CFGERR3"); // config error + } + } + for (const auto &fn : fname) { + if (!fauthor.count(get_basename(fn.first))) { + throw jgf_error("CFGERR4"); // config error + } + if (!author_name.count(fauthor[get_basename(fn.first)])) { + throw jgf_error("CFGERR5"); // config error + } + } +} + +fs::path TestPoint::get_fname(const string &fkey, const string &default_val) { + return fname.count(fkey) ? fname[fkey] : fs::path(conf_str(fkey, default_val)); +} + +string TestPoint::get_basename(const string &key) { + size_t pos = key.find_last_of('.'); + if (pos == string::npos) { + return key; + } else { + return key.substr(0, pos); + } +} + +int TestPoint::get_fpreview_size(const string &fkey, int default_val) { + return fpreview_size.count(fkey) ? fpreview_size[fkey] : default_val; +} + +runp::config TestPoint::get_program(const string &id) { + try { + return program.at(id); + } catch (out_of_range &e) { + throw jgf_error("PROGNF1", "Program " + id + " Not Found"); // program not found + } +} +runp::config TestPoint::search_program(const initializer_list &ids) { + for (auto &id : ids) { + if (program.count(id)) { + return program[id]; + } + } + throw jgf_error("PROGNF2", "Program " + list_to_string(ids) + " Not Found"); // program not found +} + +void TestPoint::set_res_if_empty(const string &res) { + if (po.res.empty()) { + po.res = res; + } +} + +void TestPoint::set_info_if_empty(const string &info) { + if (po.info.empty()) { + po.set_info(info); + } +} + +void TestPoint::set_info_if_empty(const string &info, Author author) { + if (po.info.empty()) { + if (!author_name.count(author)) { + po.set_info(info); + } else { + po.set_info(info + " (" + author_name[author] + ")"); + } + } +} + +bool TestPoint::add_info_block(const string &fkey) { + if (ftitle.count(fkey) && ftitle[fkey] != "disabled") { + po.li.push_back(InfoBlock::from_file_with_size(ftitle[fkey], fname[fkey], get_fpreview_size(fkey))); + } + return true; +} + +bool TestPoint::check() { + return po.info.empty() || po.scr == 100; +} + +bool TestPoint::prepare_io() { + try { + fs::remove_all(work_path / "io"); + fs::create_directories(work_path / "io"); + return true; + } catch (exception &e) { + throw jgf_error("PPIOFAL"); // prepare_io failed + } +} + +bool TestPoint::preprocess_input(const string &input) { + return validate(input) && check() + && encode(input) && check() + && encrypt(input) && check(); +} + +bool TestPoint::postprocess_output(const string &output) { + return decrypt(output) && check() + && decode(output) && check(); +} + +bool TestPoint::validate(const string &fkey) { + if (!validate_input_before_test) { + return true; + } + + run_validator_result ret = run_validator(get_program("val"), fname[fkey]); + if (ret.type != runp::RS_AC) { + throw jgf_error("VALFAL", "Validator " + runp::rstype_str(ret.type)); // validator failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[fkey]); + if (!(fauthor[fkey] & AUTHOR_USERBIT)) { + throw jgf_error("INVINV"); // invalid input (found by val) + } + } + return true; +} + +bool TestPoint::encode(const string &input) { + run_transformer_result ret = transform_file( + search_program({input + "encoder", "encoder"}), + fname[input], fname[input + ".plain"] + ); + add_info_block(input + ".plain"); + if (ret.type != runp::RS_AC) { + throw jgf_error("ECOFAL", "Encoder " + runp::rstype_str(ret.type)); // encoder failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[input]); + if (!(fauthor[input] & AUTHOR_USERBIT)) { + throw jgf_error("INVINO"); // invalid input (found by encode) + } + } + return true; +} + +bool TestPoint::encrypt(const string &input) { + run_transformer_result ret = transform_file( + search_program({input + "encrypter", "encrypter"}), + fname[input + ".plain"], fname[input + ".raw"] + ); + add_info_block(input + ".raw"); + if (ret.type != runp::RS_AC) { + throw jgf_error("ECRFAL", "Encrypter " + runp::rstype_str(ret.type)); // encrypter failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[input]); + if (!(fauthor[input] & AUTHOR_USERBIT)) { + throw jgf_error("INVINC"); // invalid input (found by encrypt) + } + } + return true; +} + +bool TestPoint::decrypt(const string &output) { + run_transformer_result ret = transform_file( + search_program({output + "decrypter", "decrypter"}), + fname[output + ".raw"], fname[output + ".plain"] + ); + add_info_block(output + ".plain"); + if (ret.type != runp::RS_AC) { + throw jgf_error("DCRFAL", "Decrypter " + runp::rstype_str(ret.type)); // decrypter failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + if (!(fauthor[output] & AUTHOR_USERBIT)) { + set_info_if_empty("Invalid Output", fauthor[output]); + throw jgf_error("INTWAC"); // internal WA (found by encrypt) + } + set_info_if_empty("Wrong Answer", fauthor[output]); + } + return true; +} + +bool TestPoint::decode(const string &output) { + run_transformer_result ret = transform_file( + search_program({output + "decoder", "decoder"}), + fname[output + ".plain"], fname[output] + ); + add_info_block(output); + if (ret.type != runp::RS_AC) { + throw jgf_error("DCOFAL", "Decrypter " + runp::rstype_str(ret.type)); // decoder failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + if (!(fauthor[output] & AUTHOR_USERBIT)) { + set_info_if_empty("Invalid Output", fauthor[output]); + throw jgf_error("INTWAO"); // internal WA (found by encode) + } + set_info_if_empty("Wrong Answer", fauthor[output]); + } + return true; +} + +bool TestPoint::generate_answer(const string &input, const string &answer) { + runp::result ret = run_submission_program( + get_program("std"), + fname["input.raw"], fname["answer.raw"] + ); + if (ret.type != runp::RS_AC) { + set_info_if_empty("Standard Program " + runp::rstype_str(ret.type)); + throw jgf_error("STDFAL"); // std failed + } + return true; +} + +bool TestPoint::grade(const string &input, const string &output, const string &answer) { + run_checker_result ret = run_checker( + get_program("chk"), + fname[input], fname[output], fname[answer] + ); + if (ret.type != runp::RS_AC) { + uoj_error("CHKFAL"); // checker failed + set_info_if_empty("Checker " + runp::rstype_str(ret.type)); + return false; + } else { + po.scr = ret.scr; + set_res_if_empty(ret.info); + set_info_if_empty("default"); + } + return true; +} + +bool TestPoint::run_custom(const string &id, const string &input, const string &output) { + if (!program.count(id)) { + throw jgf_error("PRGNF"); // program not found + } + + runp::result ret = run_submission_program( + get_program(id), + fname[input + ".raw"], fname[output + ".raw"] + ); + set_res_if_empty(ret.extra); + if (ret.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(ret.type)); + if (ret.type == runp::RS_JGF) { + throw jgf_error("RPUSRJGF"); // run_program(user's program) JGF + } + } else { + set_info_if_empty("Success"); + po.scr = 100; + po.ust = ret.ust; + po.usm = ret.usm; + } + return true; +} + +void TestPoint::complete_basic_config() { + po.num = num; + if (validate_input_before_test == -1) { + validate_input_before_test = conf_is("validate_input_before_test", "on"); + } +} + +void TestPoint::complete_config() { + map_add(fauthor, { + {"input", AUTHOR_SETTER}, + {"output", AUTHOR_USER}, + {"answer", AUTHOR_SETTER} + }); + + map_add(fname, { + {"input", data_path / conf_input_file_name(num)}, + {"input.plain", work_path / "io" / "plain_input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"}, + {"output", work_path / "io" / "output.txt"}, + {"answer", data_path / conf_output_file_name(num)} + }); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, { + {"input", "input"}, + {"output.plain", "output"} + }); + } else { + map_add(ftitle, { + {"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"} + }); + } +} + +void TestPoint::complete_hack_config() { + map_add(fauthor, { + {"input", AUTHOR_USER}, + {"output", AUTHOR_USER}, + {"answer", AUTHOR_STD} + }); + + map_add(fname, { + {"input", work_path / "hack_input.txt"}, + {"input.plain", work_path / "io" / "plain_input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"}, + {"output", work_path / "pro_output.txt"}, + {"answer.raw", work_path / "io" / "raw_std_output.txt"}, + {"answer.plain", work_path / "io" / "plain_std_output.txt"}, + {"answer", work_path / "std_output.txt"} + }); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, { + {"input", "input"}, + {"output.plain", "output"} + }); + } else { + map_add(ftitle, { + {"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"} + }); + } +} + +void TestPoint::complete_custom_test_config() { + map_add(fauthor, { + {"input", AUTHOR_USER}, + {"output", AUTHOR_USER} + }); + + map_add(fname, { + {"input.plain", work_path / "input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"} + }); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, { + {"input", "input"}, + {"output.plain", "output"} + }); + } else { + map_add(ftitle, { + {"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"} + }); + } + + map_add(fpreview_size, { + {"output.plain", 2048} + }); +} + +bool TestPoint::_test() { + uoj_error("TPTSTCAL"); // TestPoint::_test() is called + po.set_info(runp::rstype_str(runp::RS_JGF)); + return false; +} +bool TestPoint::_hack_test() { + uoj_error("TPHKTCAL"); // TestPoint::_hack_test() is called + po.set_info(runp::rstype_str(runp::RS_JGF)); + return false; +} +bool TestPoint::_custom_test() { + return prepare_io() && add_info_block("input") && encrypt("input") + && run_custom("answer", "input", "output") && decrypt("output") && check(); +} + +class SubmitAnswerTestPoint : public TestPoint { +protected: + virtual void complete_config() override { + map_add(fname, { + {"input", data_path / conf_input_file_name(num)}, + {"input.plain", data_path / conf_input_file_name(num)}, + {"output.plain", work_path / conf_output_file_name(num)}, + {"output", work_path / conf_output_file_name(num)}, + {"answer", data_path / conf_output_file_name(num)} + }); + TestPoint::complete_config(); + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && encode("input") && check() + && decode("output") && check() + && grade("input", "output", "answer") && check(); + } +}; + +class SingleProgramTestPoint : public TestPoint { +protected: + virtual bool run() { + if (!program.count("answer")) { + uoj_error("PRGNF"); // program not found + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + return false; + } + + runp::result ret = run_submission_program( + get_program("answer"), + fname["input.raw"], + fname["output.raw"] + ); + if (ret.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(ret.type)); + set_res_if_empty(ret.extra); + if (ret.type == runp::RS_JGF) { + uoj_error("RPUSRJGF"); // run_program(user's program) JGF + return false; + } + } else { + po.ust = ret.ust; + po.usm = ret.usm; + } + return true; + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") + && run() && postprocess_output("output") + && grade("input", "output", "answer") && check(); + } + + virtual bool _hack_test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") + && generate_answer("input", "answer") && postprocess_output("answer") + && run() && postprocess_output("output") + && grade("input", "output", "answer") && check(); + } +}; + +class SimpleInteractionTestPoint : public SingleProgramTestPoint { +public: + enum { + AUTHOR_INTERACTOR = 1 << 4 + }; + +protected: + virtual void complete_config() override { + map_add(fauthor, { + {"program_input", AUTHOR_INTERACTOR} + }); + map_add(author_name, { + {AUTHOR_INTERACTOR, "Interactor"} + }); + map_add(fname, { + {"program_input.raw", work_path / "io" / "raw_program_input.txt"}, + {"program_input.plain", work_path / "io" / "plain_program_input.txt"} + }); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, { + {"input", "input_to_interactor"}, + {"input.plain", TITLE_DISABLED}, + {"program_input.plain", "input"} + }); + } + SingleProgramTestPoint::complete_config(); + } + + virtual bool run() { + if (!program.count("answer") || !program.count("interactor")) { + uoj_error("PRGNF"); // program not found + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + return false; + } + + // conf_run_limit(num, RL_DEFAULT), + // conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), + run_simple_interaction_result rires = run_simple_interaction( + fname["input.raw"], + fname["answer"], + fname["program_input.raw"], + fname["output.raw"], + get_program("answer"), + get_program("interactor") + ); + + if (rires.ires.type != runp::RS_AC) { + uoj_error("INTFAL"); // interactor failed + set_info_if_empty("Interactor " + runp::rstype_str(rires.ires.type)); + return false; + } else if (rires.res.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(rires.res.type)); + if (rires.res.type == runp::RS_JGF) { + uoj_error("RPUSRJGF"); // run_program(user's program) JGF + return false; + } + } else { + po.scr = rires.ires.scr; + po.ust = rires.res.ust; + po.usm = rires.res.usm; + po.res = rires.ires.info; + po.set_info("default"); + } + return true; + } + + virtual bool run_std() { + // ??? + return true; + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") + && run() && decrypt("program_input") && decrypt("output") && check(); + } + + virtual bool _hack_test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") + // && run_std() + && run() && decrypt("program_input") && decrypt("output") && check(); + } +}; + +/* +class TwoRoundTestPoint : public TestPoint { +public: + string alice_name = "alice"; + string bob_name = "bob"; + +protected: + virtual void complete_config() override { + TestPoint::complete_config(); + } + + virtual bool transport() { + return true; + } + + virtual bool run_alice() { + return true; + } + + virtual bool run_bob() { + return true; + } + + virtual bool _test() override { + return prepare_io() && prepare_input("input") + && run_alice() && decrypt("alice_output") && check() + && decode("alice_output") && check() + && transport() + && prepare_input("bob_input") + && run_bob() && decrypt("bob_output") && check() + && decode("bob_output") && check() + && grade() && check(); + } +};*/ + +/* +PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) { + tpc.auto_complete(num); + + if (tpc.validate_input_before_test) { + RunValidatorResult val_ret = run_validator( + tpc.input_file_name, + conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Validator " + runp::rstype_str(val_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } else if (!val_ret.succeeded) { + return PointInfo(num, 0, -1, -1, + "Invalid Input", + file_preview(tpc.input_file_name), "", + val_ret.info); + } + } + + if (!conf_is("interaction_mode", "on")) { + runp::result pro_ret; + if (!tpc.submit_answer) { + pro_ret = run_submission_program( + tpc.input_file_name.c_str(), + tpc.output_file_name.c_str(), + conf_run_limit(num, RL_DEFAULT), + name); + if (conf_has("token")) { + file_hide_token(tpc.output_file_name, conf_str("token", "")); + } + if (pro_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + runp::rstype_str(pro_ret.type), + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + ""); + } + } else { + pro_ret.type = RS_AC; + pro_ret.ust = -1; + pro_ret.usm = -1; + pro_ret.exit_code = 0; + } + + RunCheckerResult chk_ret = run_checker( + conf_run_limit("checker", num, RL_CHECKER_DEFAULT), + conf_str("checker"), + tpc.input_file_name, + tpc.output_file_name, + tpc.answer_file_name); + if (chk_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Checker " + runp::rstype_str(chk_ret.type), + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + ""); + } + + return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, + "default", + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + chk_ret.info); + } else { + string real_output_file_name = tpc.output_file_name + ".real_input.txt"; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, + tpc.answer_file_name, + real_input_file_name, + real_output_file_name, + conf_run_limit(num, RL_DEFAULT), + conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), + name); + + if (rires.ires.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Interactor " + runp::rstype_str(rires.ires.type), + file_preview(real_input_file_name), file_preview(real_output_file_name), + ""); + } + if (rires.res.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + runp::rstype_str(rires.res.type), + file_preview(real_input_file_name), file_preview(real_output_file_name), + ""); + } + + return PointInfo(num, rires.ires.scr, rires.res.ust, rires.res.usm, + "default", + file_preview(real_input_file_name), file_preview(real_output_file_name), + rires.ires.info); + } +}*/ + +/* +PointInfo test_hack_point(const string &name, TestPointConfig tpc) { + tpc.submit_answer = false; + tpc.validate_input_before_test = false; + tpc.auto_complete(0); + RunValidatorResult val_ret = run_validator( + tpc.input_file_name, + conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Validator " + runp::rstype_str(val_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } else if (!val_ret.succeeded) { + return PointInfo(0, 0, -1, -1, + "Invalid Input", + file_preview(tpc.input_file_name), "", + val_ret.info); + } + + RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT); + + prepare_run_standard_program(); + if (!conf_is("interaction_mode", "on")) { + runp::config rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + runp::result std_ret = run_submission_program( + tpc.input_file_name, + tpc.answer_file_name, + conf_run_limit("standard", 0, default_std_run_limit), + "std", + rpc); + if (std_ret.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Standard Program " + runp::rstype_str(std_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } + if (conf_has("token")) { + file_hide_token(tpc.answer_file_name, conf_str("token", "")); + } + } else { + runp::config rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + string real_output_file_name = tpc.answer_file_name; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, + tpc.answer_file_name, + real_input_file_name, + real_output_file_name, + conf_run_limit("standard", 0, default_std_run_limit), + conf_run_limit("interactor", 0, RL_INTERACTOR_DEFAULT), + "std", + rpc); + + if (rires.ires.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Interactor " + runp::rstype_str(rires.ires.type) + " (Standard Program)", + file_preview(real_input_file_name), "", + ""); + } + if (rires.res.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Standard Program " + runp::rstype_str(rires.res.type), + file_preview(real_input_file_name), "", + ""); + } + } + + PointInfo po = test_point(name, 0, tpc); + po.scr = po.scr != 100; + return po; +}*/ + +/* +CustomTestInfo ordinary_custom_test(const string &name) { + RunLimit lim = conf_run_limit(0, RL_DEFAULT); + lim.time += 2; + + string input_file_name = work_path + "/input.txt"; + string output_file_name = work_path + "/output.txt"; + + esult pro_ret = run_submission_program( + input_file_name, + output_file_name, + lim, + name); + if (conf_has("token")) { + file_hide_token(output_file_name, conf_str("token", "")); + } + string info; + if (pro_ret.type == RS_AC) { + info = "Success"; + } else { + info = runp::rstype_str(pro_ret.type); + } + string exp; + if (pro_ret.type == RS_TLE) { + exp = "

[time limit: " + vtos(lim.time) + "s]

"; + } + return CustomTestInfo(pro_ret.ust, pro_ret.usm, + info, exp, file_preview(output_file_name, 2048)); +} +*/ + +int scale_score(int scr100, int full) { + return scr100 * full / 100; +} + +/*====================== test End ================== */ + +/*====================== judger ================== */ + +struct SubtaskInfo { + bool passed; + int score; + + SubtaskInfo() { + } + SubtaskInfo(const bool &_p, const int &_s) + : passed(_p), score(_s){} +}; + +class Judger { +protected: + map program; + + bool add_program(const string &id, const runp::config &candidate) { + if (program.count(id)) { + return false; + } + if (candidate.type == "internal") { + program[id] = candidate; + return true; + } + if (fs::exists(candidate.program_name)) { + program[id] = candidate; + return true; + } + return false; + } + + bool compile_and_add_program(const string &name) { + run_compiler_result c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer"); + if (!c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + + runp::config rpc("./" + name); + rpc.set_type(runp::get_type_from_lang(conf_str(name + "_language"))); + add_program(name, rpc); + return true; + } + + void add_readable_to_program(runp::config &rpc) { + int p = 1; + while (true) { + string fname = conf_str("readable", p, ""); + if (fname.empty()) { + break; + } + if (fname[0] != '/') { + fname = work_path / fname; + } + rpc.readable_file_names.push_back(fname); + p++; + } + } + + void add_writable_to_program(runp::config &rpc) { + int p = 1; + while (true) { + string fname = conf_str("writable", p, ""); + if (fname.empty()) { + break; + } + if (fname[0] != '/') { + fname = work_path / fname; + } + rpc.writable_file_names.push_back(fname); + p++; + } + } + + virtual void configure_programs_for_point(int num) { + if (program.count("std")) { + program["std"].limits = conf_run_limit("standard", num, conf_run_limit(num, RL_DEFAULT)); + } + if (program.count("chk")) { + program["chk"].limits = conf_run_limit("checker", num, RL_CHECKER_DEFAULT); + } + if (program.count("val")) { + program["val"].limits = conf_run_limit("validator", num, RL_VALIDATOR_DEFAULT); + } + + for (auto &name : {"encoder", "encrypter", "decrypter", "decoder"}) { + if (program.count(name)) { + program[name].limits = conf_run_limit(name, num, RL_TRANSFORMER_DEFAULT); + } + } + } + + virtual void prepare() { + if (conf_has("use_builtin_checker")) { + add_program("chk", runp::config(main_path / "builtin" / "checker" / conf_str("use_builtin_checker"))); + } + + // search for chk, val, std, encoder, decoder, etc. + for (auto &p : fs::directory_iterator(data_path)) { + if ((p.status().permissions() & fs::perms::others_exec) != fs::perms::none) { + add_program(p.path().filename(), runp::config(p.path())); + } + } + + add_program("encoder", runp::config("cp").set_type("internal")); + add_program("encrypter", runp::config("cp").set_type("internal")); + if (conf_has("token")) { + add_program("decrypter", runp::config("hide_token").set_type("internal")); + } else { + add_program("decrypter", runp::config("cp").set_type("internal")); + } + add_program("decoder", runp::config("cp").set_type("internal")); + + for (auto &kv : program) { + add_readable_to_program(kv.second); + add_writable_to_program(kv.second); + } + } + + virtual void prepare_ordinary_test() { + } + + virtual void prepare_sample_test() { + } + + virtual void prepare_hack_test() { + } + + virtual void prepare_custom_test() { + } + + virtual PointInfo test_point(int num) { + end_judge_judgment_failed(runp::rstype_str(runp::RS_JGF)); + } + + virtual PointInfo test_sample_point(int num) { + end_judge_judgment_failed("Sample test is not supported in this problem."); + } + + virtual PointInfo test_hack_point() { + end_judge_judgment_failed("Hack is not supported in this problem."); + } + + virtual PointInfo test_custom_point() { + end_judge_judgment_failed("Custom test is not supported in this problem."); + } + +public: + virtual int n_tests() { + return conf_int("n_tests", 10); + } + + virtual int n_ex_tests() { + return conf_int("n_ex_tests", 0); + } + + virtual int n_sample_tests() { + return conf_int("n_sample_tests", 0); + } + + virtual int sample_test_point_score(int num) { + return 100 / this->n_sample_tests(); + } + + virtual bool is_hack_enabled() { + return true; + } + + virtual bool is_custom_test_enabled() { + return true; + } + + virtual void ordinary_test() { + int n = conf_int("n_tests", 10); + int m = this->n_ex_tests(); + int nT = conf_int("n_subtasks", 0); + + this->prepare(); + this->prepare_ordinary_test(); + + bool passed = true; + if (nT == 0) { // OI + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = this->test_point(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") { // ACM + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = this->test_point(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); + } + } + } else { // subtask + map subtasks; + map 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 points; + minScore[t] = 100; + + vector dependences; + if (conf_str("subtask_dependence", t, "none") == "many") { + string cur = "subtask_dependence_" + to_string(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::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 (minScore[t] == 0) { + skipped = true; + break; + } + } + } + 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 = this->test_point(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; + if (tscore == 0) { + break; + } + } else { + points.push_back(po); + } + } + } + + subtasks[t] = SubtaskInfo(info == "Accepted", tscore); + + add_subtask_info(t, tscore, info, points); + } + } + if (!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 = this->test_point(-i); + if (po.scr != 100) { + po.num = -1; + po.info = "Extra Test Failed : " + po.info + " on " + to_string(i); + po.scr = -3; + add_point_info(po); + end_judge_ok(); + } + } + if (m != 0) { + add_point_info(PointInfo::extra_test_passed()); + } + end_judge_ok(); + } + + virtual void hack_test() { + if (!this->is_hack_enabled()) { + end_judge_judgment_failed("Hack is not supported in this problem."); + } else { + this->prepare(); + this->prepare_hack_test(); + add_point_info(this->test_hack_point()); + end_judge_ok(); + } + } + + virtual void sample_test() { + this->prepare(); + this->prepare_sample_test(); + int n = this->n_sample_tests(); + bool passed = true; + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Sample Test #%d", i); + PointInfo po = this->test_sample_point(i); + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, this->sample_test_point_score(i)); + add_point_info(po); + } + if (passed) { + tot_score = 100; + } + end_judge_ok(); + } + + virtual void custom_test() { + if (!is_custom_test_enabled()) { + end_judge_judgment_failed("Custom test is not supported in this problem."); + } else { + this->prepare(); + this->prepare_custom_test(); + + report_judge_status_f("Judging"); + add_custom_test_info(this->test_custom_point()); + + end_judge_ok(); + } + } + + virtual void judge() { + if (conf_is("test_new_hack_only", "on")) { + this->hack_test(); + } else if (conf_is("test_sample_only", "on")) { + this->sample_test(); + } else if (conf_is("custom_test", "on")) { + this->custom_test(); + } else { + this->ordinary_test(); + } + } +}; + +template +class OrdinaryJudger : public Judger { +protected: + virtual void configure_programs_for_point(int num) { + program["answer"].limits = conf_run_limit(num, RL_DEFAULT); + Judger::configure_programs_for_point(num); + } + + virtual void prepare() override { + report_judge_status_f("Compiling"); + compile_and_add_program("answer"); + Judger::prepare(); + } + + virtual PointInfo test_point(int num) override { + TP tp; + tp.num = num; + Judger::configure_programs_for_point(num); + tp.program = program; + return tp.test(); + } + + virtual PointInfo test_sample_point(int num) override { + PointInfo po = this->test_point(-num); + po.num = num; + return po; + } + + virtual PointInfo test_hack_point() override { + TP tp; + tp.num = 0; + tp.validate_input_before_test = true; + Judger::configure_programs_for_point(0); + tp.program = program; + return tp.hack_test(); + } + + virtual PointInfo test_custom_point() override { + TP tp; + tp.num = 0; + Judger::configure_programs_for_point(0); + tp.program = program; + tp.program["answer"].limits.time += 2; + return tp.custom_test(); + } +}; + +class SubmitAnswerJudger : public Judger { +protected: + virtual PointInfo test_point(int num) override { + SubmitAnswerTestPoint tp; + tp.num = num; + Judger::configure_programs_for_point(num); + tp.program = program; + return tp.test(); + } + + virtual PointInfo test_sample_point(int num) override { + if (conf_is("check_existence_only_in_sample_test", "on")) { + SubmitAnswerTestPoint tp; + tp.num = num; + tp.program = program; + tp.program["chk"] = runp::config("nonempty").set_type("internal"); + Judger::configure_programs_for_point(num); + return tp.test(); + } else { + PointInfo po = this->test_point(num); + if (po.scr != 0) { + po.info = "Accepted"; + po.scr = 100; + } + po.res = "no comment"; + return po; + } + } + +public: + virtual int n_ex_tests() override { + return 0; + } + + virtual int n_sample_tests() override { + return this->n_tests(); + } + + virtual int sample_test_point_score(int num) override { + return conf_int("point_score", num, 100 / this->n_sample_tests()); + } + + virtual bool is_hack_enabled() override { + return false; + } + + virtual bool is_custom_test_enabled() override { + return false; + } +}; + +/*====================== judger end ================== */ + +/*======================= conf init =================== */ + +void main_judger_init(int argc, char **argv) { + try { + main_path = fs::read_symlink("/proc/self/exe").parent_path(); + work_path = fs::current_path() / "work"; + result_path = fs::current_path() / "result"; + load_config(work_path / "submission.conf"); + data_path = main_path / "data" / conf_str("problem_id"); + load_config(data_path / "problem.conf"); + + if (fs::is_directory(data_path / "require")) { + fs::copy(data_path / "require", work_path, fs::copy_options::update_existing | fs::copy_options::recursive); + } + + if (conf_is("use_builtin_judger", "on")) { + conf_add("judger", main_path / "builtin" / "judger" / "judger"); + } else { + conf_add("judger", data_path / "judger"); + } + + runp::run_path = main_path / "run"; + + } catch (exception &e) { + cerr << e.what() << endl; + exit(1); + } +} +void judger_init(int argc, char **argv) { + if (argc != 5) { + cerr << "judger: argc != 5" << endl; + exit(1); + } + main_path = argv[1]; + work_path = argv[2]; + result_path = argv[3]; + data_path = argv[4]; + load_config(work_path / "submission.conf"); + load_config(data_path / "problem.conf"); + runp::run_path = main_path / "run"; + + fs::current_path(work_path); +} + +/*===================== conf init End ================= */ + +/*===================== default judger ================ */ + +int default_judger_main(int argc, char **argv) { + judger_init(argc, argv); + + Judger *judger; + if (conf_is("submit_answer", "on")) { + judger = new SubmitAnswerJudger(); + } else if (conf_is("interaction_mode", "on")) { + judger = new OrdinaryJudger(); + } else { + judger = new OrdinaryJudger(); + } + judger->judge(); + + return -1; // error +} + +/*===================== default judger End ============ */ diff --git a/judger/uoj_judger/include/uoj_run.h b/judger/uoj_judger/include/uoj_run.h new file mode 100644 index 0000000..4c72253 --- /dev/null +++ b/judger/uoj_judger/include/uoj_run.h @@ -0,0 +1,438 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +std::ostream& spaced_out(std::ostream &out, const T &arg) { + return out << arg; +} + +template +std::ostream& spaced_out(std::ostream &out, const T &arg, const Args& ...rest) { + return spaced_out(out << arg << " ", rest...); +} + +template +std::ostream& add_spaced_out(std::ostream &out, const T &arg) { + return out << " " << arg; +} + +template +std::ostream& add_spaced_out(std::ostream &out, const T &arg, const Args& ...rest) { + return spaced_out(out << " " << arg, rest...); +} + +template +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 lang_upgrade_map = { + {"Java7" , "Java8" }, + {"Java14", "Java17" }, +}; + +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 + inline std::ostream& add_runp_arg(std::ostream &out, const std::pair> &arg) { + for (const auto &t : arg.second) { + out << " --" << arg.first << "=" << escapeshellarg(t); + } + return out; + } + template + inline std::ostream& add_runp_arg(std::ostream &out, const std::pair &arg) { + return out << " --" << arg.first << "=" << escapeshellarg(arg.second); + } + inline std::ostream& add_runp_arg(std::ostream &out, const std::vector &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 readable_file_names; // other than stdin + std::vector 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 rest_args; + + // full args (possbily with interpreter) + std::vector full_args; + + bool unsafe = false; + bool allow_proc = false; + bool need_show_trace_details = false; + + config(std::string program_name = "", const std::vector &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 cmds; + std::vector 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()); + } +} diff --git a/judger/uoj_judger/include/uoj_secure.h b/judger/uoj_judger/include/uoj_secure.h new file mode 100644 index 0000000..c829932 --- /dev/null +++ b/judger/uoj_judger/include/uoj_secure.h @@ -0,0 +1,332 @@ +#include +#include +#include +#include + +#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(""); + } + }; +} diff --git a/judger/uoj_judger/main_judger.cpp b/judger/uoj_judger/main_judger.cpp index e9bdae3..06607df 100644 --- a/judger/uoj_judger/main_judger.cpp +++ b/judger/uoj_judger/main_judger.cpp @@ -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; } diff --git a/judger/uoj_judger/run/compile.cpp b/judger/uoj_judger/run/compile.cpp new file mode 100644 index 0000000..817d721 --- /dev/null +++ b/judger/uoj_judger/run/compile.cpp @@ -0,0 +1,491 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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> 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 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 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++03") { + return compile_cpp(conf, "c++03"); + } else if (lang == "C++11") { + return compile_cpp(conf, "c++11"); + } else if (lang == "C++14") { + return compile_cpp(conf, "c++14"); + } 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; + } +} diff --git a/judger/uoj_judger/run/run_interaction.cpp b/judger/uoj_judger/run/run_interaction.cpp index 7c9ca15..139f229 100644 --- a/judger/uoj_judger/run/run_interaction.cpp +++ b/judger/uoj_judger/run/run_interaction.cpp @@ -16,62 +16,38 @@ #include #include #include -#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 cmds; - vector pipes; -}; - -struct RunCmdData { +struct run_cmd_data { string cmd; pid_t pid; vector 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 cmds; - vector pipes; + vector cmds; + vector 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 :-: (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; diff --git a/judger/uoj_judger/run/run_program.cpp b/judger/uoj_judger/run/run_program.cpp index b7abcd4..d6135dc 100644 --- a/judger/uoj_judger/run/run_program.cpp +++ b/judger/uoj_judger/run/run_program.cpp @@ -1,86 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 extra_readable_files; - vector 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 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_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); } diff --git a/judger/uoj_judger/run/run_program_conf.h b/judger/uoj_judger/run/run_program_conf.h index 6498404..f0a7bdc 100644 --- a/judger/uoj_judger/run/run_program_conf.h +++ b/judger/uoj_judger/run/run_program_conf.h @@ -1,673 +1,882 @@ -#ifdef __x86_64__ -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 -#else -typedef long int reg_val_t; -#define REG_SYSCALL orig_eax -#define REG_RET eax -#define REG_ARG0 ebx -#define REG_ARG1 ecx -#define REG_ARG2 edx -#define REG_ARG3 esx +#include +#include +#include + +#ifndef __x86_64__ +#error only x86-64 is supported! #endif -const size_t MaxPathLen = 200; +/* + * a mask that tells seccomp that it should SCMP_ACT_ERRNO(no) + * when syscall #(mask | no) is called + * used to implement SCMP_ACT_ERRNO(no) using ptrace: + * set the syscall number to mask | no; + * PTRACE_CONT + * seccomp performs SCMP_ACT_ERRNO(no) + */ +const int SYSCALL_SOFT_BAN_MASK = 996 << 18; -set writable_file_name_set; -set readable_file_name_set; -set statable_file_name_set; -set soft_ban_file_name_set; -int syscall_max_cnt[1000]; -bool syscall_should_soft_ban[1000]; - -string basename(const string &path) { - size_t p = path.rfind('/'); - if (p == string::npos) { - return path; - } else { - return path.substr(p + 1); - } -} -string dirname(const string &path) { - size_t p = path.rfind('/'); - if (p == string::npos) { - return ""; - } else { - return path.substr(0, p); - } -} -string getcwdp(pid_t pid) { - char s[50]; - char cwd[MaxPathLen + 1]; - if (pid != 0) { - sprintf(s, "/proc/%lld/cwd", (long long int)pid); - } else { - sprintf(s, "/proc/self/cwd"); - } - int l = readlink(s, cwd, MaxPathLen); - if (l == -1) { - return ""; - } - cwd[l] = '\0'; - return cwd; -} -string abspath(pid_t pid, const string &path) { - if (path.size() > MaxPathLen) { - return ""; - } - if (path.empty()) { - return path; - } - string s; - string b; - size_t st; - if (path[0] == '/') { - s = "/"; - st = 1; - } else { - s = getcwdp(pid) + "/"; - st = 0; - } - for (size_t i = st; i < path.size(); i++) { - b += path[i]; - if (path[i] == '/') { - if (b == "../" && !s.empty()) { - if (s == "./") { - s = "../"; - } else if (s != "/") { - size_t p = s.size() - 1; - while (p > 0 && s[p - 1] != '/') { - p--; - } - if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') { - s += b; - } else { - s.resize(p); - } - } - } else if (b != "./" && b != "/") { - s += b; - } - b.clear(); - } - } - if (b == ".." && !s.empty()) { - if (s == "./") { - s = ".."; - } else if (s != "/") { - size_t p = s.size() - 1; - while (p > 0 && s[p - 1] != '/') { - p--; - } - if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') { - s += b; - } else { - s.resize(p); - } - } - } else if (b != ".") { - s += b; - } - if (s.size() >= 2 && s[s.size() - 1] == '/') { - s.resize(s.size() - 1); - } - return s; -} -string realpath(const string &path) { - char real[PATH_MAX + 1] = {}; - if (realpath(path.c_str(), real) == NULL) { - return ""; - } - return real; -} - -inline bool is_in_set_smart(string name, const set &s) { - if (name.size() > MaxPathLen) { - return false; - } - if (s.count(name)) { - return true; - } - for (size_t i = 0; i + 1 < name.size(); i++) { - if ((i == 0 || name[i - 1] == '/') && name[i] == '.' && name[i + 1] == '.' && (i + 2 == name.size() || name[i + 2] == '.')) { - return false; - } - } - 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) || is_in_set_smart(realpath(name), writable_file_name_set); -} -inline bool is_readable_file(const string &name) { - if (is_writable_file(name)) { - return true; - } - if (name == "/") { - return readable_file_name_set.count("system_root"); - } - return is_in_set_smart(name, readable_file_name_set) || is_in_set_smart(realpath(name), readable_file_name_set); -} -inline bool is_statable_file(const string &name) { - if (is_readable_file(name)) { - return true; - } - if (name == "/") { - return statable_file_name_set.count("system_root"); - } - return is_in_set_smart(name, statable_file_name_set) || is_in_set_smart(realpath(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) || is_in_set_smart(realpath(name), soft_ban_file_name_set); -} - -#ifdef __x86_64__ -int syscall_max_cnt_list_default[][2] = { - {__NR_read , -1}, - {__NR_write , -1}, - {__NR_readv , -1}, - {__NR_writev , -1}, - {__NR_pread64 , -1}, - {__NR_open , -1}, - {__NR_unlink , -1}, - {__NR_close , -1}, - {__NR_readlink , -1}, - {__NR_openat , -1}, - {__NR_unlinkat , -1}, - {__NR_readlinkat , -1}, - {__NR_stat , -1}, - {__NR_fstat , -1}, - {__NR_lstat , -1}, - {__NR_lseek , -1}, - {__NR_access , -1}, - {__NR_dup , -1}, - {__NR_dup2 , -1}, - {__NR_dup3 , -1}, - {__NR_ioctl , -1}, - {__NR_fcntl , -1}, - - {__NR_mmap , -1}, - {__NR_mprotect , -1}, - {__NR_munmap , -1}, - {__NR_brk , -1}, - {__NR_mremap , -1}, - {__NR_msync , -1}, - {__NR_mincore , -1}, - {__NR_madvise , -1}, - - {__NR_rt_sigaction , -1}, - {__NR_rt_sigprocmask, -1}, - {__NR_rt_sigreturn , -1}, - {__NR_rt_sigpending , -1}, - {__NR_sigaltstack , -1}, - - {__NR_getcwd , -1}, - - {__NR_exit , -1}, - {__NR_exit_group , -1}, - - {__NR_arch_prctl , -1}, - - {__NR_gettimeofday , -1}, - {__NR_getrlimit , -1}, - {__NR_getrusage , -1}, - {__NR_times , -1}, - {__NR_time , -1}, - {__NR_clock_gettime , -1}, - - {__NR_restart_syscall, -1}, - - {-1 , -1} +std::vector supported_soft_ban_errno_list = { + ENOENT, // No such file or directory + EPERM, // Operation not permitted + EACCES, // Permission denied }; -int syscall_soft_ban_list_default[] = { - -1 +std::set available_program_type_set = { + "default", "python2.7", "python3", "java8", "java11", "java17", "compiler" }; -const char *readable_file_name_list_default[] = { - "/etc/ld.so.nohwcap", - "/etc/ld.so.preload", - "/etc/ld.so.cache", - "/lib/x86_64-linux-gnu/", - "/usr/lib/x86_64-linux-gnu/", - "/usr/lib/locale/locale-archive", - "/proc/self/exe", - "/etc/timezone", - "/usr/share/zoneinfo/", - "/dev/random", - "/dev/urandom", - "/proc/meminfo", - "/etc/localtime", - NULL +/* + * folder program: the program to run is a folder, not a single regular file + */ +std::set folder_program_type_set = { + "java8", "java11", "java17" }; -#else -#error T_T -#endif +std::map>> allowed_syscall_list = { + {"default", { + {__NR_read , syscall_info::unlimited()}, + {__NR_pread64 , syscall_info::unlimited()}, + {__NR_write , syscall_info::unlimited()}, + {__NR_pwrite64 , syscall_info::unlimited()}, + {__NR_readv , syscall_info::unlimited()}, + {__NR_writev , syscall_info::unlimited()}, + {__NR_preadv , syscall_info::unlimited()}, + {__NR_pwritev , syscall_info::unlimited()}, + {__NR_sendfile , syscall_info::unlimited()}, -void add_file_permission(const string &file_name, char mode) { - 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); - } - for (string name = dirname(file_name); !name.empty(); name = dirname(name)) { - statable_file_name_set.insert(name); - } -} + {__NR_close , syscall_info::unlimited()}, + {__NR_fstat , syscall_info::unlimited()}, + {__NR_fstatfs , syscall_info::unlimited()}, + {__NR_lseek , syscall_info::unlimited()}, + {__NR_dup , syscall_info::unlimited()}, + {__NR_dup2 , syscall_info::unlimited()}, + {__NR_dup3 , syscall_info::unlimited()}, + {__NR_ioctl , syscall_info::unlimited()}, + {__NR_fcntl , syscall_info::unlimited()}, -void init_conf(const RunProgramConfig &config) { - for (int i = 0; syscall_max_cnt_list_default[i][0] != -1; i++) { - syscall_max_cnt[syscall_max_cnt_list_default[i][0]] = syscall_max_cnt_list_default[i][1]; - } - for (int i = 0; syscall_soft_ban_list_default[i] != -1; i++) { - syscall_should_soft_ban[syscall_soft_ban_list_default[i]] = true; - } + {__NR_gettid , syscall_info::unlimited()}, + {__NR_getpid , syscall_info::unlimited()}, - for (int i = 0; readable_file_name_list_default[i]; i++) { - readable_file_name_set.insert(readable_file_name_list_default[i]); - } - statable_file_name_set.insert(config.work_path + "/"); + {__NR_mmap , syscall_info::unlimited()}, + {__NR_mprotect , syscall_info::unlimited()}, + {__NR_munmap , syscall_info::unlimited()}, + {__NR_brk , syscall_info::unlimited()}, + {__NR_mremap , syscall_info::unlimited()}, + {__NR_msync , syscall_info::unlimited()}, + {__NR_mincore , syscall_info::unlimited()}, + {__NR_madvise , syscall_info::unlimited()}, - add_file_permission(config.program_name, 'r'); - add_file_permission(config.work_path, 'r'); + {__NR_rt_sigaction , syscall_info::unlimited()}, + {__NR_rt_sigprocmask , syscall_info::unlimited()}, + {__NR_rt_sigreturn , syscall_info::unlimited()}, + {__NR_rt_sigpending , syscall_info::unlimited()}, + {__NR_sigaltstack , syscall_info::unlimited()}, - for (vector::const_iterator it = config.extra_readable_files.begin(); it != config.extra_readable_files.end(); it++) { - add_file_permission(*it, 'r'); - } - for (vector::const_iterator it = config.extra_writable_files.begin(); it != config.extra_writable_files.end(); it++) { - add_file_permission(*it, 'w'); - } + {__NR_getcwd , syscall_info::unlimited()}, + {__NR_uname , syscall_info::unlimited()}, - writable_file_name_set.insert("/dev/null"); + {__NR_exit , syscall_info::unlimited()}, + {__NR_exit_group , syscall_info::unlimited()}, - if (config.allow_proc) { - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_fork ] = -1; - syscall_max_cnt[__NR_vfork ] = -1; - syscall_max_cnt[__NR_nanosleep ] = -1; - syscall_max_cnt[__NR_execve ] = -1; - } + {__NR_arch_prctl , syscall_info::unlimited()}, - if (config.type == "python2") { - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 1; - syscall_max_cnt[__NR_futex ] = -1; + {__NR_getrusage , syscall_info::unlimited()}, + {__NR_getrlimit , syscall_info::unlimited()}, - syscall_max_cnt[__NR_getdents ] = -1; - syscall_max_cnt[__NR_getdents64 ] = -1; + {__NR_gettimeofday , syscall_info::unlimited()}, + {__NR_times , syscall_info::unlimited()}, + {__NR_time , syscall_info::unlimited()}, + {__NR_clock_gettime , syscall_info::unlimited()}, + {__NR_clock_getres , syscall_info::unlimited()}, - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - # endif + {__NR_restart_syscall, syscall_info::unlimited()}, - readable_file_name_set.insert("/usr/bin/python2.7"); - readable_file_name_set.insert("/usr/lib/python2.7/"); - readable_file_name_set.insert("/usr/bin/lib/python2.7/"); - readable_file_name_set.insert("/usr/local/lib/python2.7/"); - readable_file_name_set.insert("/usr/lib/pymodules/python2.7/"); - readable_file_name_set.insert("/usr/bin/Modules/"); - readable_file_name_set.insert("/usr/bin/pybuilddir.txt"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/usr/lib/locale/"); - readable_file_name_set.insert(config.work_path + "/answer.code"); - # endif + // for startup + {__NR_setitimer , syscall_info::count_based(1)}, + {__NR_execve , syscall_info::count_based(1)}, + {__NR_set_robust_list, syscall_info::unlimited() }, - statable_file_name_set.insert("/usr"); - statable_file_name_set.insert("/usr/bin"); - } else if (config.type == "python3") { - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 1; - syscall_max_cnt[__NR_futex ] = -1; + {__NR_set_tid_address, syscall_info::count_based(1)}, + {__NR_rseq , syscall_info::count_based(1)}, - syscall_max_cnt[__NR_getdents ] = -1; - syscall_max_cnt[__NR_getdents64 ] = -1; + // need to check file permissions + {__NR_open , syscall_info::with_extra_check(ECT_FILE_OP | ECT_CHECK_OPEN_FLAGS)}, + {__NR_openat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_CHECK_OPEN_FLAGS)}, + {__NR_readlink , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_readlinkat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)}, + {__NR_access , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + {__NR_faccessat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, + {__NR_faccessat2 , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, + {__NR_stat , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_statfs , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_lstat , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_newfstatat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)}, - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getrandom ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - # endif + // kill could be DGS or RE + {__NR_kill , syscall_info::kill_type_syscall()}, + {__NR_tkill , syscall_info::kill_type_syscall()}, + {__NR_tgkill , syscall_info::kill_type_syscall()}, - readable_file_name_set.insert("/usr/bin/python3"); - readable_file_name_set.insert("/usr/lib/python3/"); - # ifdef UOJ_JUDGER_PYTHON3_VERSION - readable_file_name_set.insert("/usr/bin/python" UOJ_JUDGER_PYTHON3_VERSION); - readable_file_name_set.insert("/usr/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - readable_file_name_set.insert("/usr/bin/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - readable_file_name_set.insert("/usr/local/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - # else - readable_file_name_set.insert("/usr/bin/python3.4"); - readable_file_name_set.insert("/usr/lib/python3.4/"); - readable_file_name_set.insert("/usr/bin/lib/python3.4/"); - readable_file_name_set.insert("/usr/local/lib/python3.4/"); - # endif - readable_file_name_set.insert("/usr/bin/pyvenv.cfg"); - readable_file_name_set.insert("/usr/pyvenv.cfg"); - readable_file_name_set.insert("/usr/bin/Modules/"); - readable_file_name_set.insert("/usr/bin/pybuilddir.txt"); - readable_file_name_set.insert("/usr/lib/dist-python"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/usr/lib/locale/"); - readable_file_name_set.insert(config.work_path + "/answer.code"); - # endif + // for python + {__NR_prlimit64 , syscall_info::soft_ban()}, - statable_file_name_set.insert("/usr"); - statable_file_name_set.insert("/usr/bin"); - statable_file_name_set.insert("/usr/lib"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - statable_file_name_set.insert("/usr/lib/python38.zip"); - # endif - } else if (config.type == "compiler") { - syscall_max_cnt[__NR_gettid ] = -1; - syscall_max_cnt[__NR_set_tid_address] = -1; - syscall_max_cnt[__NR_set_robust_list] = -1; - syscall_max_cnt[__NR_futex ] = -1; + // for python and java + {__NR_sysinfo , syscall_info::unlimited()}, - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_vfork ] = -1; - syscall_max_cnt[__NR_fork ] = -1; - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_execve ] = -1; - syscall_max_cnt[__NR_wait4 ] = -1; + // python3 uses this call to generate random numbers + // for fairness, all types of programs can use this call + {__NR_getrandom , syscall_info::unlimited()}, - syscall_max_cnt[__NR_clock_gettime ] = -1; - syscall_max_cnt[__NR_clock_getres ] = -1; + // futex + {__NR_futex , syscall_info::unlimited()}, - syscall_max_cnt[__NR_setrlimit ] = -1; - syscall_max_cnt[__NR_pipe ] = -1; - syscall_max_cnt[__NR_pipe2 ] = -1; + // some python library uses epoll (e.g., z3-solver) + {__NR_epoll_create , syscall_info::unlimited()}, + {__NR_epoll_create1 , syscall_info::unlimited()}, + {__NR_epoll_ctl , syscall_info::unlimited()}, + {__NR_epoll_wait , syscall_info::unlimited()}, + {__NR_epoll_pwait , syscall_info::unlimited()}, - syscall_max_cnt[__NR_getdents64 ] = -1; - syscall_max_cnt[__NR_getdents ] = -1; + // for java + {__NR_geteuid , syscall_info::unlimited()}, + {__NR_getuid , syscall_info::unlimited()}, + {__NR_setrlimit , syscall_info::soft_ban()}, + {__NR_socket , syscall_info::soft_ban()}, + {__NR_connect , syscall_info::soft_ban()}, + }}, - syscall_max_cnt[__NR_umask ] = -1; - syscall_max_cnt[__NR_rename ] = -1; - syscall_max_cnt[__NR_chmod ] = -1; - syscall_max_cnt[__NR_mkdir ] = -1; + {"allow_proc", { + {__NR_clone , syscall_info::unlimited()}, + {__NR_clone3 , syscall_info::unlimited()}, + {__NR_fork , syscall_info::unlimited()}, + {__NR_vfork , syscall_info::unlimited()}, + {__NR_nanosleep , syscall_info::unlimited()}, + {__NR_clock_nanosleep, syscall_info::unlimited()}, + {__NR_wait4 , syscall_info::unlimited()}, - syscall_max_cnt[__NR_chdir ] = -1; - syscall_max_cnt[__NR_fchdir ] = -1; + {__NR_execve , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + }}, - syscall_max_cnt[__NR_ftruncate ] = -1; + {"python2.7", { + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, + }}, - syscall_max_cnt[__NR_sched_getaffinity] = -1; - syscall_max_cnt[__NR_sched_yield ] = -1; + {"python3", { + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, + }}, - syscall_max_cnt[__NR_uname ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; + {"java8", { + {__NR_clone , syscall_info::with_extra_check(ECT_CLONE_THREAD, 9)}, + {__NR_clone3 , syscall_info::with_extra_check(ECT_CLONE_THREAD, 9)}, + {__NR_rseq , syscall_info::unlimited()}, + {__NR_prctl , syscall_info::unlimited()}, // TODO: add extra checks for prctl + {__NR_prlimit64 , syscall_info::unlimited()}, // TODO: add extra checks for prlimit64 - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getrandom ] = -1; - syscall_max_cnt[__NR_pread64 ] = -1; - syscall_max_cnt[__NR_prctl ] = -1; - syscall_max_cnt[__NR_nanosleep ] = -1; - syscall_max_cnt[__NR_clock_nanosleep] = -1; - syscall_max_cnt[__NR_socketpair ] = -1; - # endif + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, - syscall_should_soft_ban[__NR_socket ] = true; - syscall_should_soft_ban[__NR_connect ] = true; - syscall_should_soft_ban[__NR_geteuid ] = true; - syscall_should_soft_ban[__NR_getuid ] = true; + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield , syscall_info::unlimited()}, + }}, - writable_file_name_set.insert("/tmp/"); + {"java11", { + {__NR_clone , syscall_info::with_extra_check(ECT_CLONE_THREAD, 11)}, + {__NR_clone3 , syscall_info::with_extra_check(ECT_CLONE_THREAD, 11)}, + {__NR_rseq , syscall_info::unlimited()}, + {__NR_prctl , syscall_info::unlimited()}, // TODO: add extra checks for prctl + {__NR_prlimit64 , syscall_info::unlimited()}, // TODO: add extra checks for prlimit64 - readable_file_name_set.insert(config.work_path); - writable_file_name_set.insert(config.work_path + "/"); + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, - readable_file_name_set.insert(abspath(0, string(self_path) + "/../runtime") + "/"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/sys/fs/cgroup/cpu/"); - readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/"); - readable_file_name_set.insert("/sys/fs/cgroup/memory/"); - readable_file_name_set.insert("/etc/oracle/java/usagetracker.properties"); - # endif + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield , syscall_info::unlimited()}, - readable_file_name_set.insert("system_root"); - readable_file_name_set.insert("/usr/"); - readable_file_name_set.insert("/lib/"); - readable_file_name_set.insert("/lib64/"); - readable_file_name_set.insert("/bin/"); - readable_file_name_set.insert("/sbin/"); - // readable_file_name_set.insert("/proc/meminfo"); - // readable_file_name_set.insert("/proc/self/"); + {__NR_nanosleep , syscall_info::unlimited()}, + {__NR_clock_nanosleep , syscall_info::unlimited()}, + }}, - readable_file_name_set.insert("/sys/devices/system/cpu/"); - readable_file_name_set.insert("/proc/"); - soft_ban_file_name_set.insert("/etc/nsswitch.conf"); - soft_ban_file_name_set.insert("/etc/passwd"); + {"java17", { + {__NR_clone , syscall_info::with_extra_check(ECT_CLONE_THREAD, 13)}, + {__NR_clone3 , syscall_info::with_extra_check(ECT_CLONE_THREAD, 13)}, + {__NR_rseq , syscall_info::unlimited()}, + {__NR_prctl , syscall_info::unlimited()}, // TODO: add extra checks for prctl + {__NR_prlimit64 , syscall_info::unlimited()}, // TODO: add extra checks for prlimit64 - readable_file_name_set.insert("/etc/timezone"); - # ifdef UOJ_JUDGER_FPC_VERSION - readable_file_name_set.insert("/etc/fpc-" UOJ_JUDGER_FPC_VERSION ".cfg.d/"); - # else - readable_file_name_set.insert("/etc/fpc-2.6.2.cfg.d/"); - # endif - readable_file_name_set.insert("/etc/fpc.cfg"); + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, - statable_file_name_set.insert("/*"); - } -} + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield , syscall_info::unlimited()}, -string read_string_from_regs(reg_val_t addr, pid_t pid) { - char res[MaxPathLen + 1], *ptr = res; - while (ptr != res + MaxPathLen) { - *(reg_val_t*)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); - for (int i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) { - if (*ptr == 0) { - return res; - } - } - } - res[MaxPathLen] = 0; - return res; -} -string read_abspath_from_regs(reg_val_t addr, pid_t pid) { - return abspath(pid, read_string_from_regs(addr, pid)); -} + {__NR_nanosleep , syscall_info::unlimited()}, + {__NR_clock_nanosleep , syscall_info::unlimited()}, + }}, -inline void soft_ban_syscall(pid_t pid, user_regs_struct reg) { - reg.REG_SYSCALL += 1024; - ptrace(PTRACE_SETREGS, pid, NULL, ®); -} + {"compiler", { + {__NR_set_tid_address , syscall_info::unlimited()}, + {__NR_rseq , syscall_info::unlimited()}, -inline bool on_dgs_file_detect(pid_t pid, user_regs_struct reg, const string &fn) { - if (is_soft_ban_file(fn)) { - soft_ban_syscall(pid, reg); - return true; - } else { - return false; - } -} + {__NR_clone , syscall_info::unlimited()}, + {__NR_clone3 , syscall_info::unlimited()}, + {__NR_fork , syscall_info::unlimited()}, + {__NR_vfork , syscall_info::unlimited()}, + {__NR_nanosleep , syscall_info::unlimited()}, + {__NR_clock_nanosleep , syscall_info::unlimited()}, + {__NR_wait4 , syscall_info::unlimited()}, -inline bool check_safe_syscall(pid_t pid, bool need_show_trace_details) { - struct user_regs_struct reg; - ptrace(PTRACE_GETREGS, pid, NULL, ®); + {__NR_geteuid , syscall_info::unlimited()}, + {__NR_getuid , syscall_info::unlimited()}, + {__NR_getgid , syscall_info::unlimited()}, + {__NR_getegid , syscall_info::unlimited()}, + {__NR_getppid , syscall_info::unlimited()}, - int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff; - if (cur_instruction != 0x050f) { - if (need_show_trace_details) { - fprintf(stderr, "informal syscall %d\n", cur_instruction); - } - return false; - } + {__NR_setrlimit , syscall_info::unlimited()}, + {__NR_prlimit64 , syscall_info::unlimited()}, + {__NR_prctl , syscall_info::unlimited()}, - int syscall = (int)reg.REG_SYSCALL; - if (0 > syscall || syscall >= 1000) { - return false; - } - if (need_show_trace_details) { - fprintf(stderr, "syscall %d\n", (int)syscall); - } + {__NR_pipe , syscall_info::unlimited()}, + {__NR_pipe2 , syscall_info::unlimited()}, - if (syscall_should_soft_ban[syscall]) { - soft_ban_syscall(pid, reg); - } else if (syscall_max_cnt[syscall]-- == 0) { - if (need_show_trace_details) { - fprintf(stderr, "dgs %d\n", (int)syscall); - } - return false; - } + // for java... we have no choice + {__NR_socketpair , syscall_info::unlimited()}, + {__NR_socket , syscall_info::unlimited()}, + {__NR_getsockname , syscall_info::unlimited()}, + {__NR_setsockopt , syscall_info::unlimited()}, + {__NR_connect , syscall_info::unlimited()}, + {__NR_sendto , syscall_info::unlimited()}, + {__NR_poll , syscall_info::unlimited()}, + {__NR_recvmsg , syscall_info::unlimited()}, + {__NR_sysinfo , syscall_info::unlimited()}, - if (syscall == __NR_open || syscall == __NR_openat) { - reg_val_t fn_addr; - reg_val_t flags; - if (syscall == __NR_open) { - fn_addr = reg.REG_ARG0; - flags = reg.REG_ARG1; - } else { - fn_addr = reg.REG_ARG1; - flags = reg.REG_ARG2; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "open "); + {__NR_umask , syscall_info::unlimited()}, + {__NR_getdents , syscall_info::unlimited()}, + {__NR_getdents64 , syscall_info::unlimited()}, - switch (flags & O_ACCMODE) { - case O_RDONLY: - fprintf(stderr, "r "); - break; - case O_WRONLY: - fprintf(stderr, "w "); - break; - case O_RDWR: - fprintf(stderr, "rw"); - break; - default: - fprintf(stderr, "??"); - break; - } - fprintf(stderr, " %s\n", fn.c_str()); - } + {__NR_chdir , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_fchdir , syscall_info::unlimited()}, - bool is_read_only = (flags & O_ACCMODE) == O_RDONLY && - (flags & O_CREAT) == 0 && - (flags & O_EXCL) == 0 && - (flags & O_TRUNC) == 0; - if (is_read_only) { - if (realpath(fn) != "" && !is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else { - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } - } else if (syscall == __NR_readlink || syscall == __NR_readlinkat) { - reg_val_t fn_addr; - if (syscall == __NR_readlink) { - fn_addr = reg.REG_ARG0; - } else { - fn_addr = reg.REG_ARG1; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "readlink %s\n", fn.c_str()); - } - if (!is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_unlink || syscall == __NR_unlinkat) { - reg_val_t fn_addr; - if (syscall == __NR_unlink) { - fn_addr = reg.REG_ARG0; - } else { - fn_addr = reg.REG_ARG1; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "unlink %s\n", fn.c_str()); - } - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_access) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "access %s\n", fn.c_str()); - } - if (!is_statable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_stat || syscall == __NR_lstat) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "stat %s\n", fn.c_str()); - } - if (!is_statable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_execve) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "execve %s\n", fn.c_str()); - } - if (!is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_chmod || syscall == __NR_rename) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "change %s\n", fn.c_str()); - } - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } - return true; -} + {__NR_execve , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + {__NR_execveat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, -inline void on_syscall_exit(pid_t pid, bool need_show_trace_details) { - struct user_regs_struct reg; - ptrace(PTRACE_GETREGS, pid, NULL, ®); - if (need_show_trace_details) { - if ((long long int)reg.REG_SYSCALL >= 1024) { - fprintf(stderr, "ban sys %lld\n", (long long int)reg.REG_SYSCALL - 1024); - } else { - fprintf(stderr, "exitsys %lld (ret %d)\n", (long long int)reg.REG_SYSCALL, (int)reg.REG_RET); - } - } + {__NR_truncate , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_ftruncate , syscall_info::unlimited()}, - if ((long long int)reg.REG_SYSCALL >= 1024) { - reg.REG_SYSCALL -= 1024; - reg.REG_RET = -EACCES; - ptrace(PTRACE_SETREGS, pid, NULL, ®); - } -} + {__NR_chmod , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_fchmodat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + {__NR_fchmod , syscall_info::unlimited()}, + + {__NR_rename , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W | ECT_FILE2_W)}, + {__NR_renameat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W | ECT_FILE2_W)}, + {__NR_renameat2 , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W | ECT_FILE2_W)}, + + {__NR_unlink , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_unlinkat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + + {__NR_mkdir , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_mkdirat , syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + + {__NR_rmdir , syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + + {__NR_fadvise64 , syscall_info::unlimited()}, + + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield , syscall_info::unlimited()}, + + {__NR_kill , syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + {__NR_tkill , syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + {__NR_tgkill , syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + }}, +}; + +std::map> soft_ban_file_name_list = { + {"default", { + "/dev/tty", + "/dev/pts/", + + // for java and javac... + "/etc/nsswitch.conf", + "/etc/passwd", + }} +}; + +std::map> statable_file_name_list = { + {"default", {}}, + + {"python2.7", { + "/usr", + "/usr/bin", + "/usr/lib", + }}, + + {"python3", { + "/usr", + "/usr/bin", + "/usr/lib", + }}, + + {"java8", { + "system_root", + "/tmp/", + }}, + + {"java11", { + "system_root", + "/tmp/", + }}, + + {"java17", { + "system_root", + "/tmp/", + }}, + + {"compiler", { + "/*", + "/boot/", + }} +}; + +std::map> readable_file_name_list = { + {"default", { + "/lib/x86_64-linux-gnu/", + "/usr/lib/x86_64-linux-gnu/", + "/usr/lib/locale/", + "/usr/share/zoneinfo/", + "/etc/ld.so.nohwcap", + "/etc/ld.so.preload", + "/etc/ld.so.cache", + "/etc/timezone", + "/etc/localtime", + "/etc/locale.alias", + "/proc/self/", + "/proc/*", + "/dev/random", + "/dev/urandom", + "/sys/devices/system/cpu/", // for java & some python libraries + "/proc/sys/vm/", // for java + }}, + + {"python2.7", { + "/etc/python2.7/", + "/usr/bin/python2.7", + "/usr/lib/python2.7/", + "/usr/bin/lib/python2.7/", + "/usr/local/lib/python2.7/", + "/usr/lib/pymodules/python2.7/", + "/usr/bin/Modules/", + "/usr/bin/pybuilddir.txt", + }}, + + {"python3", { + "/etc/python3.10/", + "/usr/bin/python3.10", + "/usr/lib/python3.10/", + "/usr/lib/python3/dist-packages/", + "/usr/bin/lib/python3.10/", + "/usr/local/lib/python3.10/", + "/usr/bin/pyvenv.cfg", + "/usr/pyvenv.cfg", + "/usr/bin/Modules/", + "/usr/bin/pybuilddir.txt", + "/usr/lib/dist-python", + }}, + + {"java8", { + UOJ_OPEN_JDK8 "/", + "/sys/fs/cgroup/", + "/etc/java-8-openjdk/", + "/usr/share/java/", + }}, + + {"java11", { + UOJ_OPEN_JDK11 "/", + "/sys/fs/cgroup/", + "/etc/java-11-openjdk/", + "/usr/share/java/", + }}, + + {"java17", { + UOJ_OPEN_JDK17 "/", + "/sys/fs/cgroup/", + "/etc/java-17-openjdk/", + "/usr/share/java/", + }}, + + {"compiler", { + "system_root", + "/usr/", + "/lib/", + "/lib64/", + "/bin/", + "/sbin/", + "/sys/fs/cgroup/", + "/proc/", + "/etc/timezone", + "/etc/python2.7/", + "/etc/python3.10/", + "/etc/fpc-3.2.2.cfg", + "/etc/java-8-openjdk/", + "/etc/java-11-openjdk/", + "/etc/java-17-openjdk/", + }} +}; + +std::map> writable_file_name_list = { + {"default", { + "/dev/null", + + // for java11 and java17 + "/proc/self/coredump_filter", + }}, + + {"compiler", { + "/tmp/", + }} +}; + +const int N_SYSCALL = 440; +std::string syscall_name[N_SYSCALL] = { + "read", + "write", + "open", + "close", + "stat", + "fstat", + "lstat", + "poll", + "lseek", + "mmap", + "mprotect", + "munmap", + "brk", + "rt_sigaction", + "rt_sigprocmask", + "rt_sigreturn", + "ioctl", + "pread64", + "pwrite64", + "readv", + "writev", + "access", + "pipe", + "select", + "sched_yield", + "mremap", + "msync", + "mincore", + "madvise", + "shmget", + "shmat", + "shmctl", + "dup", + "dup2", + "pause", + "nanosleep", + "getitimer", + "alarm", + "setitimer", + "getpid", + "sendfile", + "socket", + "connect", + "accept", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "shutdown", + "bind", + "listen", + "getsockname", + "getpeername", + "socketpair", + "setsockopt", + "getsockopt", + "clone", + "fork", + "vfork", + "execve", + "exit", + "wait4", + "kill", + "uname", + "semget", + "semop", + "semctl", + "shmdt", + "msgget", + "msgsnd", + "msgrcv", + "msgctl", + "fcntl", + "flock", + "fsync", + "fdatasync", + "truncate", + "ftruncate", + "getdents", + "getcwd", + "chdir", + "fchdir", + "rename", + "mkdir", + "rmdir", + "creat", + "link", + "unlink", + "symlink", + "readlink", + "chmod", + "fchmod", + "chown", + "fchown", + "lchown", + "umask", + "gettimeofday", + "getrlimit", + "getrusage", + "sysinfo", + "times", + "ptrace", + "getuid", + "syslog", + "getgid", + "setuid", + "setgid", + "geteuid", + "getegid", + "setpgid", + "getppid", + "getpgrp", + "setsid", + "setreuid", + "setregid", + "getgroups", + "setgroups", + "setresuid", + "getresuid", + "setresgid", + "getresgid", + "getpgid", + "setfsuid", + "setfsgid", + "getsid", + "capget", + "capset", + "rt_sigpending", + "rt_sigtimedwait", + "rt_sigqueueinfo", + "rt_sigsuspend", + "sigaltstack", + "utime", + "mknod", + "uselib", + "personality", + "ustat", + "statfs", + "fstatfs", + "sysfs", + "getpriority", + "setpriority", + "sched_setparam", + "sched_getparam", + "sched_setscheduler", + "sched_getscheduler", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_rr_get_interval", + "mlock", + "munlock", + "mlockall", + "munlockall", + "vhangup", + "modify_ldt", + "pivot_root", + "_sysctl", + "prctl", + "arch_prctl", + "adjtimex", + "setrlimit", + "chroot", + "sync", + "acct", + "settimeofday", + "mount", + "umount2", + "swapon", + "swapoff", + "reboot", + "sethostname", + "setdomainname", + "iopl", + "ioperm", + "create_module", + "init_module", + "delete_module", + "get_kernel_syms", + "query_module", + "quotactl", + "nfsservctl", + "getpmsg", + "putpmsg", + "afs_syscall", + "tuxcall", + "security", + "gettid", + "readahead", + "setxattr", + "lsetxattr", + "fsetxattr", + "getxattr", + "lgetxattr", + "fgetxattr", + "listxattr", + "llistxattr", + "flistxattr", + "removexattr", + "lremovexattr", + "fremovexattr", + "tkill", + "time", + "futex", + "sched_setaffinity", + "sched_getaffinity", + "set_thread_area", + "io_setup", + "io_destroy", + "io_getevents", + "io_submit", + "io_cancel", + "get_thread_area", + "lookup_dcookie", + "epoll_create", + "epoll_ctl_old", + "epoll_wait_old", + "remap_file_pages", + "getdents64", + "set_tid_address", + "restart_syscall", + "semtimedop", + "fadvise64", + "timer_create", + "timer_settime", + "timer_gettime", + "timer_getoverrun", + "timer_delete", + "clock_settime", + "clock_gettime", + "clock_getres", + "clock_nanosleep", + "exit_group", + "epoll_wait", + "epoll_ctl", + "tgkill", + "utimes", + "vserver", + "mbind", + "set_mempolicy", + "get_mempolicy", + "mq_open", + "mq_unlink", + "mq_timedsend", + "mq_timedreceive", + "mq_notify", + "mq_getsetattr", + "kexec_load", + "waitid", + "add_key", + "request_key", + "keyctl", + "ioprio_set", + "ioprio_get", + "inotify_init", + "inotify_add_watch", + "inotify_rm_watch", + "migrate_pages", + "openat", + "mkdirat", + "mknodat", + "fchownat", + "futimesat", + "newfstatat", + "unlinkat", + "renameat", + "linkat", + "symlinkat", + "readlinkat", + "fchmodat", + "faccessat", + "pselect6", + "ppoll", + "unshare", + "set_robust_list", + "get_robust_list", + "splice", + "tee", + "sync_file_range", + "vmsplice", + "move_pages", + "utimensat", + "epoll_pwait", + "signalfd", + "timerfd_create", + "eventfd", + "fallocate", + "timerfd_settime", + "timerfd_gettime", + "accept4", + "signalfd4", + "eventfd2", + "epoll_create1", + "dup3", + "pipe2", + "inotify_init1", + "preadv", + "pwritev", + "rt_tgsigqueueinfo", + "perf_event_open", + "recvmmsg", + "fanotify_init", + "fanotify_mark", + "prlimit64", + "name_to_handle_at", + "open_by_handle_at", + "clock_adjtime", + "syncfs", + "sendmmsg", + "setns", + "getcpu", + "process_vm_readv", + "process_vm_writev", + "kcmp", + "finit_module", + "sched_setattr", + "sched_getattr", + "renameat2", + "seccomp", + "getrandom", + "memfd_create", + "kexec_file_load", + "bpf", + "execveat", + "userfaultfd", + "membarrier", + "mlock2", + "copy_file_range", + "preadv2", + "pwritev2", + "pkey_mprotect", + "pkey_alloc", + "pkey_free", + "statx", + "io_pgetevents", + "rseq", // 334 + "?335", + "?336", + "?337", + "?338", + "?339", + "?340", + "?341", + "?342", + "?343", + "?344", + "?345", + "?346", + "?347", + "?348", + "?349", + "?350", + "?351", + "?352", + "?353", + "?354", + "?355", + "?356", + "?357", + "?358", + "?359", + "?360", + "?361", + "?362", + "?363", + "?364", + "?365", + "?366", + "?367", + "?368", + "?369", + "?370", + "?371", + "?372", + "?373", + "?374", + "?375", + "?376", + "?377", + "?378", + "?379", + "?380", + "?381", + "?382", + "?383", + "?384", + "?385", + "?386", + "?387", + "?388", + "?389", + "?390", + "?391", + "?392", + "?393", + "?394", + "?395", + "?396", + "?397", + "?398", + "?399", + "?400", + "?401", + "?402", + "?403", + "?404", + "?405", + "?406", + "?407", + "?408", + "?409", + "?410", + "?411", + "?412", + "?413", + "?414", + "?415", + "?416", + "?417", + "?418", + "?419", + "?420", + "?421", + "?422", + "?423", + "?424", + "?425", + "?426", + "?427", + "?428", + "?429", + "?430", + "?431", + "?432", + "?433", + "?434", + "?435", + "?436", + "?437", + "?438", + "faccessat2", // 439 +}; diff --git a/judger/uoj_judger/run/run_program_sandbox.h b/judger/uoj_judger/run/run_program_sandbox.h new file mode 100644 index 0000000..27dbfd4 --- /dev/null +++ b/judger/uoj_judger/run/run_program_sandbox.h @@ -0,0 +1,760 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 writable_file_name_set; +set readable_file_name_set; +set statable_file_name_set; +set 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 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 &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 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; +} diff --git a/web/Dockerfile b/web/Dockerfile index e26e7d4..87bd85b 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -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 - diff --git a/web/install.sh b/web/install.sh index 40376ca..3c931ec 100644 --- a/web/install.sh +++ b/web/install.sh @@ -18,8 +18,10 @@ 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 @@ -77,7 +79,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() {