1*2d57dc69SVladimir Kotal# 2*2d57dc69SVladimir Kotal# CDDL HEADER START 3*2d57dc69SVladimir Kotal# 4*2d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the 5*2d57dc69SVladimir Kotal# Common Development and Distribution License (the "License"). 6*2d57dc69SVladimir Kotal# You may not use this file except in compliance with the License. 7*2d57dc69SVladimir Kotal# 8*2d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific 9*2d57dc69SVladimir Kotal# language governing permissions and limitations under the License. 10*2d57dc69SVladimir Kotal# 11*2d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each 12*2d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt. 13*2d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the 14*2d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying 15*2d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner] 16*2d57dc69SVladimir Kotal# 17*2d57dc69SVladimir Kotal# CDDL HEADER END 18*2d57dc69SVladimir Kotal# 19*2d57dc69SVladimir Kotal 20*2d57dc69SVladimir Kotal# 21*2d57dc69SVladimir Kotal# Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. 22*2d57dc69SVladimir Kotal# 23*2d57dc69SVladimir Kotal 24*2d57dc69SVladimir Kotalimport logging 25*2d57dc69SVladimir Kotalimport os 26*2d57dc69SVladimir Kotalimport signal 27*2d57dc69SVladimir Kotalimport subprocess 28*2d57dc69SVladimir Kotalimport threading 29*2d57dc69SVladimir Kotalimport time 30*2d57dc69SVladimir Kotal 31*2d57dc69SVladimir Kotal 32*2d57dc69SVladimir Kotalclass TimeoutException(Exception): 33*2d57dc69SVladimir Kotal """ 34*2d57dc69SVladimir Kotal Exception returned when command exceeded its timeout. 35*2d57dc69SVladimir Kotal """ 36*2d57dc69SVladimir Kotal pass 37*2d57dc69SVladimir Kotal 38*2d57dc69SVladimir Kotal 39*2d57dc69SVladimir Kotalclass Command: 40*2d57dc69SVladimir Kotal """ 41*2d57dc69SVladimir Kotal wrapper for synchronous execution of commands via subprocess.Popen() 42*2d57dc69SVladimir Kotal and getting their output (stderr is redirected to stdout by default) 43*2d57dc69SVladimir Kotal and exit value 44*2d57dc69SVladimir Kotal """ 45*2d57dc69SVladimir Kotal 46*2d57dc69SVladimir Kotal # state definitions 47*2d57dc69SVladimir Kotal FINISHED = "finished" 48*2d57dc69SVladimir Kotal INTERRUPTED = "interrupted" 49*2d57dc69SVladimir Kotal ERRORED = "errored" 50*2d57dc69SVladimir Kotal TIMEDOUT = "timed out" 51*2d57dc69SVladimir Kotal 52*2d57dc69SVladimir Kotal def __init__(self, cmd, args_subst=None, args_append=None, logger=None, 53*2d57dc69SVladimir Kotal excl_subst=False, work_dir=None, env_vars=None, timeout=None, 54*2d57dc69SVladimir Kotal redirect_stderr=True, resource_limits=None, doprint=False): 55*2d57dc69SVladimir Kotal 56*2d57dc69SVladimir Kotal if doprint is None: 57*2d57dc69SVladimir Kotal doprint = False 58*2d57dc69SVladimir Kotal 59*2d57dc69SVladimir Kotal if isinstance(doprint, list): 60*2d57dc69SVladimir Kotal doprint = doprint[0] 61*2d57dc69SVladimir Kotal 62*2d57dc69SVladimir Kotal self.cmd = list(map(str, cmd)) 63*2d57dc69SVladimir Kotal self.state = "notrun" 64*2d57dc69SVladimir Kotal self.excl_subst = excl_subst 65*2d57dc69SVladimir Kotal self.work_dir = work_dir 66*2d57dc69SVladimir Kotal self.env_vars = env_vars 67*2d57dc69SVladimir Kotal self.timeout = timeout 68*2d57dc69SVladimir Kotal self.pid = None 69*2d57dc69SVladimir Kotal self.redirect_stderr = redirect_stderr 70*2d57dc69SVladimir Kotal self.limits = resource_limits 71*2d57dc69SVladimir Kotal self.doprint = doprint 72*2d57dc69SVladimir Kotal self.err = None 73*2d57dc69SVladimir Kotal self.returncode = None 74*2d57dc69SVladimir Kotal 75*2d57dc69SVladimir Kotal self.logger = logger or logging.getLogger(__name__) 76*2d57dc69SVladimir Kotal 77*2d57dc69SVladimir Kotal if args_subst or args_append: 78*2d57dc69SVladimir Kotal self.fill_arg(args_append, args_subst) 79*2d57dc69SVladimir Kotal 80*2d57dc69SVladimir Kotal def __str__(self): 81*2d57dc69SVladimir Kotal return " ".join(self.cmd) 82*2d57dc69SVladimir Kotal 83*2d57dc69SVladimir Kotal def execute(self): 84*2d57dc69SVladimir Kotal """ 85*2d57dc69SVladimir Kotal Execute the command and capture its output and return code. 86*2d57dc69SVladimir Kotal """ 87*2d57dc69SVladimir Kotal 88*2d57dc69SVladimir Kotal class TimeoutThread(threading.Thread): 89*2d57dc69SVladimir Kotal """ 90*2d57dc69SVladimir Kotal Wait until the timeout specified in seconds expires and kill 91*2d57dc69SVladimir Kotal the process specified by the Popen object after that. 92*2d57dc69SVladimir Kotal If timeout expires, TimeoutException is stored in the object 93*2d57dc69SVladimir Kotal and can be retrieved by the caller. 94*2d57dc69SVladimir Kotal """ 95*2d57dc69SVladimir Kotal 96*2d57dc69SVladimir Kotal def __init__(self, logger, timeout, condition, p): 97*2d57dc69SVladimir Kotal super(TimeoutThread, self).__init__() 98*2d57dc69SVladimir Kotal self.timeout = timeout 99*2d57dc69SVladimir Kotal self.popen = p 100*2d57dc69SVladimir Kotal self.condition = condition 101*2d57dc69SVladimir Kotal self.logger = logger 102*2d57dc69SVladimir Kotal self.start() 103*2d57dc69SVladimir Kotal self.exception = None 104*2d57dc69SVladimir Kotal 105*2d57dc69SVladimir Kotal def terminate(self, p): 106*2d57dc69SVladimir Kotal """ 107*2d57dc69SVladimir Kotal Make sure the process goes away. 108*2d57dc69SVladimir Kotal """ 109*2d57dc69SVladimir Kotal self.logger.info("Terminating PID {}".format(p.pid)) 110*2d57dc69SVladimir Kotal p.terminate() 111*2d57dc69SVladimir Kotal 112*2d57dc69SVladimir Kotal # The following code tries more methods to terminate 113*2d57dc69SVladimir Kotal # the process and is specific to Unix. 114*2d57dc69SVladimir Kotal if os.name == 'posix': 115*2d57dc69SVladimir Kotal timeout = self.timeout 116*2d57dc69SVladimir Kotal # disable E1101 - non existent attribute SIGKILL on windows 117*2d57dc69SVladimir Kotal # pylint: disable=E1101 118*2d57dc69SVladimir Kotal term_signals = [signal.SIGINT, signal.SIGKILL] 119*2d57dc69SVladimir Kotal # pylint: enable=E1101 120*2d57dc69SVladimir Kotal for sig in term_signals: 121*2d57dc69SVladimir Kotal timeout = timeout / 2 # exponential back-off 122*2d57dc69SVladimir Kotal self.logger.info("Sleeping for {} seconds". 123*2d57dc69SVladimir Kotal format(timeout)) 124*2d57dc69SVladimir Kotal time.sleep(timeout) 125*2d57dc69SVladimir Kotal 126*2d57dc69SVladimir Kotal if p.poll() is None: 127*2d57dc69SVladimir Kotal self.logger.info("Command with PID {} still alive," 128*2d57dc69SVladimir Kotal " killing with signal {}". 129*2d57dc69SVladimir Kotal format(p.pid, sig)) 130*2d57dc69SVladimir Kotal p.send_signal(sig) 131*2d57dc69SVladimir Kotal else: 132*2d57dc69SVladimir Kotal self.logger.info("Command with PID {} is gone". 133*2d57dc69SVladimir Kotal format(p.pid)) 134*2d57dc69SVladimir Kotal break 135*2d57dc69SVladimir Kotal 136*2d57dc69SVladimir Kotal def run(self): 137*2d57dc69SVladimir Kotal with self.condition: 138*2d57dc69SVladimir Kotal if not self.condition.wait(self.timeout): 139*2d57dc69SVladimir Kotal p = self.popen 140*2d57dc69SVladimir Kotal self.logger.info("Terminating command {} with PID {} " 141*2d57dc69SVladimir Kotal "after timeout of {} seconds". 142*2d57dc69SVladimir Kotal format(p.args, p.pid, self.timeout)) 143*2d57dc69SVladimir Kotal self.exception = TimeoutException("Command {} with pid" 144*2d57dc69SVladimir Kotal " {} timed out". 145*2d57dc69SVladimir Kotal format(p.args, 146*2d57dc69SVladimir Kotal p.pid)) 147*2d57dc69SVladimir Kotal self.terminate(p) 148*2d57dc69SVladimir Kotal else: 149*2d57dc69SVladimir Kotal return None 150*2d57dc69SVladimir Kotal 151*2d57dc69SVladimir Kotal def get_exception(self): 152*2d57dc69SVladimir Kotal return self.exception 153*2d57dc69SVladimir Kotal 154*2d57dc69SVladimir Kotal class OutputThread(threading.Thread): 155*2d57dc69SVladimir Kotal """ 156*2d57dc69SVladimir Kotal Capture data from subprocess.Popen(). This avoids hangs when 157*2d57dc69SVladimir Kotal stdout/stderr buffers fill up. 158*2d57dc69SVladimir Kotal """ 159*2d57dc69SVladimir Kotal 160*2d57dc69SVladimir Kotal def __init__(self, event, logger, doprint=False): 161*2d57dc69SVladimir Kotal super(OutputThread, self).__init__() 162*2d57dc69SVladimir Kotal self.read_fd, self.write_fd = os.pipe() 163*2d57dc69SVladimir Kotal self.pipe_fobj = os.fdopen(self.read_fd, encoding='utf8') 164*2d57dc69SVladimir Kotal self.out = [] 165*2d57dc69SVladimir Kotal self.event = event 166*2d57dc69SVladimir Kotal self.logger = logger 167*2d57dc69SVladimir Kotal self.doprint = doprint 168*2d57dc69SVladimir Kotal 169*2d57dc69SVladimir Kotal # Start the thread now. 170*2d57dc69SVladimir Kotal self.start() 171*2d57dc69SVladimir Kotal 172*2d57dc69SVladimir Kotal def run(self): 173*2d57dc69SVladimir Kotal """ 174*2d57dc69SVladimir Kotal It might happen that after the process is gone, the thread 175*2d57dc69SVladimir Kotal still has data to read from the pipe. Hence, event is used 176*2d57dc69SVladimir Kotal to synchronize with the caller. 177*2d57dc69SVladimir Kotal """ 178*2d57dc69SVladimir Kotal while True: 179*2d57dc69SVladimir Kotal line = self.pipe_fobj.readline() 180*2d57dc69SVladimir Kotal if not line: 181*2d57dc69SVladimir Kotal self.logger.debug("end of output") 182*2d57dc69SVladimir Kotal self.pipe_fobj.close() 183*2d57dc69SVladimir Kotal self.event.set() 184*2d57dc69SVladimir Kotal return 185*2d57dc69SVladimir Kotal 186*2d57dc69SVladimir Kotal self.out.append(line) 187*2d57dc69SVladimir Kotal 188*2d57dc69SVladimir Kotal if self.doprint: 189*2d57dc69SVladimir Kotal # Even if logging below fails, the thread has to keep 190*2d57dc69SVladimir Kotal # running to avoid hangups of the executed command. 191*2d57dc69SVladimir Kotal try: 192*2d57dc69SVladimir Kotal self.logger.info(line.rstrip()) 193*2d57dc69SVladimir Kotal except Exception as print_exc: 194*2d57dc69SVladimir Kotal self.logger.error(print_exc) 195*2d57dc69SVladimir Kotal 196*2d57dc69SVladimir Kotal def getoutput(self): 197*2d57dc69SVladimir Kotal return self.out 198*2d57dc69SVladimir Kotal 199*2d57dc69SVladimir Kotal def fileno(self): 200*2d57dc69SVladimir Kotal return self.write_fd 201*2d57dc69SVladimir Kotal 202*2d57dc69SVladimir Kotal def close(self): 203*2d57dc69SVladimir Kotal self.logger.debug("closed") 204*2d57dc69SVladimir Kotal os.close(self.write_fd) 205*2d57dc69SVladimir Kotal 206*2d57dc69SVladimir Kotal orig_work_dir = None 207*2d57dc69SVladimir Kotal if self.work_dir: 208*2d57dc69SVladimir Kotal try: 209*2d57dc69SVladimir Kotal orig_work_dir = os.getcwd() 210*2d57dc69SVladimir Kotal except OSError: 211*2d57dc69SVladimir Kotal self.state = Command.ERRORED 212*2d57dc69SVladimir Kotal self.logger.error("Cannot get working directory", 213*2d57dc69SVladimir Kotal exc_info=True) 214*2d57dc69SVladimir Kotal return 215*2d57dc69SVladimir Kotal 216*2d57dc69SVladimir Kotal try: 217*2d57dc69SVladimir Kotal os.chdir(self.work_dir) 218*2d57dc69SVladimir Kotal except OSError: 219*2d57dc69SVladimir Kotal self.state = Command.ERRORED 220*2d57dc69SVladimir Kotal self.logger.error("Cannot change working directory to {}". 221*2d57dc69SVladimir Kotal format(self.work_dir), exc_info=True) 222*2d57dc69SVladimir Kotal return 223*2d57dc69SVladimir Kotal 224*2d57dc69SVladimir Kotal timeout_thread = None 225*2d57dc69SVladimir Kotal output_event = threading.Event() 226*2d57dc69SVladimir Kotal output_thread = OutputThread(output_event, self.logger, 227*2d57dc69SVladimir Kotal doprint=self.doprint) 228*2d57dc69SVladimir Kotal 229*2d57dc69SVladimir Kotal # If stderr redirection is off, setup a thread that will capture 230*2d57dc69SVladimir Kotal # stderr data. 231*2d57dc69SVladimir Kotal if self.redirect_stderr: 232*2d57dc69SVladimir Kotal stderr_dest = subprocess.STDOUT 233*2d57dc69SVladimir Kotal else: 234*2d57dc69SVladimir Kotal stderr_event = threading.Event() 235*2d57dc69SVladimir Kotal stderr_thread = OutputThread(stderr_event, self.logger, 236*2d57dc69SVladimir Kotal doprint=self.doprint) 237*2d57dc69SVladimir Kotal stderr_dest = stderr_thread 238*2d57dc69SVladimir Kotal 239*2d57dc69SVladimir Kotal try: 240*2d57dc69SVladimir Kotal start_time = time.time() 241*2d57dc69SVladimir Kotal try: 242*2d57dc69SVladimir Kotal self.logger.debug("working directory = {}".format(os.getcwd())) 243*2d57dc69SVladimir Kotal except PermissionError: 244*2d57dc69SVladimir Kotal pass 245*2d57dc69SVladimir Kotal self.logger.debug("command = '{}'".format(self)) 246*2d57dc69SVladimir Kotal my_args = {'stderr': stderr_dest, 247*2d57dc69SVladimir Kotal 'stdout': output_thread} 248*2d57dc69SVladimir Kotal if self.env_vars: 249*2d57dc69SVladimir Kotal my_env = os.environ.copy() 250*2d57dc69SVladimir Kotal my_env.update(self.env_vars) 251*2d57dc69SVladimir Kotal self.logger.debug("environment variables: {}".format(my_env)) 252*2d57dc69SVladimir Kotal my_args['env'] = my_env 253*2d57dc69SVladimir Kotal if self.limits: 254*2d57dc69SVladimir Kotal my_args['preexec_fn'] = \ 255*2d57dc69SVladimir Kotal lambda: self.set_resource_limits(self.limits) 256*2d57dc69SVladimir Kotal 257*2d57dc69SVladimir Kotal # Actually run the command. 258*2d57dc69SVladimir Kotal p = subprocess.Popen(self.cmd, **my_args) 259*2d57dc69SVladimir Kotal 260*2d57dc69SVladimir Kotal self.pid = p.pid 261*2d57dc69SVladimir Kotal 262*2d57dc69SVladimir Kotal if self.timeout: 263*2d57dc69SVladimir Kotal time_condition = threading.Condition() 264*2d57dc69SVladimir Kotal self.logger.debug("Setting timeout to {} seconds". 265*2d57dc69SVladimir Kotal format(self.timeout)) 266*2d57dc69SVladimir Kotal timeout_thread = TimeoutThread(self.logger, self.timeout, 267*2d57dc69SVladimir Kotal time_condition, p) 268*2d57dc69SVladimir Kotal 269*2d57dc69SVladimir Kotal self.logger.debug("Waiting for process with PID {}".format(p.pid)) 270*2d57dc69SVladimir Kotal p.wait() 271*2d57dc69SVladimir Kotal self.logger.debug("done waiting") 272*2d57dc69SVladimir Kotal 273*2d57dc69SVladimir Kotal if self.timeout: 274*2d57dc69SVladimir Kotal e = timeout_thread.get_exception() 275*2d57dc69SVladimir Kotal if e: 276*2d57dc69SVladimir Kotal raise e # pylint: disable=E0702 277*2d57dc69SVladimir Kotal 278*2d57dc69SVladimir Kotal except KeyboardInterrupt: 279*2d57dc69SVladimir Kotal self.logger.info("Got KeyboardException while processing ", 280*2d57dc69SVladimir Kotal exc_info=True) 281*2d57dc69SVladimir Kotal self.state = Command.INTERRUPTED 282*2d57dc69SVladimir Kotal except OSError: 283*2d57dc69SVladimir Kotal self.logger.error("Got OS error", exc_info=True) 284*2d57dc69SVladimir Kotal self.state = Command.ERRORED 285*2d57dc69SVladimir Kotal except TimeoutException: 286*2d57dc69SVladimir Kotal self.logger.error("Timed out") 287*2d57dc69SVladimir Kotal self.state = Command.TIMEDOUT 288*2d57dc69SVladimir Kotal else: 289*2d57dc69SVladimir Kotal self.state = Command.FINISHED 290*2d57dc69SVladimir Kotal self.returncode = int(p.returncode) 291*2d57dc69SVladimir Kotal self.logger.debug("'{}' -> {}".format(self, self.getretcode())) 292*2d57dc69SVladimir Kotal finally: 293*2d57dc69SVladimir Kotal if self.timeout != 0 and timeout_thread: 294*2d57dc69SVladimir Kotal with time_condition: 295*2d57dc69SVladimir Kotal time_condition.notifyAll() 296*2d57dc69SVladimir Kotal 297*2d57dc69SVladimir Kotal # The subprocess module does not close the write pipe descriptor 298*2d57dc69SVladimir Kotal # it fetched via OutputThread's fileno() so in order to gracefully 299*2d57dc69SVladimir Kotal # exit the read loop we have to close it here ourselves. 300*2d57dc69SVladimir Kotal output_thread.close() 301*2d57dc69SVladimir Kotal self.logger.debug("Waiting on output thread to finish reading") 302*2d57dc69SVladimir Kotal output_event.wait() 303*2d57dc69SVladimir Kotal self.out = output_thread.getoutput() 304*2d57dc69SVladimir Kotal 305*2d57dc69SVladimir Kotal if not self.redirect_stderr: 306*2d57dc69SVladimir Kotal stderr_thread.close() 307*2d57dc69SVladimir Kotal self.logger.debug("Waiting on stderr thread to finish reading") 308*2d57dc69SVladimir Kotal stderr_event.wait() 309*2d57dc69SVladimir Kotal self.err = stderr_thread.getoutput() 310*2d57dc69SVladimir Kotal 311*2d57dc69SVladimir Kotal elapsed_time = time.time() - start_time 312*2d57dc69SVladimir Kotal self.logger.debug("Command '{}' took {} seconds". 313*2d57dc69SVladimir Kotal format(self, int(elapsed_time))) 314*2d57dc69SVladimir Kotal 315*2d57dc69SVladimir Kotal if orig_work_dir: 316*2d57dc69SVladimir Kotal try: 317*2d57dc69SVladimir Kotal os.chdir(orig_work_dir) 318*2d57dc69SVladimir Kotal except OSError: 319*2d57dc69SVladimir Kotal self.state = Command.ERRORED 320*2d57dc69SVladimir Kotal self.logger.error("Cannot change working directory back to {}". 321*2d57dc69SVladimir Kotal format(orig_work_dir), exc_info=True) 322*2d57dc69SVladimir Kotal return 323*2d57dc69SVladimir Kotal 324*2d57dc69SVladimir Kotal def fill_arg(self, args_append=None, args_subst=None): 325*2d57dc69SVladimir Kotal """ 326*2d57dc69SVladimir Kotal Replace argument names with actual values or append arguments 327*2d57dc69SVladimir Kotal to the command vector. 328*2d57dc69SVladimir Kotal 329*2d57dc69SVladimir Kotal The action depends whether exclusive substitution is on. 330*2d57dc69SVladimir Kotal If yes, arguments will be appended only if no substitution was 331*2d57dc69SVladimir Kotal performed. 332*2d57dc69SVladimir Kotal """ 333*2d57dc69SVladimir Kotal 334*2d57dc69SVladimir Kotal newcmd = [] 335*2d57dc69SVladimir Kotal subst_done = -1 336*2d57dc69SVladimir Kotal for i, cmdarg in enumerate(self.cmd): 337*2d57dc69SVladimir Kotal if args_subst: 338*2d57dc69SVladimir Kotal for pattern in args_subst.keys(): 339*2d57dc69SVladimir Kotal if pattern in cmdarg: 340*2d57dc69SVladimir Kotal newarg = cmdarg.replace(pattern, args_subst[pattern]) 341*2d57dc69SVladimir Kotal self.logger.debug("replacing cmdarg with {}". 342*2d57dc69SVladimir Kotal format(newarg)) 343*2d57dc69SVladimir Kotal newcmd.append(newarg) 344*2d57dc69SVladimir Kotal subst_done = i 345*2d57dc69SVladimir Kotal 346*2d57dc69SVladimir Kotal if subst_done != i: 347*2d57dc69SVladimir Kotal newcmd.append(self.cmd[i]) 348*2d57dc69SVladimir Kotal else: 349*2d57dc69SVladimir Kotal newcmd.append(self.cmd[i]) 350*2d57dc69SVladimir Kotal 351*2d57dc69SVladimir Kotal if args_append and (not self.excl_subst or subst_done == -1): 352*2d57dc69SVladimir Kotal self.logger.debug("appending {}".format(args_append)) 353*2d57dc69SVladimir Kotal newcmd.extend(args_append) 354*2d57dc69SVladimir Kotal 355*2d57dc69SVladimir Kotal self.cmd = newcmd 356*2d57dc69SVladimir Kotal 357*2d57dc69SVladimir Kotal def get_resource(self, name): 358*2d57dc69SVladimir Kotal try: 359*2d57dc69SVladimir Kotal import resource 360*2d57dc69SVladimir Kotal if name == "RLIMIT_NOFILE": 361*2d57dc69SVladimir Kotal return resource.RLIMIT_NOFILE 362*2d57dc69SVladimir Kotal except ImportError: 363*2d57dc69SVladimir Kotal raise NotImplementedError("manipulating resources is not " 364*2d57dc69SVladimir Kotal "available on your platform") 365*2d57dc69SVladimir Kotal 366*2d57dc69SVladimir Kotal raise NotImplementedError("unknown resource") 367*2d57dc69SVladimir Kotal 368*2d57dc69SVladimir Kotal def set_resource_limit(self, name, value): 369*2d57dc69SVladimir Kotal try: 370*2d57dc69SVladimir Kotal import resource 371*2d57dc69SVladimir Kotal self.logger.debug("Setting resource {} to {}" 372*2d57dc69SVladimir Kotal .format(name, value)) 373*2d57dc69SVladimir Kotal resource.setrlimit(self.get_resource(name), (value, value)) 374*2d57dc69SVladimir Kotal except ImportError: 375*2d57dc69SVladimir Kotal raise NotImplementedError("manipulating resources is not " 376*2d57dc69SVladimir Kotal "available on your platform") 377*2d57dc69SVladimir Kotal 378*2d57dc69SVladimir Kotal def set_resource_limits(self, limits): 379*2d57dc69SVladimir Kotal self.logger.debug("Setting resource limits") 380*2d57dc69SVladimir Kotal for name, value in limits.items(): 381*2d57dc69SVladimir Kotal self.set_resource_limit(name, value) 382*2d57dc69SVladimir Kotal 383*2d57dc69SVladimir Kotal def getretcode(self): 384*2d57dc69SVladimir Kotal if self.state != Command.FINISHED: 385*2d57dc69SVladimir Kotal return None 386*2d57dc69SVladimir Kotal else: 387*2d57dc69SVladimir Kotal return self.returncode 388*2d57dc69SVladimir Kotal 389*2d57dc69SVladimir Kotal def getoutputstr(self): 390*2d57dc69SVladimir Kotal if self.state == Command.FINISHED: 391*2d57dc69SVladimir Kotal return "".join(self.out).strip() 392*2d57dc69SVladimir Kotal else: 393*2d57dc69SVladimir Kotal return None 394*2d57dc69SVladimir Kotal 395*2d57dc69SVladimir Kotal def getoutput(self): 396*2d57dc69SVladimir Kotal if self.state == Command.FINISHED: 397*2d57dc69SVladimir Kotal return self.out 398*2d57dc69SVladimir Kotal else: 399*2d57dc69SVladimir Kotal return None 400*2d57dc69SVladimir Kotal 401*2d57dc69SVladimir Kotal def geterroutput(self): 402*2d57dc69SVladimir Kotal return self.err 403*2d57dc69SVladimir Kotal 404*2d57dc69SVladimir Kotal def geterroutputstr(self): 405*2d57dc69SVladimir Kotal if self.err: 406*2d57dc69SVladimir Kotal return "".join(self.err).strip() 407*2d57dc69SVladimir Kotal else: 408*2d57dc69SVladimir Kotal return "" 409*2d57dc69SVladimir Kotal 410*2d57dc69SVladimir Kotal def getstate(self): 411*2d57dc69SVladimir Kotal return self.state 412*2d57dc69SVladimir Kotal 413*2d57dc69SVladimir Kotal def getpid(self): 414*2d57dc69SVladimir Kotal return self.pid 415*2d57dc69SVladimir Kotal 416*2d57dc69SVladimir Kotal def log_error(self, msg): 417*2d57dc69SVladimir Kotal if self.state is Command.FINISHED: 418*2d57dc69SVladimir Kotal self.logger.error("{}: command {} in directory {} exited with {}". 419*2d57dc69SVladimir Kotal format(msg, self.cmd, self.work_dir, 420*2d57dc69SVladimir Kotal self.getretcode())) 421*2d57dc69SVladimir Kotal else: 422*2d57dc69SVladimir Kotal self.logger.error("{}: command {} in directory {} ended with " 423*2d57dc69SVladimir Kotal "invalid state {}". 424*2d57dc69SVladimir Kotal format(msg, self.cmd, self.work_dir, self.state)) 425