def create_compile_process(self, args: List[str]) -> TracedPopen: # Some languages may insist on providing certain functionality (e.g. colored highlighting of errors) if they # feel they are connected to a terminal. Some are more persistent than others in enforcing this, so this hack # aims to provide a convincing-enough lie to the runtime so that it starts singing in color. # # Emulate the streams of a process connected to a terminal: stdin, stdout, and stderr are all ptys. _master, _slave = pty.openpty() # Some runtimes *cough cough* Swift *cough cough* actually check the environment variables too. env = self.get_compile_env() or os.environ.copy() env['TERM'] = 'xterm' # Instruct compilers to put their temporary files into the submission directory, # so that we can allow it as writeable, rather than of all of /tmp. assert self._dir is not None env['TMPDIR'] = self._dir proc = TracedPopen( [utf8bytes(a) for a in args], **{ 'executable': utf8bytes(args[0]), 'security': self.get_compiler_security(), 'stderr': _slave, 'stdout': _slave, 'stdin': _slave, 'cwd': utf8bytes(self._dir), 'env': env, 'nproc': -1, 'fsize': self.executable_size, 'time': self.compiler_time_limit or 0, 'memory': 0, **self.get_compile_popen_kwargs(), }, ) class io_error_wrapper: """ Wrap pty-related IO errors so that we don't crash Popen.communicate() """ def __init__(self, io: IO) -> None: self.io = io def read(self, *args, **kwargs): try: return self.io.read(*args, **kwargs) except (IOError, OSError): return b'' def __getattr__(self, attr): return getattr(self.io, attr) # Since stderr and stdout are connected to the same slave pty, proc.stderr will contain the merged stdout # of the process as well. proc.stderr = io_error_wrapper(os.fdopen(_master, 'rb')) # type: ignore os.close(_slave) return proc
def launch(self, *args, **kwargs) -> TracedPopen: assert self._dir is not None for src, dst in kwargs.get('symlinks', {}).items(): src = os.path.abspath(os.path.join(self._dir, src)) # Disallow the creation of symlinks outside the submission directory. if os.path.commonprefix([src, self._dir]) == self._dir: # If a link already exists under this name, it's probably from a # previous case, but might point to something different. if os.path.islink(src): os.unlink(src) os.symlink(dst, src) else: raise InternalError( 'cannot symlink outside of submission directory') agent = self._file('setbufsize.so') # Hardcode the ABIs for different executors for now if self.name == 'CPP17X': shutil.copyfile(setbufsize32_path, agent) elif self.name == 'TUR': shutil.copyfile(setbufsize86_path, agent) else: shutil.copyfile(setbufsize_path, agent) env = { # Forward LD_LIBRARY_PATH for systems (e.g. Android Termux) that require # it to find shared libraries 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH', ''), 'LD_PRELOAD': agent, 'CPTBOX_STDOUT_BUFFER_SIZE': kwargs.get('stdout_buffer_size'), 'CPTBOX_STDERR_BUFFER_SIZE': kwargs.get('stderr_buffer_size'), } env.update(self.get_env()) executable = self.get_executable() assert executable is not None return TracedPopen( [utf8bytes(a) for a in self.get_cmdline(**kwargs) + list(args)], executable=utf8bytes(executable), security=self.get_security(launch_kwargs=kwargs), address_grace=self.get_address_grace(), data_grace=self.data_grace, personality=self.personality, time=kwargs.get('time', 0), memory=kwargs.get('memory', 0), wall_time=kwargs.get('wall_time'), stdin=kwargs.get('stdin'), stdout=kwargs.get('stdout'), stderr=kwargs.get('stderr'), env=env, cwd=utf8bytes(self._dir), nproc=self.get_nproc(), fsize=self.fsize, )