From 51da00a5dac5a58ed4a4fec7a0e5ae188ce687a4 Mon Sep 17 00:00:00 2001 From: Quantum Date: Sun, 3 Dec 2017 18:14:14 -0500 Subject: [PATCH] Replace old pty-based unbuffered with LD_PRELOAD; #243 --- dmoj/cptbox/sandbox.py | 18 +++++------------- dmoj/executors/base_executor.py | 1 + dmoj/executors/mixins.py | 12 +++++++++--- dmoj/graders/standard.py | 6 +++--- dmoj/utils/__init__.py | 4 ++++ 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/dmoj/cptbox/sandbox.py b/dmoj/cptbox/sandbox.py index 885ecda08..4f64fb24f 100644 --- a/dmoj/cptbox/sandbox.py +++ b/dmoj/cptbox/sandbox.py @@ -3,7 +3,6 @@ import errno import logging import os -import pty import re import select import signal @@ -140,7 +139,7 @@ class SecurePopen(six.with_metaclass(SecurePopenMeta, Process)): def __init__(self, debugger, _, args, executable=None, security=None, time=0, memory=0, stdin=PIPE, stdout=PIPE, stderr=None, env=None, nproc=0, address_grace=4096, personality=0, cwd='', - fds=None, unbuffered=False, wall_time=None): + fds=None, wall_time=None): self._debugger_type = debugger self._syscall_index = index = _SYSCALL_INDICIES[debugger] self._executable = executable or _find_exe(args[0]) @@ -157,7 +156,7 @@ def __init__(self, debugger, _, args, executable=None, security=None, time=0, me self._nproc = nproc self._tle = False self._fds = fds - self.__init_streams(stdin, stdout, stderr, unbuffered) + self.__init_streams(stdin, stdout, stderr) self.protection_fault = None self.debugger._syscall_index = index @@ -296,14 +295,11 @@ def _shocker_thread(self): else: time.sleep(0.01) - def __init_streams(self, stdin, stdout, stderr, unbuffered): + def __init_streams(self, stdin, stdout, stderr): self.stdin = self.stdout = self.stderr = None - if unbuffered: - master, slave = pty.openpty() - if stdin is PIPE: - self._child_stdin, self._stdin = (os.dup(slave), os.dup(master)) if unbuffered else os.pipe() + self._child_stdin, self._stdin = os.pipe() self.stdin = os.fdopen(self._stdin, 'w') elif isinstance(stdin, int): self._child_stdin, self._stdin = stdin, -1 @@ -313,7 +309,7 @@ def __init_streams(self, stdin, stdout, stderr, unbuffered): self._child_stdin = self._stdin = -1 if stdout is PIPE: - self._stdout, self._child_stdout = (os.dup(master), os.dup(slave)) if unbuffered else os.pipe() + self._stdout, self._child_stdout = os.pipe() self.stdout = os.fdopen(self._stdout, 'r') elif isinstance(stdout, int): self._stdout, self._child_stdout = -1, stdout @@ -332,10 +328,6 @@ def __init_streams(self, stdin, stdout, stderr, unbuffered): else: self._stderr = self._child_stderr = -1 - if unbuffered: - os.close(master) - os.close(slave) - # All communicate stuff copied from subprocess. def communicate(self, input=None): # Optimization: If we are only using one pipe, or no pipe at diff --git a/dmoj/executors/base_executor.py b/dmoj/executors/base_executor.py index dff006bb2..e959b8c94 100644 --- a/dmoj/executors/base_executor.py +++ b/dmoj/executors/base_executor.py @@ -40,6 +40,7 @@ def __init__(self, problem_id, source_code, **kwargs): self.problem = problem_id self.source = source_code self._hints = kwargs.pop('hints', []) + self.unbuffered = kwargs.pop('unbuffered', None) or False def cleanup(self): if not hasattr(self, '_dir'): diff --git a/dmoj/executors/mixins.py b/dmoj/executors/mixins.py index 60cc43a02..0ae908a2a 100644 --- a/dmoj/executors/mixins.py +++ b/dmoj/executors/mixins.py @@ -1,8 +1,10 @@ import os +import shutil import sys from shutil import copyfile from dmoj.judgeenv import env +from dmoj.utils import nobuf_path from dmoj.utils.unicode import utf8bytes try: @@ -103,7 +105,12 @@ def get_address_grace(self): return self.address_grace def get_env(self): - return {'LANG': 'C'} + env = {'LANG': 'C'} + if self.unbuffered: + unbuf = self._file('unbuf.so') + shutil.copyfile(nobuf_path, unbuf) + env['LD_PRELOAD'] = unbuf + return env def launch(self, *args, **kwargs): return SecurePopen([utf8bytes(a) for a in self.get_cmdline() + list(args)], @@ -114,8 +121,7 @@ def launch(self, *args, **kwargs): time=kwargs.get('time'), memory=kwargs.get('memory'), wall_time=kwargs.get('wall_time'), stderr=(PIPE if kwargs.get('pipe_stderr', False) else None), - env=self.get_env(), cwd=utf8bytes(self._dir), nproc=self.get_nproc(), - unbuffered=kwargs.get('unbuffered', False)) + env=self.get_env(), cwd=utf8bytes(self._dir), nproc=self.get_nproc()) except ImportError: pass diff --git a/dmoj/graders/standard.py b/dmoj/graders/standard.py index 4dee5d718..f13576373 100644 --- a/dmoj/graders/standard.py +++ b/dmoj/graders/standard.py @@ -33,8 +33,7 @@ def grade(self, case): input = case.input_data() # cache generator data self._current_proc = self.binary.launch(time=self.problem.time_limit, memory=self.problem.memory_limit, - pipe_stderr=True, unbuffered=case.config.unbuffered, - io_redirects=case.io_redirects(), + pipe_stderr=True, io_redirects=case.io_redirects(), wall_time=case.config.wall_time_factor * self.problem.time_limit) error = self._interact_with_process(case, result, input) @@ -165,7 +164,8 @@ def _generate_binary(self): try: # Fetch an appropriate executor for the language binary = executors[self.language].Executor(self.problem.id, self.source, - hints=self.problem.config.hints or []) + hints=self.problem.config.hints or [], + unbuffered=self.problem.config.unbuffered) except CompileError as compilation_error: error = compilation_error.args[0] error = error.decode('mbcs') if os.name == 'nt' and isinstance(error, six.binary_type) else error diff --git a/dmoj/utils/__init__.py b/dmoj/utils/__init__.py index e69de29bb..ed71801ad 100644 --- a/dmoj/utils/__init__.py +++ b/dmoj/utils/__init__.py @@ -0,0 +1,4 @@ +import os + +nobuf_path = os.path.join(os.path.dirname(__file__), 'nobuf.so') +del os