12d57dc69SVladimir Kotal# 22d57dc69SVladimir Kotal# CDDL HEADER START 32d57dc69SVladimir Kotal# 42d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the 52d57dc69SVladimir Kotal# Common Development and Distribution License (the "License"). 62d57dc69SVladimir Kotal# You may not use this file except in compliance with the License. 72d57dc69SVladimir Kotal# 82d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific 92d57dc69SVladimir Kotal# language governing permissions and limitations under the License. 102d57dc69SVladimir Kotal# 112d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each 122d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt. 132d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the 142d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying 152d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner] 162d57dc69SVladimir Kotal# 172d57dc69SVladimir Kotal# CDDL HEADER END 182d57dc69SVladimir Kotal# 192d57dc69SVladimir Kotal 202d57dc69SVladimir Kotal# 21*ffda442aSVladimir Kotal# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. 222d57dc69SVladimir Kotal# 232d57dc69SVladimir Kotal 242d57dc69SVladimir Kotalimport logging 252d57dc69SVladimir Kotalimport os 262d57dc69SVladimir Kotalimport signal 272d57dc69SVladimir Kotalimport subprocess 282d57dc69SVladimir Kotalimport threading 292d57dc69SVladimir Kotalimport time 302d57dc69SVladimir Kotal 312d57dc69SVladimir Kotal 322d57dc69SVladimir Kotalclass TimeoutException(Exception): 332d57dc69SVladimir Kotal """ 342d57dc69SVladimir Kotal Exception returned when command exceeded its timeout. 352d57dc69SVladimir Kotal """ 362d57dc69SVladimir Kotal pass 372d57dc69SVladimir Kotal 382d57dc69SVladimir Kotal 392d57dc69SVladimir Kotalclass Command: 402d57dc69SVladimir Kotal """ 412d57dc69SVladimir Kotal wrapper for synchronous execution of commands via subprocess.Popen() 422d57dc69SVladimir Kotal and getting their output (stderr is redirected to stdout by default) 432d57dc69SVladimir Kotal and exit value 442d57dc69SVladimir Kotal """ 452d57dc69SVladimir Kotal 462d57dc69SVladimir Kotal # state definitions 472d57dc69SVladimir Kotal FINISHED = "finished" 482d57dc69SVladimir Kotal INTERRUPTED = "interrupted" 492d57dc69SVladimir Kotal ERRORED = "errored" 502d57dc69SVladimir Kotal TIMEDOUT = "timed out" 512d57dc69SVladimir Kotal 522d57dc69SVladimir Kotal def __init__(self, cmd, args_subst=None, args_append=None, logger=None, 532d57dc69SVladimir Kotal excl_subst=False, work_dir=None, env_vars=None, timeout=None, 542d57dc69SVladimir Kotal redirect_stderr=True, resource_limits=None, doprint=False): 552d57dc69SVladimir Kotal 562d57dc69SVladimir Kotal if doprint is None: 572d57dc69SVladimir Kotal doprint = False 582d57dc69SVladimir Kotal 592d57dc69SVladimir Kotal if isinstance(doprint, list): 602d57dc69SVladimir Kotal doprint = doprint[0] 612d57dc69SVladimir Kotal 622d57dc69SVladimir Kotal self.cmd = list(map(str, cmd)) 632d57dc69SVladimir Kotal self.state = "notrun" 642d57dc69SVladimir Kotal self.excl_subst = excl_subst 652d57dc69SVladimir Kotal self.work_dir = work_dir 662d57dc69SVladimir Kotal self.env_vars = env_vars 672d57dc69SVladimir Kotal self.timeout = timeout 682d57dc69SVladimir Kotal self.pid = None 692d57dc69SVladimir Kotal self.redirect_stderr = redirect_stderr 702d57dc69SVladimir Kotal self.limits = resource_limits 712d57dc69SVladimir Kotal self.doprint = doprint 722d57dc69SVladimir Kotal self.err = None 732d57dc69SVladimir Kotal self.returncode = None 742d57dc69SVladimir Kotal 752d57dc69SVladimir Kotal self.logger = logger or logging.getLogger(__name__) 762d57dc69SVladimir Kotal 772d57dc69SVladimir Kotal if args_subst or args_append: 782d57dc69SVladimir Kotal self.fill_arg(args_append, args_subst) 792d57dc69SVladimir Kotal 80f174e573SVladimir Kotal self.out = None 81f174e573SVladimir Kotal 822d57dc69SVladimir Kotal def __str__(self): 832d57dc69SVladimir Kotal return " ".join(self.cmd) 842d57dc69SVladimir Kotal 852d57dc69SVladimir Kotal def execute(self): 862d57dc69SVladimir Kotal """ 872d57dc69SVladimir Kotal Execute the command and capture its output and return code. 882d57dc69SVladimir Kotal """ 892d57dc69SVladimir Kotal 902d57dc69SVladimir Kotal class TimeoutThread(threading.Thread): 912d57dc69SVladimir Kotal """ 922d57dc69SVladimir Kotal Wait until the timeout specified in seconds expires and kill 932d57dc69SVladimir Kotal the process specified by the Popen object after that. 942d57dc69SVladimir Kotal If timeout expires, TimeoutException is stored in the object 952d57dc69SVladimir Kotal and can be retrieved by the caller. 962d57dc69SVladimir Kotal """ 972d57dc69SVladimir Kotal 982d57dc69SVladimir Kotal def __init__(self, logger, timeout, condition, p): 992d57dc69SVladimir Kotal super(TimeoutThread, self).__init__() 1002d57dc69SVladimir Kotal self.timeout = timeout 1012d57dc69SVladimir Kotal self.popen = p 1022d57dc69SVladimir Kotal self.condition = condition 1032d57dc69SVladimir Kotal self.logger = logger 1042d57dc69SVladimir Kotal self.start() 1052d57dc69SVladimir Kotal self.exception = None 1062d57dc69SVladimir Kotal 1072d57dc69SVladimir Kotal def terminate(self, p): 1082d57dc69SVladimir Kotal """ 1092d57dc69SVladimir Kotal Make sure the process goes away. 1102d57dc69SVladimir Kotal """ 1112d57dc69SVladimir Kotal self.logger.info("Terminating PID {}".format(p.pid)) 1122d57dc69SVladimir Kotal p.terminate() 1132d57dc69SVladimir Kotal 1142d57dc69SVladimir Kotal # The following code tries more methods to terminate 1152d57dc69SVladimir Kotal # the process and is specific to Unix. 1162d57dc69SVladimir Kotal if os.name == 'posix': 1172d57dc69SVladimir Kotal timeout = self.timeout 1182d57dc69SVladimir Kotal # disable E1101 - non existent attribute SIGKILL on windows 1192d57dc69SVladimir Kotal # pylint: disable=E1101 1202d57dc69SVladimir Kotal term_signals = [signal.SIGINT, signal.SIGKILL] 1212d57dc69SVladimir Kotal # pylint: enable=E1101 1222d57dc69SVladimir Kotal for sig in term_signals: 1232d57dc69SVladimir Kotal timeout = timeout / 2 # exponential back-off 1242d57dc69SVladimir Kotal self.logger.info("Sleeping for {} seconds". 1252d57dc69SVladimir Kotal format(timeout)) 1262d57dc69SVladimir Kotal time.sleep(timeout) 1272d57dc69SVladimir Kotal 1282d57dc69SVladimir Kotal if p.poll() is None: 1292d57dc69SVladimir Kotal self.logger.info("Command with PID {} still alive," 1302d57dc69SVladimir Kotal " killing with signal {}". 1312d57dc69SVladimir Kotal format(p.pid, sig)) 1322d57dc69SVladimir Kotal p.send_signal(sig) 1332d57dc69SVladimir Kotal else: 1342d57dc69SVladimir Kotal self.logger.info("Command with PID {} is gone". 1352d57dc69SVladimir Kotal format(p.pid)) 1362d57dc69SVladimir Kotal break 1372d57dc69SVladimir Kotal 1382d57dc69SVladimir Kotal def run(self): 1392d57dc69SVladimir Kotal with self.condition: 1402d57dc69SVladimir Kotal if not self.condition.wait(self.timeout): 1412d57dc69SVladimir Kotal p = self.popen 1422d57dc69SVladimir Kotal self.logger.info("Terminating command {} with PID {} " 1432d57dc69SVladimir Kotal "after timeout of {} seconds". 1442d57dc69SVladimir Kotal format(p.args, p.pid, self.timeout)) 1452d57dc69SVladimir Kotal self.exception = TimeoutException("Command {} with pid" 1462d57dc69SVladimir Kotal " {} timed out". 1472d57dc69SVladimir Kotal format(p.args, 1482d57dc69SVladimir Kotal p.pid)) 1492d57dc69SVladimir Kotal self.terminate(p) 1502d57dc69SVladimir Kotal else: 1512d57dc69SVladimir Kotal return None 1522d57dc69SVladimir Kotal 1532d57dc69SVladimir Kotal def get_exception(self): 1542d57dc69SVladimir Kotal return self.exception 1552d57dc69SVladimir Kotal 1562d57dc69SVladimir Kotal class OutputThread(threading.Thread): 1572d57dc69SVladimir Kotal """ 1582d57dc69SVladimir Kotal Capture data from subprocess.Popen(). This avoids hangs when 1592d57dc69SVladimir Kotal stdout/stderr buffers fill up. 1602d57dc69SVladimir Kotal """ 1612d57dc69SVladimir Kotal 1622d57dc69SVladimir Kotal def __init__(self, event, logger, doprint=False): 1632d57dc69SVladimir Kotal super(OutputThread, self).__init__() 1642d57dc69SVladimir Kotal self.read_fd, self.write_fd = os.pipe() 1652d57dc69SVladimir Kotal self.pipe_fobj = os.fdopen(self.read_fd, encoding='utf8') 1662d57dc69SVladimir Kotal self.out = [] 1672d57dc69SVladimir Kotal self.event = event 1682d57dc69SVladimir Kotal self.logger = logger 1692d57dc69SVladimir Kotal self.doprint = doprint 1702d57dc69SVladimir Kotal 1712d57dc69SVladimir Kotal # Start the thread now. 1722d57dc69SVladimir Kotal self.start() 1732d57dc69SVladimir Kotal 1742d57dc69SVladimir Kotal def run(self): 1752d57dc69SVladimir Kotal """ 1762d57dc69SVladimir Kotal It might happen that after the process is gone, the thread 1772d57dc69SVladimir Kotal still has data to read from the pipe. Hence, event is used 1782d57dc69SVladimir Kotal to synchronize with the caller. 1792d57dc69SVladimir Kotal """ 1802d57dc69SVladimir Kotal while True: 1812d57dc69SVladimir Kotal line = self.pipe_fobj.readline() 1822d57dc69SVladimir Kotal if not line: 1832d57dc69SVladimir Kotal self.logger.debug("end of output") 1842d57dc69SVladimir Kotal self.pipe_fobj.close() 1852d57dc69SVladimir Kotal self.event.set() 1862d57dc69SVladimir Kotal return 1872d57dc69SVladimir Kotal 1882d57dc69SVladimir Kotal self.out.append(line) 1892d57dc69SVladimir Kotal 1902d57dc69SVladimir Kotal if self.doprint: 1912d57dc69SVladimir Kotal # Even if logging below fails, the thread has to keep 1922d57dc69SVladimir Kotal # running to avoid hangups of the executed command. 1932d57dc69SVladimir Kotal try: 1942d57dc69SVladimir Kotal self.logger.info(line.rstrip()) 1952d57dc69SVladimir Kotal except Exception as print_exc: 1962d57dc69SVladimir Kotal self.logger.error(print_exc) 1972d57dc69SVladimir Kotal 1982d57dc69SVladimir Kotal def getoutput(self): 1992d57dc69SVladimir Kotal return self.out 2002d57dc69SVladimir Kotal 2012d57dc69SVladimir Kotal def fileno(self): 2022d57dc69SVladimir Kotal return self.write_fd 2032d57dc69SVladimir Kotal 2042d57dc69SVladimir Kotal def close(self): 2052d57dc69SVladimir Kotal self.logger.debug("closed") 2062d57dc69SVladimir Kotal os.close(self.write_fd) 2072d57dc69SVladimir Kotal 2082d57dc69SVladimir Kotal orig_work_dir = None 2092d57dc69SVladimir Kotal if self.work_dir: 2102d57dc69SVladimir Kotal try: 2112d57dc69SVladimir Kotal orig_work_dir = os.getcwd() 2122d57dc69SVladimir Kotal except OSError: 2132d57dc69SVladimir Kotal self.state = Command.ERRORED 2142d57dc69SVladimir Kotal self.logger.error("Cannot get working directory", 2152d57dc69SVladimir Kotal exc_info=True) 2162d57dc69SVladimir Kotal return 2172d57dc69SVladimir Kotal 2182d57dc69SVladimir Kotal try: 2192d57dc69SVladimir Kotal os.chdir(self.work_dir) 2202d57dc69SVladimir Kotal except OSError: 2212d57dc69SVladimir Kotal self.state = Command.ERRORED 2222d57dc69SVladimir Kotal self.logger.error("Cannot change working directory to {}". 2232d57dc69SVladimir Kotal format(self.work_dir), exc_info=True) 2242d57dc69SVladimir Kotal return 2252d57dc69SVladimir Kotal 2262d57dc69SVladimir Kotal timeout_thread = None 2272d57dc69SVladimir Kotal output_event = threading.Event() 2282d57dc69SVladimir Kotal output_thread = OutputThread(output_event, self.logger, 2292d57dc69SVladimir Kotal doprint=self.doprint) 2302d57dc69SVladimir Kotal 2312d57dc69SVladimir Kotal # If stderr redirection is off, setup a thread that will capture 2322d57dc69SVladimir Kotal # stderr data. 233f174e573SVladimir Kotal stderr_thread = None 234f174e573SVladimir Kotal stderr_event = None 2352d57dc69SVladimir Kotal if self.redirect_stderr: 2362d57dc69SVladimir Kotal stderr_dest = subprocess.STDOUT 2372d57dc69SVladimir Kotal else: 2382d57dc69SVladimir Kotal stderr_event = threading.Event() 2392d57dc69SVladimir Kotal stderr_thread = OutputThread(stderr_event, self.logger, 2402d57dc69SVladimir Kotal doprint=self.doprint) 2412d57dc69SVladimir Kotal stderr_dest = stderr_thread 2422d57dc69SVladimir Kotal 243f174e573SVladimir Kotal start_time = None 2442d57dc69SVladimir Kotal try: 2452d57dc69SVladimir Kotal start_time = time.time() 2462d57dc69SVladimir Kotal try: 2472d57dc69SVladimir Kotal self.logger.debug("working directory = {}".format(os.getcwd())) 2482d57dc69SVladimir Kotal except PermissionError: 2492d57dc69SVladimir Kotal pass 2502d57dc69SVladimir Kotal self.logger.debug("command = '{}'".format(self)) 2512d57dc69SVladimir Kotal my_args = {'stderr': stderr_dest, 2522d57dc69SVladimir Kotal 'stdout': output_thread} 2532d57dc69SVladimir Kotal if self.env_vars: 2542d57dc69SVladimir Kotal my_env = os.environ.copy() 2552d57dc69SVladimir Kotal my_env.update(self.env_vars) 2562d57dc69SVladimir Kotal self.logger.debug("environment variables: {}".format(my_env)) 2572d57dc69SVladimir Kotal my_args['env'] = my_env 2582d57dc69SVladimir Kotal if self.limits: 2592d57dc69SVladimir Kotal my_args['preexec_fn'] = \ 2602d57dc69SVladimir Kotal lambda: self.set_resource_limits(self.limits) 2612d57dc69SVladimir Kotal 2622d57dc69SVladimir Kotal # Actually run the command. 2632d57dc69SVladimir Kotal p = subprocess.Popen(self.cmd, **my_args) 2642d57dc69SVladimir Kotal 2652d57dc69SVladimir Kotal self.pid = p.pid 2662d57dc69SVladimir Kotal 2672d57dc69SVladimir Kotal if self.timeout: 2682d57dc69SVladimir Kotal time_condition = threading.Condition() 2692d57dc69SVladimir Kotal self.logger.debug("Setting timeout to {} seconds". 2702d57dc69SVladimir Kotal format(self.timeout)) 2712d57dc69SVladimir Kotal timeout_thread = TimeoutThread(self.logger, self.timeout, 2722d57dc69SVladimir Kotal time_condition, p) 2732d57dc69SVladimir Kotal 2742d57dc69SVladimir Kotal self.logger.debug("Waiting for process with PID {}".format(p.pid)) 2752d57dc69SVladimir Kotal p.wait() 2761ac92230SAdam Hornáček self.logger.debug("Done waiting") 2772d57dc69SVladimir Kotal 2782d57dc69SVladimir Kotal if self.timeout: 2792d57dc69SVladimir Kotal e = timeout_thread.get_exception() 2802d57dc69SVladimir Kotal if e: 2812d57dc69SVladimir Kotal raise e # pylint: disable=E0702 2822d57dc69SVladimir Kotal 2832d57dc69SVladimir Kotal except KeyboardInterrupt: 2842d57dc69SVladimir Kotal self.logger.info("Got KeyboardException while processing ", 2852d57dc69SVladimir Kotal exc_info=True) 2862d57dc69SVladimir Kotal self.state = Command.INTERRUPTED 2872d57dc69SVladimir Kotal except OSError: 2882d57dc69SVladimir Kotal self.logger.error("Got OS error", exc_info=True) 2892d57dc69SVladimir Kotal self.state = Command.ERRORED 2902d57dc69SVladimir Kotal except TimeoutException: 2912d57dc69SVladimir Kotal self.logger.error("Timed out") 2922d57dc69SVladimir Kotal self.state = Command.TIMEDOUT 2932d57dc69SVladimir Kotal else: 2942d57dc69SVladimir Kotal self.state = Command.FINISHED 2952d57dc69SVladimir Kotal self.returncode = int(p.returncode) 2962d57dc69SVladimir Kotal self.logger.debug("'{}' -> {}".format(self, self.getretcode())) 2972d57dc69SVladimir Kotal finally: 2982d57dc69SVladimir Kotal if self.timeout != 0 and timeout_thread: 2992d57dc69SVladimir Kotal with time_condition: 3002d57dc69SVladimir Kotal time_condition.notifyAll() 3012d57dc69SVladimir Kotal 3022d57dc69SVladimir Kotal # The subprocess module does not close the write pipe descriptor 3032d57dc69SVladimir Kotal # it fetched via OutputThread's fileno() so in order to gracefully 3042d57dc69SVladimir Kotal # exit the read loop we have to close it here ourselves. 3052d57dc69SVladimir Kotal output_thread.close() 3062d57dc69SVladimir Kotal self.logger.debug("Waiting on output thread to finish reading") 3072d57dc69SVladimir Kotal output_event.wait() 3082d57dc69SVladimir Kotal self.out = output_thread.getoutput() 3092d57dc69SVladimir Kotal 310f174e573SVladimir Kotal if not self.redirect_stderr and stderr_thread and stderr_event: 3112d57dc69SVladimir Kotal stderr_thread.close() 3122d57dc69SVladimir Kotal self.logger.debug("Waiting on stderr thread to finish reading") 3132d57dc69SVladimir Kotal stderr_event.wait() 3142d57dc69SVladimir Kotal self.err = stderr_thread.getoutput() 3152d57dc69SVladimir Kotal 316f174e573SVladimir Kotal if start_time: 3172d57dc69SVladimir Kotal elapsed_time = time.time() - start_time 3182d57dc69SVladimir Kotal self.logger.debug("Command '{}' took {} seconds". 3192d57dc69SVladimir Kotal format(self, int(elapsed_time))) 3202d57dc69SVladimir Kotal 3212d57dc69SVladimir Kotal if orig_work_dir: 3222d57dc69SVladimir Kotal try: 3232d57dc69SVladimir Kotal os.chdir(orig_work_dir) 3242d57dc69SVladimir Kotal except OSError: 3252d57dc69SVladimir Kotal self.state = Command.ERRORED 3262d57dc69SVladimir Kotal self.logger.error("Cannot change working directory back to {}". 3272d57dc69SVladimir Kotal format(orig_work_dir), exc_info=True) 3282d57dc69SVladimir Kotal return 3292d57dc69SVladimir Kotal 3302d57dc69SVladimir Kotal def fill_arg(self, args_append=None, args_subst=None): 3312d57dc69SVladimir Kotal """ 3322d57dc69SVladimir Kotal Replace argument names with actual values or append arguments 3332d57dc69SVladimir Kotal to the command vector. 3342d57dc69SVladimir Kotal 3352d57dc69SVladimir Kotal The action depends whether exclusive substitution is on. 3362d57dc69SVladimir Kotal If yes, arguments will be appended only if no substitution was 3372d57dc69SVladimir Kotal performed. 3382d57dc69SVladimir Kotal """ 3392d57dc69SVladimir Kotal 3402d57dc69SVladimir Kotal newcmd = [] 3412d57dc69SVladimir Kotal subst_done = -1 3422d57dc69SVladimir Kotal for i, cmdarg in enumerate(self.cmd): 3432d57dc69SVladimir Kotal if args_subst: 3442d97c0a2SVladimir Kotal newarg = cmdarg 3452d57dc69SVladimir Kotal for pattern in args_subst.keys(): 3462d97c0a2SVladimir Kotal if pattern in newarg and args_subst[pattern]: 3472d97c0a2SVladimir Kotal self.logger.debug("replacing '{}' in '{}' with '{}'". 3482d97c0a2SVladimir Kotal format(pattern, newarg, 3492d97c0a2SVladimir Kotal args_subst[pattern])) 3502d97c0a2SVladimir Kotal newarg = newarg.replace(pattern, args_subst[pattern]) 3512d97c0a2SVladimir Kotal self.logger.debug("replaced argument with {}". 3522d57dc69SVladimir Kotal format(newarg)) 3532d57dc69SVladimir Kotal subst_done = i 3542d57dc69SVladimir Kotal 3552d57dc69SVladimir Kotal if subst_done != i: 3562d57dc69SVladimir Kotal newcmd.append(self.cmd[i]) 3572d57dc69SVladimir Kotal else: 3582d97c0a2SVladimir Kotal newcmd.append(newarg) 3592d97c0a2SVladimir Kotal else: 3602d57dc69SVladimir Kotal newcmd.append(self.cmd[i]) 3612d57dc69SVladimir Kotal 3622d57dc69SVladimir Kotal if args_append and (not self.excl_subst or subst_done == -1): 3632d57dc69SVladimir Kotal self.logger.debug("appending {}".format(args_append)) 3642d57dc69SVladimir Kotal newcmd.extend(args_append) 3652d57dc69SVladimir Kotal 3662d57dc69SVladimir Kotal self.cmd = newcmd 3672d57dc69SVladimir Kotal 3682d57dc69SVladimir Kotal def get_resource(self, name): 3692d57dc69SVladimir Kotal try: 3702d57dc69SVladimir Kotal import resource 3712d57dc69SVladimir Kotal if name == "RLIMIT_NOFILE": 3722d57dc69SVladimir Kotal return resource.RLIMIT_NOFILE 3732d57dc69SVladimir Kotal except ImportError: 3742d57dc69SVladimir Kotal raise NotImplementedError("manipulating resources is not " 3752d57dc69SVladimir Kotal "available on your platform") 3762d57dc69SVladimir Kotal 3772d57dc69SVladimir Kotal raise NotImplementedError("unknown resource") 3782d57dc69SVladimir Kotal 3792d57dc69SVladimir Kotal def set_resource_limit(self, name, value): 3802d57dc69SVladimir Kotal try: 3812d57dc69SVladimir Kotal import resource 3822d57dc69SVladimir Kotal self.logger.debug("Setting resource {} to {}" 3832d57dc69SVladimir Kotal .format(name, value)) 3842d57dc69SVladimir Kotal resource.setrlimit(self.get_resource(name), (value, value)) 3852d57dc69SVladimir Kotal except ImportError: 3862d57dc69SVladimir Kotal raise NotImplementedError("manipulating resources is not " 3872d57dc69SVladimir Kotal "available on your platform") 3882d57dc69SVladimir Kotal 3892d57dc69SVladimir Kotal def set_resource_limits(self, limits): 3902d57dc69SVladimir Kotal self.logger.debug("Setting resource limits") 3912d57dc69SVladimir Kotal for name, value in limits.items(): 3922d57dc69SVladimir Kotal self.set_resource_limit(name, value) 3932d57dc69SVladimir Kotal 3942d57dc69SVladimir Kotal def getretcode(self): 3952d57dc69SVladimir Kotal if self.state != Command.FINISHED: 3962d57dc69SVladimir Kotal return None 3972d57dc69SVladimir Kotal else: 3982d57dc69SVladimir Kotal return self.returncode 3992d57dc69SVladimir Kotal 4002d57dc69SVladimir Kotal def getoutputstr(self): 4012d57dc69SVladimir Kotal if self.state == Command.FINISHED: 4022d57dc69SVladimir Kotal return "".join(self.out).strip() 4032d57dc69SVladimir Kotal else: 4042d57dc69SVladimir Kotal return None 4052d57dc69SVladimir Kotal 4062d57dc69SVladimir Kotal def getoutput(self): 4072d57dc69SVladimir Kotal if self.state == Command.FINISHED: 4082d57dc69SVladimir Kotal return self.out 4092d57dc69SVladimir Kotal else: 4102d57dc69SVladimir Kotal return None 4112d57dc69SVladimir Kotal 4122d57dc69SVladimir Kotal def geterroutput(self): 4132d57dc69SVladimir Kotal return self.err 4142d57dc69SVladimir Kotal 4152d57dc69SVladimir Kotal def geterroutputstr(self): 4162d57dc69SVladimir Kotal if self.err: 4172d57dc69SVladimir Kotal return "".join(self.err).strip() 4182d57dc69SVladimir Kotal else: 4192d57dc69SVladimir Kotal return "" 4202d57dc69SVladimir Kotal 4212d57dc69SVladimir Kotal def getstate(self): 4222d57dc69SVladimir Kotal return self.state 4232d57dc69SVladimir Kotal 4242d57dc69SVladimir Kotal def getpid(self): 4252d57dc69SVladimir Kotal return self.pid 4262d57dc69SVladimir Kotal 4272d57dc69SVladimir Kotal def log_error(self, msg): 4282d57dc69SVladimir Kotal if self.state is Command.FINISHED: 4292d57dc69SVladimir Kotal self.logger.error("{}: command {} in directory {} exited with {}". 4302d57dc69SVladimir Kotal format(msg, self.cmd, self.work_dir, 4312d57dc69SVladimir Kotal self.getretcode())) 4322d57dc69SVladimir Kotal else: 4332d57dc69SVladimir Kotal self.logger.error("{}: command {} in directory {} ended with " 4342d57dc69SVladimir Kotal "invalid state {}". 4352d57dc69SVladimir Kotal format(msg, self.cmd, self.work_dir, self.state)) 436