Skip to content

Commit 4fddf4b

Browse files
committed
Encoder-decoder task support
1 parent ecbdfad commit 4fddf4b

File tree

5 files changed

+275
-3
lines changed

5 files changed

+275
-3
lines changed

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@
6060
'ping = sio.workers.ping:run',
6161
'compile = sio.compilers.job:run',
6262
'exec = sio.executors.executor:run',
63+
'encdec-exec = sio.executors.executor:encdec_run',
6364
'sio2jail-exec = sio.executors.sio2jail_exec:run',
65+
'sio2jail-encdec-exec = sio.executors.sio2jail_exec:encdec_run',
6466
'cpu-exec = sio.executors.executor:run',
67+
'cpu-encdec-exec = sio.executors.executor:encdec_run',
6568
'unsafe-exec = sio.executors.unsafe_exec:run',
69+
'unsafe-encdec-exec = sio.executors.unsafe_exec:encdec_run',
6670
'ingen = sio.executors.ingen:run',
6771
'inwer = sio.executors.inwer:run',
6872
],

sio/executors/encdec_common.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
from __future__ import absolute_import
2+
import os
3+
import logging
4+
from shutil import rmtree
5+
import tempfile
6+
from zipfile import ZipFile, is_zipfile
7+
from sio.executors.checker import _limit_length
8+
from sio.executors.common import _run_core
9+
from sio.workers import ft
10+
from sio.workers.executors import ExecError, PRootExecutor, UnprotectedExecutor
11+
from sio.workers.util import decode_fields, replace_invalid_UTF, tempcwd
12+
from sio.workers.file_runners import get_file_runner
13+
14+
# TODO XXX FIXME
15+
# Hide the files like enc_in or hint from the contestants
16+
# Would not be nice if someone just sideloaded enc_in in decoder
17+
18+
import six
19+
20+
logger = logging.getLogger(__name__)
21+
22+
23+
DEFAULT_SUPPLEMENTARY_TIME_LIMIT = 30000 # in ms
24+
DEFAULT_SUPPLEMENTARY_MEM_LIMIT = 268 * 2**10 # in KiB
25+
26+
27+
class ChannelError(Exception):
28+
pass
29+
30+
31+
class CheckerError(Exception):
32+
pass
33+
34+
35+
def _populate_environ(renv, environ, prefix):
36+
"""Takes interesting fields from renv into environ"""
37+
for key in (
38+
"time_used",
39+
"mem_used",
40+
"num_syscalls",
41+
"result_code",
42+
"result_string",
43+
):
44+
if key in renv:
45+
environ[prefix + key] = renv[key]
46+
47+
48+
def _run_supplementary(env, command, executor, environ_prefix, **kwargs):
49+
with executor:
50+
return executor(
51+
command,
52+
capture_output=True,
53+
split_lines=True,
54+
mem_limit=DEFAULT_SUPPLEMENTARY_MEM_LIMIT,
55+
time_limit=DEFAULT_SUPPLEMENTARY_TIME_LIMIT,
56+
environ=env,
57+
environ_prefix=environ_prefix,
58+
**kwargs
59+
)
60+
61+
62+
def _run_encoder(environ, file_executor, exe_filename, use_sandboxes):
63+
ft.download(environ, "in_file", "enc_in", add_to_cache=True)
64+
return _run_core(
65+
environ,
66+
file_executor,
67+
tempcwd("enc_in"),
68+
tempcwd("enc_out"),
69+
tempcwd(exe_filename),
70+
"encoder_",
71+
use_sandboxes,
72+
)
73+
74+
75+
def _run_channel_core(env, result_file, checker_file, use_sandboxes=False):
76+
command = [
77+
"./chn",
78+
"enc_in",
79+
"enc_out",
80+
"hint",
81+
str(result_file.fileno()),
82+
str(checker_file.fileno()),
83+
]
84+
85+
def execute_channel(with_stderr=False, stderr=None):
86+
return _run_supplementary(
87+
env,
88+
command,
89+
PRootExecutor("null-sandbox")
90+
if env.get("untrusted_channel", False) and use_sandboxes
91+
else UnprotectedExecutor(),
92+
"channel_",
93+
ignore_errors=True,
94+
forward_stderr=with_stderr,
95+
stderr=stderr,
96+
pass_fds=(result_file.fileno(), checker_file.fileno()),
97+
)
98+
99+
with tempfile.TemporaryFile() as stderr_file:
100+
renv = execute_channel(stderr=stderr_file)
101+
if renv["return_code"] >= 2:
102+
stderr_file.seek(0)
103+
stderr = stderr_file.read()
104+
raise ChannelError(
105+
"Channel returned code(%d) >= 2. Channel stdout: "
106+
'"%s", stderr: "%s". Channel environ dump: %s'
107+
% (renv["return_code"], renv["stdout"], stderr, env)
108+
)
109+
110+
return renv["stdout"]
111+
112+
113+
def _run_channel(environ, use_sandboxes=False):
114+
ft.download(environ, "hint_file", "hint", add_to_cache=True)
115+
ft.download(environ, "chn_file", "chn", add_to_cache=True)
116+
os.chmod(tempcwd("chn"), 0o700)
117+
result_filename = tempcwd("dec_in")
118+
checker_filename = tempcwd("chn_out")
119+
120+
try:
121+
with open(result_filename, "wb") as result_file, open(
122+
checker_filename, "wb"
123+
) as checker_file:
124+
output = _run_channel_core(
125+
environ, result_file, checker_file, use_sandboxes
126+
)
127+
except (ChannelError, ExecError) as e:
128+
logger.error("Channel failed! %s", e)
129+
logger.error("Environ dump: %s", environ)
130+
raise SystemError(e)
131+
132+
while len(output) < 3:
133+
output.append("")
134+
135+
if six.ensure_binary(output[0]) == b"OK":
136+
environ["channel_result_code"] = "OK"
137+
if output[1]:
138+
environ["channel_result_string"] = _limit_length(output[1]).decode("utf-8")
139+
return True
140+
else:
141+
environ["failed_step"] = "channel"
142+
environ["channel_result_code"] = "WA"
143+
environ["channel_result_string"] = _limit_length(output[1]).decode("utf-8")
144+
return False
145+
146+
147+
def _run_decoder(environ, file_executor, exe_filename, use_sandboxes):
148+
return _run_core(
149+
environ,
150+
file_executor,
151+
tempcwd("dec_in"),
152+
tempcwd("dec_out"),
153+
tempcwd(exe_filename),
154+
"decoder_",
155+
use_sandboxes,
156+
)
157+
158+
159+
def _run_checker_core(env, use_sandboxes=False):
160+
command = ["./chk", "enc_in", "hint", "chn_out", "dec_out"]
161+
162+
def execute_checker(with_stderr=False, stderr=None):
163+
return _run_supplementary(
164+
env,
165+
command,
166+
PRootExecutor("null-sandbox")
167+
if env.get("untrusted_checker", False) and use_sandboxes
168+
else UnprotectedExecutor(),
169+
"checker_",
170+
ignore_errors=True,
171+
forward_stderr=with_stderr,
172+
stderr=stderr,
173+
)
174+
175+
with tempfile.TemporaryFile() as stderr_file:
176+
renv = execute_checker(stderr=stderr_file)
177+
if renv["return_code"] >= 2:
178+
stderr_file.seek(0)
179+
stderr = stderr_file.read()
180+
raise CheckerError(
181+
"Checker returned code(%d) >= 2. Checker stdout: "
182+
'"%s", stderr: "%s". Checker environ dump: %s'
183+
% (renv["return_code"], renv["stdout"], stderr, env)
184+
)
185+
186+
return renv["stdout"]
187+
188+
189+
def _run_checker(environ, use_sandboxes=False):
190+
ft.download(environ, "chk_file", "chk", add_to_cache=True)
191+
os.chmod(tempcwd("chk"), 0o700)
192+
193+
try:
194+
output = _run_checker_core(environ, use_sandboxes)
195+
except (ChannelError, ExecError) as e:
196+
logger.error("Checker failed! %s", e)
197+
logger.error("Environ dump: %s", environ)
198+
raise SystemError(e)
199+
200+
while len(output) < 3:
201+
output.append("")
202+
203+
if six.ensure_binary(output[0]) == b"OK":
204+
environ["checker_result_code"] = "OK"
205+
if output[1]:
206+
environ["checker_result_string"] = _limit_length(output[1]).decode("utf-8")
207+
environ["checker_result_percentage"] = float(output[2] or 100)
208+
return True
209+
else:
210+
environ["failed_step"] = "checker"
211+
environ["checker_result_code"] = "WA"
212+
environ["checker_result_string"] = _limit_length(output[1]).decode("utf-8")
213+
environ["checker_result_percentage"] = 0
214+
return False
215+
216+
217+
def run(environ, executor, use_sandboxes=True):
218+
"""
219+
Common code for executors.
220+
221+
:param: environ Recipe to pass to `filetracker` and `sio.workers.executors`
222+
For all supported options, see the global documentation for
223+
`sio.workers.executors` and prefix them with ``encoder_``
224+
or ``decoder_``.
225+
:param: executor Executor instance used for executing commands.
226+
:param: use_sandboxes Enables safe checking output correctness.
227+
See `sio.executors.checkers`. True by default.
228+
"""
229+
230+
file_executor = get_file_runner(executor, environ)
231+
exe_filename = file_executor.preferred_filename()
232+
233+
ft.download(environ, "exe_file", exe_filename, add_to_cache=True)
234+
os.chmod(tempcwd(exe_filename), 0o700)
235+
236+
encoder_environ = environ.copy()
237+
renv = _run_encoder(encoder_environ, file_executor, exe_filename, use_sandboxes)
238+
_populate_environ(renv, environ, "encoder_")
239+
240+
if renv["result_code"] != "OK":
241+
environ["failed_step"] = "encoder"
242+
return environ
243+
244+
if not _run_channel(environ, use_sandboxes):
245+
return environ
246+
247+
renv = _run_decoder(environ, file_executor, exe_filename, use_sandboxes)
248+
_populate_environ(renv, environ, "decoder_")
249+
250+
if renv["result_code"] != "OK":
251+
environ["failed_step"] = "decoder"
252+
return environ
253+
254+
_run_checker(environ, use_sandboxes)
255+
256+
return environ

sio/executors/executor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from __future__ import absolute_import
2-
from sio.executors import common
2+
from sio.executors import common, encdec_common
33
from sio.workers.executors import SupervisedExecutor
44

55

66
def run(environ):
77
return common.run(environ, SupervisedExecutor())
8+
9+
10+
def encdec_run(environ):
11+
return encdec_common.run(environ, SupervisedExecutor())

sio/executors/sio2jail_exec.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from sio.executors import common
1+
from sio.executors import common, encdec_common
22
from sio.workers.executors import Sio2JailExecutor
33

44

55
def run(environ):
66
return common.run(environ, Sio2JailExecutor())
7+
8+
9+
def encdec_run(environ):
10+
return encdec_common.run(environ, Sio2JailExecutor())

sio/executors/unsafe_exec.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from __future__ import absolute_import
2-
from sio.executors import common
2+
from sio.executors import common, encdec_common
33
from sio.workers.executors import DetailedUnprotectedExecutor
44

55

66
def run(environ):
77
return common.run(environ, DetailedUnprotectedExecutor(), use_sandboxes=False)
8+
9+
10+
def encdec_run(environ):
11+
return encdec_common.run(environ, DetailedUnprotectedExecutor(), use_sandboxes=False)

0 commit comments

Comments
 (0)