'''Standard challenge module.'''
import os
import shutil
import fcntl
from cffi import FFI
from tornado import gen, concurrent, process
from tornado.stack_context import StackContext
from tornado.ioloop import IOLoop
import PyExt
import Privilege
import Config
from Utils import FileUtils
STATUS_NONE = 0
STATUS_AC = 1
STATUS_WA = 2
STATUS_RE = 3
STATUS_TLE = 4
STATUS_MLE = 5
STATUS_CE = 6
STATUS_ERR = 7
MS_BIND = 4096
[docs]class StdChal:
'''Standard challenge.
Static attributes:
last_uniqid (int): Last ID.
last_standard_uid (int): Last UID for standard tasks.
last_restrict_uid (int): Last UID for restricted tasks.
null_fd (int): File descriptor of /dev/null.
build_cache (dict): Cache information of builds.
build_cache_refcount (dict): Refcount of build caches.
Attributes:
uniqid (int): Unique ID.
code_path (string): Code path.
res_path (string): Resource path.
comp_typ (string): Type of compile.
judge_typ (string): Type of judge.
test_list ([dict]): Test parameter lists.
metadata (dict): Metadata for judge.
chal_id (int): Challenge ID.
chal_path (string): Challenge path.
'''
last_uniqid = 0
last_standard_uid = Config.CONTAINER_STANDARD_UID_BASE
last_restrict_uid = Config.CONTAINER_RESTRICT_UID_BASE
null_fd = None
[docs] @staticmethod
def init():
'''Initialize the module.'''
with StackContext(Privilege.fileaccess):
try:
shutil.rmtree('container/standard/home')
except FileNotFoundError:
pass
os.mkdir('container/standard/home', mode=0o771)
try:
shutil.rmtree('container/standard/cache')
except FileNotFoundError:
pass
os.mkdir('container/standard/cache', mode=0o771)
ffi = FFI()
ffi.cdef('''int mount(const char source[], const char target[],
const char filesystemtype[], unsigned long mountflags,
const void *data);''')
ffi.cdef('''int umount(const char *target);''')
libc = ffi.dlopen('libc.so.6')
with StackContext(Privilege.fullaccess):
libc.umount(b'container/standard/dev')
libc.mount(b'/dev', b'container/standard/dev', b'', MS_BIND, \
ffi.NULL)
StdChal.null_fd = os.open('/dev/null', os.O_RDWR | os.O_CLOEXEC)
StdChal.build_cache = {}
StdChal.build_cache_refcount = {}
[docs] @staticmethod
def get_standard_ugid():
'''Generate standard UID/GID.
Returns:
(int, int): Standard UID/GID
'''
StdChal.last_standard_uid += 1
return (StdChal.last_standard_uid, StdChal.last_standard_uid)
[docs] @staticmethod
def get_restrict_ugid():
'''Generate restrict UID/GID.
Returns:
(int, int): Restrict UID/GID
'''
StdChal.last_restrict_uid += 1
return (StdChal.last_restrict_uid, StdChal.last_restrict_uid)
[docs] @staticmethod
def build_cache_find(res_path):
'''Get build cache.
Args:
res_path (string): Resource path.
Returns:
(string, int): (cache hash, GID) or None if not found.
'''
try:
return StdChal.build_cache[res_path]
except KeyError:
return None
[docs] @staticmethod
def build_cache_update(res_path, cache_hash, gid):
'''Update build cache.
Args:
res_path (string): Resource path.
cache_hash (int): Cache hash.
gid (int): GID.
Returns:
None
'''
ret = StdChal.build_cache_find(res_path)
if ret is not None:
StdChal.build_cache_decref(ret[0])
del StdChal.build_cache[res_path]
StdChal.build_cache[res_path] = (cache_hash, gid)
StdChal.build_cache_refcount[cache_hash] = 1
[docs] @staticmethod
def build_cache_incref(cache_hash):
'''Increment the refcount of the build cache.
Args:
cache_hash (int): Cache hash.
Returns:
None
'''
StdChal.build_cache_refcount[cache_hash] += 1
[docs] @staticmethod
def build_cache_decref(cache_hash):
'''Decrement the refcount of the build cache.
Delete the build cache if the refcount = 0.
Args:
cache_hash (int): Cache hash.
Returns:
None
'''
StdChal.build_cache_refcount[cache_hash] -= 1
if StdChal.build_cache_refcount[cache_hash] == 0:
with StackContext(Privilege.fileaccess):
shutil.rmtree('container/standard/cache/%x'%cache_hash)
def __init__(self, chal_id, code_path, comp_typ, judge_typ, res_path, \
test_list, metadata):
'''Initialize.
Args:
chal_id (int): Challenge ID.
code_path (string): Code path.
comp_typ (string): Type of compile.
judge_typ (string): Type of judge.
res_path (string): Resource path.
test_list ([dict]): Test parameter lists.
metadata (dict): Metadata for judge.
'''
StdChal.last_uniqid += 1
self.uniqid = StdChal.last_uniqid
self.code_path = code_path
self.res_path = res_path
self.comp_typ = comp_typ
self.judge_typ = judge_typ
self.test_list = test_list
self.metadata = metadata
self.chal_id = chal_id
self.chal_path = None
StdChal.last_standard_uid += 1
self.compile_uid, self.compile_gid = StdChal.get_standard_ugid()
[docs] @gen.coroutine
def prefetch(self):
'''Prefetch files.'''
path_set = set([self.code_path])
for root, _, files in os.walk(self.res_path):
for filename in files:
path_set.add(os.path.abspath(os.path.join(root, filename)))
path_list = list(path_set)
proc_list = []
with StackContext(Privilege.fileaccess):
for idx in range(0, len(path_list), 16):
proc_list.append(process.Subprocess(
['./Prefetch.py'] + path_list[idx:idx + 16],
stdout=process.Subprocess.STREAM))
for proc in proc_list:
yield proc.stdout.read_bytes(2)
[docs] @gen.coroutine
def start(self):
'''Start the challenge.
Returns:
dict: Challenge result.
'''
cache_hash = None
cache_gid = None
# Check if special judge needs to rebuild.
if self.judge_typ in ['ioredir']:
hashproc = process.Subprocess( \
['./HashDir.py', self.res_path + '/check'], \
stdout=process.Subprocess.STREAM)
dirhash = yield hashproc.stdout.read_until(b'\n')
dirhash = int(dirhash.decode('utf-8').rstrip('\n'), 16)
ret = StdChal.build_cache_find(self.res_path)
if ret is not None and ret[0] == dirhash:
cache_hash, cache_gid = ret
judge_ioredir = IORedirJudge('container/standard', \
'/cache/%x'%cache_hash)
else:
cache_hash = dirhash
_, cache_gid = StdChal.get_standard_ugid()
build_ugid = StdChal.get_standard_ugid()
build_relpath = '/cache/%x'%cache_hash
build_path = 'container/standard' + build_relpath
judge_ioredir = IORedirJudge('container/standard', \
build_relpath)
if not (yield judge_ioredir.build(build_ugid, self.res_path)):
return [(0, 0, STATUS_ERR)] * len(self.test_list), ''
FileUtils.setperm(build_path, \
Privilege.JUDGE_UID, cache_gid, umask=0o750)
with StackContext(Privilege.fullaccess):
os.chmod(build_path, 0o750)
StdChal.build_cache_update(self.res_path, cache_hash, cache_gid)
print('StdChal %d built checker %x'%(self.chal_id, cache_hash))
StdChal.build_cache_incref(cache_hash)
print('StdChal %d started'%self.chal_id)
# Create challenge environment.
self.chal_path = 'container/standard/home/%d'%self.uniqid
with StackContext(Privilege.fileaccess):
os.mkdir(self.chal_path, mode=0o771)
try:
yield self.prefetch()
print('StdChal %d prefetched'%self.chal_id)
if self.comp_typ in ['g++', 'clang++']:
ret, verdict = yield self.comp_cxx()
elif self.comp_typ == 'makefile':
ret, verdict = yield self.comp_make()
elif self.comp_typ == 'python3':
ret, verdict = yield self.comp_python()
if ret != PyExt.DETECT_NONE:
return [(0, 0, STATUS_CE, verdict)] * len(self.test_list)
print('StdChal %d compiled'%self.chal_id)
# Prepare test arguments
if self.comp_typ == 'python3':
exefile_path = self.chal_path \
+ '/compile/__pycache__/test.cpython-34.pyc'
exe_path = '/usr/bin/python3.5'
argv = ['./a.out']
envp = ['HOME=/', 'LANG=en_US.UTF-8']
else:
exefile_path = self.chal_path + '/compile/a.out'
exe_path = './a.out'
argv = []
envp = []
# Prepare judge
test_future = []
if self.judge_typ == 'diff':
for test in self.test_list:
test_future.append(self.judge_diff(
exefile_path,
exe_path, argv, envp,
test['in'], test['ans'],
test['timelimit'], test['memlimit']))
elif self.judge_typ == 'ioredir':
for test in self.test_list:
check_uid, _ = StdChal.get_standard_ugid()
test_uid, test_gid = StdChal.get_restrict_ugid()
test_future.append(judge_ioredir.judge( \
exefile_path, exe_path, argv, envp, \
(check_uid, cache_gid), \
(test_uid, test_gid), \
'/home/%d/run_%d'%(self.uniqid, test_uid), \
test, self.metadata))
# Emit tests
test_result = yield gen.multi(test_future)
ret_result = list()
for result in test_result:
test_pass, data, verdict = result
runtime, peakmem, error = data
status = STATUS_ERR
if error == PyExt.DETECT_NONE:
if test_pass is True:
status = STATUS_AC
else:
status = STATUS_WA
elif error == PyExt.DETECT_OOM:
status = STATUS_MLE
elif error == PyExt.DETECT_TIMEOUT \
or error == PyExt.DETECT_FORCETIMEOUT:
status = STATUS_TLE
elif error == PyExt.DETECT_EXITERR:
status = STATUS_RE
else:
status = STATUS_ERR
ret_result.append((runtime, peakmem, status, verdict))
return ret_result
finally:
if cache_hash is not None:
StdChal.build_cache_decref(cache_hash)
with StackContext(Privilege.fileaccess):
shutil.rmtree(self.chal_path)
print('StdChal %d done'%self.chal_id)
[docs] @concurrent.return_future
def comp_cxx(self, callback=None):
'''GCC, Clang compile.
Args:
callback (function): Callback of return_future.
Returns:
None
'''
def _started_cb(task_id):
'''Started callback.
Close unused file descriptors after the task is started.
Args:
task_id (int): Task ID.
Returns:
None
'''
nonlocal errpipe_fd
os.close(errpipe_fd)
def _done_cb(task_id, stat):
'''Done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
nonlocal compile_path
with StackContext(Privilege.fileaccess):
verfile = open(compile_path + '/verdict.txt', 'rb')
# To fix decoding error.
# Force convert the binary string to string temporarily.
verdict = ''.join(chr(c) for c in verfile.read(140))
verfile.close()
callback((stat['detect_error'], verdict))
compile_path = self.chal_path + '/compile'
with StackContext(Privilege.fileaccess):
os.mkdir(compile_path, mode=0o770)
shutil.copyfile(self.code_path, compile_path + '/test.cpp', \
follow_symlinks=False)
FileUtils.setperm(compile_path, self.compile_uid, self.compile_gid)
with StackContext(Privilege.fileaccess):
errpipe_fd = os.open(compile_path + '/verdict.txt', \
os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC, mode=0o440)
if self.comp_typ == 'g++':
compiler = '/usr/bin/g++'
elif self.comp_typ == 'clang++':
compiler = '/usr/bin/clang++'
task_id = PyExt.create_task(compiler, \
[
'-O2',
'-std=c++14',
'-o', './a.out',
'./test.cpp',
], \
[
'PATH=/usr/bin:/bin',
'TMPDIR=/home/%d/compile'%self.uniqid,
], \
{
0: StdChal.null_fd,
1: StdChal.null_fd,
2: errpipe_fd,
}, \
'/home/%d/compile'%self.uniqid, 'container/standard', \
self.compile_uid, self.compile_gid, 60000, 1024 * 1024 * 1024, \
PyExt.RESTRICT_LEVEL_LOW)
if task_id is None:
os.close(errpipe_fd)
callback((PyExt.DETECT_INTERNALERR, ''))
return
PyExt.start_task(task_id, _done_cb, _started_cb)
[docs] @concurrent.return_future
def comp_make(self, callback=None):
'''Makefile compile.
Args:
callback (function): Callback of return_future.
Returns:
None
'''
def _done_cb(task_id, stat):
'''Done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
callback((stat['detect_error'], ''))
make_path = self.chal_path + '/compile'
FileUtils.copydir(self.res_path + '/make', make_path)
with StackContext(Privilege.fileaccess):
shutil.copyfile(self.code_path, make_path + '/main.cpp', \
follow_symlinks=False)
FileUtils.setperm(make_path, self.compile_uid, self.compile_gid)
with StackContext(Privilege.fullaccess):
os.chmod(make_path, mode=0o770)
task_id = PyExt.create_task('/usr/bin/make', \
[], \
[
'PATH=/usr/bin:/bin',
'TMPDIR=/home/%d/compile'%self.uniqid,
'OUT=./a.out',
], \
{
0: StdChal.null_fd,
1: StdChal.null_fd,
2: StdChal.null_fd,
}, \
'/home/%d/compile'%self.uniqid, 'container/standard', \
self.compile_uid, self.compile_gid, 60000, 1024 * 1024 * 1024, \
PyExt.RESTRICT_LEVEL_LOW)
if task_id is None:
callback((PyExt.DETECT_INTERNALERR, ''))
else:
PyExt.start_task(task_id, _done_cb)
[docs] @concurrent.return_future
def comp_python(self, callback=None):
'''Python3.4 compile.
Args:
callback (function): Callback of return_future.
Returns:
None
'''
def _started_cb(task_id):
'''Started callback.
Close unused file descriptors after the task is started.
Args:
task_id (int): Task ID.
Returns:
None
'''
nonlocal errpipe_fd
os.close(errpipe_fd)
def _done_cb(task_id, stat):
'''Done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
nonlocal compile_path
with StackContext(Privilege.fileaccess):
verfile = open(compile_path + '/verdict.txt', 'rb')
# To fix decoding error.
# Force convert the binary string to string temporarily.
verdict = ''.join(chr(c) for c in verfile.read(140))
verfile.close()
callback((stat['detect_error'], verdict))
compile_path = self.chal_path + '/compile'
with StackContext(Privilege.fileaccess):
os.mkdir(compile_path, mode=0o770)
shutil.copyfile(self.code_path, compile_path + '/test.py', \
follow_symlinks=False)
FileUtils.setperm(compile_path, self.compile_uid, self.compile_gid)
with StackContext(Privilege.fileaccess):
errpipe_fd = os.open(compile_path + '/verdict.txt', \
os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC, mode=0o440)
task_id = PyExt.create_task('/usr/bin/python3.5', \
[
'-m',
'py_compile',
'./test.py'
], \
[
'HOME=/home/%d/compile'%self.uniqid,
'LANG=en_US.UTF-8'
], \
{
0: StdChal.null_fd,
1: StdChal.null_fd,
2: errpipe_fd,
}, \
'/home/%d/compile'%self.uniqid, 'container/standard', \
self.compile_uid, self.compile_gid, 60000, 1024 * 1024 * 1024, \
PyExt.RESTRICT_LEVEL_LOW)
if task_id is None:
os.close(errpipe_fd)
callback((PyExt.DETECT_INTERNALERR, ''))
return
PyExt.start_task(task_id, _done_cb, _started_cb)
[docs] @concurrent.return_future
def judge_diff(self, src_path, exe_path, argv, envp, in_path, ans_path, \
timelimit, memlimit, callback=None):
'''Diff judge.
Args:
src_path (string): Executable source path.
exe_path (string): Executable or interpreter path in the sandbox.
argv ([string]): List of arguments.
envp ([string]): List of environment variables.
in_path (string): Input file path.
ans_path (string): Answer file path.
timelimit (int): Timelimit.
memlimit (int): Memlimit.
callback (function): Callback of return_future.
Returns:
None
'''
def _started_cb(task_id):
'''Started callback.
Close unused file descriptors after the task is started.
Args:
task_id (int): Task ID.
Returns:
None
'''
nonlocal infile_fd
nonlocal outpipe_fd
os.close(infile_fd)
os.close(outpipe_fd[1])
IOLoop.instance().add_handler(outpipe_fd[0], _diff_out, \
IOLoop.READ | IOLoop.ERROR)
def _done_cb(task_id, stat):
'''Done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
nonlocal result_stat
nonlocal result_pass
result_stat = (stat['utime'], stat['peakmem'], stat['detect_error'])
if result_pass is not None:
callback((result_pass, result_stat, ''))
def _diff_out(evfd, events):
'''Diff the output of the task.
Args:
evfd (int): Event file descriptor.
events (int): Event flags.
Returns:
None
'''
nonlocal outpipe_fd
nonlocal ansfile
nonlocal result_stat
nonlocal result_pass
end_flag = False
if events & IOLoop.READ:
while True:
try:
data = os.read(outpipe_fd[0], 65536)
except BlockingIOError:
break
ansdata = ansfile.read(len(data))
if data != ansdata:
result_pass = False
end_flag = True
break
if len(ansdata) == 0:
if len(ansfile.read(1)) == 0:
result_pass = True
else:
result_pass = False
end_flag = True
break
if (events & IOLoop.ERROR) or end_flag:
if result_pass is None:
if len(ansfile.read(1)) == 0:
result_pass = True
else:
result_pass = False
IOLoop.instance().remove_handler(evfd)
os.close(outpipe_fd[0])
ansfile.close()
if result_stat is not None:
callback((result_pass, result_stat, ''))
judge_uid, judge_gid = StdChal.get_restrict_ugid()
# Prepare I/O and stat.
with StackContext(Privilege.fileaccess):
infile_fd = os.open(in_path, os.O_RDONLY | os.O_CLOEXEC)
ansfile = open(ans_path, 'rb')
outpipe_fd = os.pipe2(os.O_CLOEXEC)
fcntl.fcntl(outpipe_fd[0], fcntl.F_SETFL, os.O_NONBLOCK)
result_stat = None
result_pass = None
# Prepare judge environment.
with StackContext(Privilege.fileaccess):
judge_path = self.chal_path + '/run_%d'%judge_uid
os.mkdir(judge_path, mode=0o771)
shutil.copyfile(src_path, judge_path + '/a.out', \
follow_symlinks=False)
with StackContext(Privilege.fullaccess):
os.chown(judge_path + '/a.out', judge_uid, judge_gid)
os.chmod(judge_path + '/a.out', 0o500)
task_id = PyExt.create_task(exe_path, argv, envp, \
{
0: infile_fd,
1: outpipe_fd[1],
2: outpipe_fd[1],
}, \
'/home/%d/run_%d'%(self.uniqid, judge_uid), 'container/standard', \
judge_uid, judge_gid, timelimit, memlimit, \
PyExt.RESTRICT_LEVEL_HIGH)
if task_id is None:
os.close(infile_fd)
os.close(outpipe_fd[0])
os.close(outpipe_fd[1])
ansfile.close()
callback((False, (0, 0, PyExt.DETECT_INTERNALERR), ''))
else:
PyExt.start_task(task_id, _done_cb, _started_cb)
[docs]class IORedirJudge:
'''I/O redirect spcial judge.
Attributes:
container_path (string): Container path.
build_relpath (string): Relative build path.
build_path (string): Build path.
'''
def __init__(self, container_path, build_relpath):
'''Initialize.
Args:
container_path (string): Container path.
build_relpath (string): Relative build path.
'''
self.container_path = container_path
self.build_relpath = build_relpath
self.build_path = container_path + build_relpath
[docs] @concurrent.return_future
def build(self, build_ugid, res_path, callback=None):
'''Build environment.
Args:
build_ugid ((int, int)): Build UID/GID.
res_path (string): Resource path.
callback (function): Callback of return_future.
Returns:
None
'''
def _done_cb(task_id, stat):
'''Done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
if stat['detect_error'] == PyExt.DETECT_NONE:
callback(True)
else:
callback(False)
build_uid, build_gid = build_ugid
# Prepare build environment.
FileUtils.copydir(res_path + '/check', self.build_path)
FileUtils.setperm(self.build_path, build_uid, build_gid)
with StackContext(Privilege.fullaccess):
os.chmod(self.build_path, mode=0o770)
with StackContext(Privilege.fileaccess):
if not os.path.isfile(self.build_path + '/build'):
callback(True)
return
# Make the build file executable.
with StackContext(Privilege.fullaccess):
os.chmod(self.build_path + '/build', mode=0o770)
# Build.
task_id = PyExt.create_task(self.build_relpath + '/build', \
[], \
[
'PATH=/usr/bin:/bin',
'TMPDIR=%s'%self.build_relpath,
'HOME=%s'%self.build_relpath,
'LANG=en_US.UTF-8'
], \
{
0: StdChal.null_fd,
1: StdChal.null_fd,
2: StdChal.null_fd,
}, \
self.build_relpath, 'container/standard', \
build_uid, build_gid, 60000, 1024 * 1024 * 1024, \
PyExt.RESTRICT_LEVEL_LOW)
if task_id is None:
callback(False)
else:
PyExt.start_task(task_id, _done_cb)
[docs] @concurrent.return_future
def judge(self, src_path, exe_relpath, argv, envp, check_ugid, test_ugid, \
test_relpath, test_param, metadata, callback=None):
'''I/O redirect special judge.
Args:
src_path (string): Executable source path.
exe_relpath (string): Executable or interpreter path in the sandbox.
argv ([string]): List of arguments.
envp ([string]): List of environment variables.
check_ugid (int, int): Check UID/GID.
test_ugid (int, int): Test UID/GID.
test_relpath (string): Test relative path.
test_param (dict): Test parameters.
metadata (dict): Metadata.
callback (function): Callback of return_future.
Returns:
None
'''
def _check_started_cb(task_id):
'''Check started callback.
Close unused file descriptors after the check is started.
Args:
task_id (int): Task ID.
Returns:
None
'''
nonlocal inpipe_fd
nonlocal outpipe_fd
nonlocal ansfile_fd
nonlocal check_infile_fd
os.close(inpipe_fd[1])
os.close(outpipe_fd[0])
if ansfile_fd is not None:
os.close(ansfile_fd)
if check_infile_fd is not None:
os.close(check_infile_fd)
def _test_started_cb(task_id):
'''Test started callback.
Close unused file descriptors after the test is started.
Args:
task_id (int): Task ID.
Returns:
None
'''
nonlocal inpipe_fd
nonlocal outpipe_fd
nonlocal outfile_fd
nonlocal test_infile_fd
os.close(inpipe_fd[0])
os.close(outpipe_fd[1])
os.close(outfile_fd)
if test_infile_fd is not None:
os.close(test_infile_fd)
def _done_cb():
'''Done callback.'''
nonlocal result_stat
nonlocal result_pass
nonlocal verdict_path
if result_pass is not None and result_stat is not None:
with StackContext(Privilege.fileaccess):
verfile = open(verdict_path, 'r')
verdict = verfile.read(140)
verfile.close()
callback((result_pass, result_stat, verdict))
return
def _check_done_cb(task_id, stat):
'''Check done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
nonlocal result_pass
if stat['detect_error'] == PyExt.DETECT_NONE:
result_pass = True
else:
result_pass = False
_done_cb()
def _test_done_cb(task_id, stat):
'''Test done callback.
Args:
task_id (int): Task ID.
stat (dict): Task result.
Returns:
None
'''
nonlocal result_stat
result_stat = (stat['utime'], stat['peakmem'], stat['detect_error'])
_done_cb()
result_stat = None
result_pass = None
in_path = test_param['in']
ans_path = test_param['ans']
timelimit = test_param['timelimit']
memlimit = test_param['memlimit']
check_uid, check_gid = check_ugid
test_uid, test_gid = test_ugid
test_path = self.container_path + test_relpath
output_relpath = test_relpath + '/output.txt'
output_path = self.container_path + output_relpath
verdict_relpath = test_relpath + '/verdict.txt'
verdict_path = self.container_path + verdict_relpath
# Prepare test environment.
with StackContext(Privilege.fileaccess):
os.mkdir(test_path, mode=0o771)
shutil.copyfile(src_path, test_path + '/a.out', \
follow_symlinks=False)
with StackContext(Privilege.fullaccess):
os.chown(test_path + '/a.out', test_uid, test_gid)
os.chmod(test_path + '/a.out', 0o500)
# Prepare I/O.
with StackContext(Privilege.fileaccess):
try:
check_infile_fd = os.open(in_path, os.O_RDONLY | os.O_CLOEXEC)
test_infile_fd = os.open(in_path, os.O_RDONLY | os.O_CLOEXEC)
except (FileNotFoundError, TypeError):
check_infile_fd = None
test_infile_fd = None
try:
ansfile_fd = os.open(ans_path, os.O_RDONLY | os.O_CLOEXEC)
except (FileNotFoundError, TypeError):
ansfile_fd = None
outfile_fd = os.open(output_path, \
os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC, mode=0o400)
os.close(os.open(verdict_path,
os.O_CREAT | os.O_CLOEXEC, mode=0o640))
with StackContext(Privilege.fullaccess):
os.chown(output_path, check_uid, check_gid)
os.chown(verdict_path, check_uid, check_gid)
inpipe_fd = os.pipe2(os.O_CLOEXEC)
outpipe_fd = os.pipe2(os.O_CLOEXEC)
# Set file descriptor mapping.
check_fdmap = {
0: StdChal.null_fd,
1: StdChal.null_fd,
2: StdChal.null_fd,
}
test_fdmap = {
0: StdChal.null_fd,
1: StdChal.null_fd,
2: StdChal.null_fd,
}
if check_infile_fd is not None:
check_fdmap[metadata['redir_check']['testin']] = check_infile_fd
if ansfile_fd is not None:
check_fdmap[metadata['redir_check']['ansin']] = ansfile_fd
check_fdmap[metadata['redir_check']['pipein']] = inpipe_fd[1]
check_fdmap[metadata['redir_check']['pipeout']] = outpipe_fd[0]
try:
del check_fdmap[-1]
except KeyError:
pass
if test_infile_fd is not None:
test_fdmap[metadata['redir_test']['testin']] = test_infile_fd
test_fdmap[metadata['redir_test']['testout']] = outfile_fd
test_fdmap[metadata['redir_test']['pipein']] = inpipe_fd[0]
test_fdmap[metadata['redir_test']['pipeout']] = outpipe_fd[1]
try:
del test_fdmap[-1]
except KeyError:
pass
check_task_id = PyExt.create_task(self.build_relpath + '/check', \
[], \
[
'PATH=/usr/bin:/bin',
'HOME=%s'%self.build_relpath,
'LANG=en_US.UTF-8',
'OUTPUT=%s'%output_relpath,
'VERDICT=%s'%verdict_relpath,
], \
check_fdmap, \
self.build_relpath, self.container_path, \
check_uid, check_gid, 60000, 1024 * 1024 * 1024, \
PyExt.RESTRICT_LEVEL_LOW)
if check_task_id is None:
callback((False, (0, 0, PyExt.DETECT_INTERNALERR), ''))
return
PyExt.start_task(check_task_id, _check_done_cb, _check_started_cb)
test_task_id = PyExt.create_task(exe_relpath, argv, envp, \
test_fdmap, \
test_relpath, self.container_path, \
test_uid, test_gid, timelimit, memlimit, \
PyExt.RESTRICT_LEVEL_HIGH)
if test_task_id is None:
callback((False, (0, 0, PyExt.DETECT_INTERNALERR), ''))
return
PyExt.start_task(test_task_id, _test_done_cb, _test_started_cb)