xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/command.py (revision b2d29dae9a94943b44f7145a695b3511eb3ba2b1)
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*b2d29daeSVladimir Kotal# Copyright (c) 2017, 2021, 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
802d57dc69SVladimir Kotal    def __str__(self):
812d57dc69SVladimir Kotal        return " ".join(self.cmd)
822d57dc69SVladimir Kotal
832d57dc69SVladimir Kotal    def execute(self):
842d57dc69SVladimir Kotal        """
852d57dc69SVladimir Kotal        Execute the command and capture its output and return code.
862d57dc69SVladimir Kotal        """
872d57dc69SVladimir Kotal
882d57dc69SVladimir Kotal        class TimeoutThread(threading.Thread):
892d57dc69SVladimir Kotal            """
902d57dc69SVladimir Kotal            Wait until the timeout specified in seconds expires and kill
912d57dc69SVladimir Kotal            the process specified by the Popen object after that.
922d57dc69SVladimir Kotal            If timeout expires, TimeoutException is stored in the object
932d57dc69SVladimir Kotal            and can be retrieved by the caller.
942d57dc69SVladimir Kotal            """
952d57dc69SVladimir Kotal
962d57dc69SVladimir Kotal            def __init__(self, logger, timeout, condition, p):
972d57dc69SVladimir Kotal                super(TimeoutThread, self).__init__()
982d57dc69SVladimir Kotal                self.timeout = timeout
992d57dc69SVladimir Kotal                self.popen = p
1002d57dc69SVladimir Kotal                self.condition = condition
1012d57dc69SVladimir Kotal                self.logger = logger
1022d57dc69SVladimir Kotal                self.start()
1032d57dc69SVladimir Kotal                self.exception = None
1042d57dc69SVladimir Kotal
1052d57dc69SVladimir Kotal            def terminate(self, p):
1062d57dc69SVladimir Kotal                """
1072d57dc69SVladimir Kotal                Make sure the process goes away.
1082d57dc69SVladimir Kotal                """
1092d57dc69SVladimir Kotal                self.logger.info("Terminating PID {}".format(p.pid))
1102d57dc69SVladimir Kotal                p.terminate()
1112d57dc69SVladimir Kotal
1122d57dc69SVladimir Kotal                # The following code tries more methods to terminate
1132d57dc69SVladimir Kotal                # the process and is specific to Unix.
1142d57dc69SVladimir Kotal                if os.name == 'posix':
1152d57dc69SVladimir Kotal                    timeout = self.timeout
1162d57dc69SVladimir Kotal                    # disable E1101 - non existent attribute SIGKILL on windows
1172d57dc69SVladimir Kotal                    # pylint: disable=E1101
1182d57dc69SVladimir Kotal                    term_signals = [signal.SIGINT, signal.SIGKILL]
1192d57dc69SVladimir Kotal                    # pylint: enable=E1101
1202d57dc69SVladimir Kotal                    for sig in term_signals:
1212d57dc69SVladimir Kotal                        timeout = timeout / 2  # exponential back-off
1222d57dc69SVladimir Kotal                        self.logger.info("Sleeping for {} seconds".
1232d57dc69SVladimir Kotal                                         format(timeout))
1242d57dc69SVladimir Kotal                        time.sleep(timeout)
1252d57dc69SVladimir Kotal
1262d57dc69SVladimir Kotal                        if p.poll() is None:
1272d57dc69SVladimir Kotal                            self.logger.info("Command with PID {} still alive,"
1282d57dc69SVladimir Kotal                                             " killing with signal {}".
1292d57dc69SVladimir Kotal                                             format(p.pid, sig))
1302d57dc69SVladimir Kotal                            p.send_signal(sig)
1312d57dc69SVladimir Kotal                        else:
1322d57dc69SVladimir Kotal                            self.logger.info("Command with PID {} is gone".
1332d57dc69SVladimir Kotal                                             format(p.pid))
1342d57dc69SVladimir Kotal                            break
1352d57dc69SVladimir Kotal
1362d57dc69SVladimir Kotal            def run(self):
1372d57dc69SVladimir Kotal                with self.condition:
1382d57dc69SVladimir Kotal                    if not self.condition.wait(self.timeout):
1392d57dc69SVladimir Kotal                        p = self.popen
1402d57dc69SVladimir Kotal                        self.logger.info("Terminating command {} with PID {} "
1412d57dc69SVladimir Kotal                                         "after timeout of {} seconds".
1422d57dc69SVladimir Kotal                                         format(p.args, p.pid, self.timeout))
1432d57dc69SVladimir Kotal                        self.exception = TimeoutException("Command {} with pid"
1442d57dc69SVladimir Kotal                                                          " {} timed out".
1452d57dc69SVladimir Kotal                                                          format(p.args,
1462d57dc69SVladimir Kotal                                                                 p.pid))
1472d57dc69SVladimir Kotal                        self.terminate(p)
1482d57dc69SVladimir Kotal                    else:
1492d57dc69SVladimir Kotal                        return None
1502d57dc69SVladimir Kotal
1512d57dc69SVladimir Kotal            def get_exception(self):
1522d57dc69SVladimir Kotal                return self.exception
1532d57dc69SVladimir Kotal
1542d57dc69SVladimir Kotal        class OutputThread(threading.Thread):
1552d57dc69SVladimir Kotal            """
1562d57dc69SVladimir Kotal            Capture data from subprocess.Popen(). This avoids hangs when
1572d57dc69SVladimir Kotal            stdout/stderr buffers fill up.
1582d57dc69SVladimir Kotal            """
1592d57dc69SVladimir Kotal
1602d57dc69SVladimir Kotal            def __init__(self, event, logger, doprint=False):
1612d57dc69SVladimir Kotal                super(OutputThread, self).__init__()
1622d57dc69SVladimir Kotal                self.read_fd, self.write_fd = os.pipe()
1632d57dc69SVladimir Kotal                self.pipe_fobj = os.fdopen(self.read_fd, encoding='utf8')
1642d57dc69SVladimir Kotal                self.out = []
1652d57dc69SVladimir Kotal                self.event = event
1662d57dc69SVladimir Kotal                self.logger = logger
1672d57dc69SVladimir Kotal                self.doprint = doprint
1682d57dc69SVladimir Kotal
1692d57dc69SVladimir Kotal                # Start the thread now.
1702d57dc69SVladimir Kotal                self.start()
1712d57dc69SVladimir Kotal
1722d57dc69SVladimir Kotal            def run(self):
1732d57dc69SVladimir Kotal                """
1742d57dc69SVladimir Kotal                It might happen that after the process is gone, the thread
1752d57dc69SVladimir Kotal                still has data to read from the pipe. Hence, event is used
1762d57dc69SVladimir Kotal                to synchronize with the caller.
1772d57dc69SVladimir Kotal                """
1782d57dc69SVladimir Kotal                while True:
1792d57dc69SVladimir Kotal                    line = self.pipe_fobj.readline()
1802d57dc69SVladimir Kotal                    if not line:
1812d57dc69SVladimir Kotal                        self.logger.debug("end of output")
1822d57dc69SVladimir Kotal                        self.pipe_fobj.close()
1832d57dc69SVladimir Kotal                        self.event.set()
1842d57dc69SVladimir Kotal                        return
1852d57dc69SVladimir Kotal
1862d57dc69SVladimir Kotal                    self.out.append(line)
1872d57dc69SVladimir Kotal
1882d57dc69SVladimir Kotal                    if self.doprint:
1892d57dc69SVladimir Kotal                        # Even if logging below fails, the thread has to keep
1902d57dc69SVladimir Kotal                        # running to avoid hangups of the executed command.
1912d57dc69SVladimir Kotal                        try:
1922d57dc69SVladimir Kotal                            self.logger.info(line.rstrip())
1932d57dc69SVladimir Kotal                        except Exception as print_exc:
1942d57dc69SVladimir Kotal                            self.logger.error(print_exc)
1952d57dc69SVladimir Kotal
1962d57dc69SVladimir Kotal            def getoutput(self):
1972d57dc69SVladimir Kotal                return self.out
1982d57dc69SVladimir Kotal
1992d57dc69SVladimir Kotal            def fileno(self):
2002d57dc69SVladimir Kotal                return self.write_fd
2012d57dc69SVladimir Kotal
2022d57dc69SVladimir Kotal            def close(self):
2032d57dc69SVladimir Kotal                self.logger.debug("closed")
2042d57dc69SVladimir Kotal                os.close(self.write_fd)
2052d57dc69SVladimir Kotal
2062d57dc69SVladimir Kotal        orig_work_dir = None
2072d57dc69SVladimir Kotal        if self.work_dir:
2082d57dc69SVladimir Kotal            try:
2092d57dc69SVladimir Kotal                orig_work_dir = os.getcwd()
2102d57dc69SVladimir Kotal            except OSError:
2112d57dc69SVladimir Kotal                self.state = Command.ERRORED
2122d57dc69SVladimir Kotal                self.logger.error("Cannot get working directory",
2132d57dc69SVladimir Kotal                                  exc_info=True)
2142d57dc69SVladimir Kotal                return
2152d57dc69SVladimir Kotal
2162d57dc69SVladimir Kotal            try:
2172d57dc69SVladimir Kotal                os.chdir(self.work_dir)
2182d57dc69SVladimir Kotal            except OSError:
2192d57dc69SVladimir Kotal                self.state = Command.ERRORED
2202d57dc69SVladimir Kotal                self.logger.error("Cannot change working directory to {}".
2212d57dc69SVladimir Kotal                                  format(self.work_dir), exc_info=True)
2222d57dc69SVladimir Kotal                return
2232d57dc69SVladimir Kotal
2242d57dc69SVladimir Kotal        timeout_thread = None
2252d57dc69SVladimir Kotal        output_event = threading.Event()
2262d57dc69SVladimir Kotal        output_thread = OutputThread(output_event, self.logger,
2272d57dc69SVladimir Kotal                                     doprint=self.doprint)
2282d57dc69SVladimir Kotal
2292d57dc69SVladimir Kotal        # If stderr redirection is off, setup a thread that will capture
2302d57dc69SVladimir Kotal        # stderr data.
2312d57dc69SVladimir Kotal        if self.redirect_stderr:
2322d57dc69SVladimir Kotal            stderr_dest = subprocess.STDOUT
2332d57dc69SVladimir Kotal        else:
2342d57dc69SVladimir Kotal            stderr_event = threading.Event()
2352d57dc69SVladimir Kotal            stderr_thread = OutputThread(stderr_event, self.logger,
2362d57dc69SVladimir Kotal                                         doprint=self.doprint)
2372d57dc69SVladimir Kotal            stderr_dest = stderr_thread
2382d57dc69SVladimir Kotal
2392d57dc69SVladimir Kotal        try:
2402d57dc69SVladimir Kotal            start_time = time.time()
2412d57dc69SVladimir Kotal            try:
2422d57dc69SVladimir Kotal                self.logger.debug("working directory = {}".format(os.getcwd()))
2432d57dc69SVladimir Kotal            except PermissionError:
2442d57dc69SVladimir Kotal                pass
2452d57dc69SVladimir Kotal            self.logger.debug("command = '{}'".format(self))
2462d57dc69SVladimir Kotal            my_args = {'stderr': stderr_dest,
2472d57dc69SVladimir Kotal                       'stdout': output_thread}
2482d57dc69SVladimir Kotal            if self.env_vars:
2492d57dc69SVladimir Kotal                my_env = os.environ.copy()
2502d57dc69SVladimir Kotal                my_env.update(self.env_vars)
2512d57dc69SVladimir Kotal                self.logger.debug("environment variables: {}".format(my_env))
2522d57dc69SVladimir Kotal                my_args['env'] = my_env
2532d57dc69SVladimir Kotal            if self.limits:
2542d57dc69SVladimir Kotal                my_args['preexec_fn'] = \
2552d57dc69SVladimir Kotal                    lambda: self.set_resource_limits(self.limits)
2562d57dc69SVladimir Kotal
2572d57dc69SVladimir Kotal            # Actually run the command.
2582d57dc69SVladimir Kotal            p = subprocess.Popen(self.cmd, **my_args)
2592d57dc69SVladimir Kotal
2602d57dc69SVladimir Kotal            self.pid = p.pid
2612d57dc69SVladimir Kotal
2622d57dc69SVladimir Kotal            if self.timeout:
2632d57dc69SVladimir Kotal                time_condition = threading.Condition()
2642d57dc69SVladimir Kotal                self.logger.debug("Setting timeout to {} seconds".
2652d57dc69SVladimir Kotal                                  format(self.timeout))
2662d57dc69SVladimir Kotal                timeout_thread = TimeoutThread(self.logger, self.timeout,
2672d57dc69SVladimir Kotal                                               time_condition, p)
2682d57dc69SVladimir Kotal
2692d57dc69SVladimir Kotal            self.logger.debug("Waiting for process with PID {}".format(p.pid))
2702d57dc69SVladimir Kotal            p.wait()
2712d57dc69SVladimir Kotal            self.logger.debug("done waiting")
2722d57dc69SVladimir Kotal
2732d57dc69SVladimir Kotal            if self.timeout:
2742d57dc69SVladimir Kotal                e = timeout_thread.get_exception()
2752d57dc69SVladimir Kotal                if e:
2762d57dc69SVladimir Kotal                    raise e  # pylint: disable=E0702
2772d57dc69SVladimir Kotal
2782d57dc69SVladimir Kotal        except KeyboardInterrupt:
2792d57dc69SVladimir Kotal            self.logger.info("Got KeyboardException while processing ",
2802d57dc69SVladimir Kotal                             exc_info=True)
2812d57dc69SVladimir Kotal            self.state = Command.INTERRUPTED
2822d57dc69SVladimir Kotal        except OSError:
2832d57dc69SVladimir Kotal            self.logger.error("Got OS error", exc_info=True)
2842d57dc69SVladimir Kotal            self.state = Command.ERRORED
2852d57dc69SVladimir Kotal        except TimeoutException:
2862d57dc69SVladimir Kotal            self.logger.error("Timed out")
2872d57dc69SVladimir Kotal            self.state = Command.TIMEDOUT
2882d57dc69SVladimir Kotal        else:
2892d57dc69SVladimir Kotal            self.state = Command.FINISHED
2902d57dc69SVladimir Kotal            self.returncode = int(p.returncode)
2912d57dc69SVladimir Kotal            self.logger.debug("'{}' -> {}".format(self, self.getretcode()))
2922d57dc69SVladimir Kotal        finally:
2932d57dc69SVladimir Kotal            if self.timeout != 0 and timeout_thread:
2942d57dc69SVladimir Kotal                with time_condition:
2952d57dc69SVladimir Kotal                    time_condition.notifyAll()
2962d57dc69SVladimir Kotal
2972d57dc69SVladimir Kotal            # The subprocess module does not close the write pipe descriptor
2982d57dc69SVladimir Kotal            # it fetched via OutputThread's fileno() so in order to gracefully
2992d57dc69SVladimir Kotal            # exit the read loop we have to close it here ourselves.
3002d57dc69SVladimir Kotal            output_thread.close()
3012d57dc69SVladimir Kotal            self.logger.debug("Waiting on output thread to finish reading")
3022d57dc69SVladimir Kotal            output_event.wait()
3032d57dc69SVladimir Kotal            self.out = output_thread.getoutput()
3042d57dc69SVladimir Kotal
3052d57dc69SVladimir Kotal            if not self.redirect_stderr:
3062d57dc69SVladimir Kotal                stderr_thread.close()
3072d57dc69SVladimir Kotal                self.logger.debug("Waiting on stderr thread to finish reading")
3082d57dc69SVladimir Kotal                stderr_event.wait()
3092d57dc69SVladimir Kotal                self.err = stderr_thread.getoutput()
3102d57dc69SVladimir Kotal
3112d57dc69SVladimir Kotal            elapsed_time = time.time() - start_time
3122d57dc69SVladimir Kotal            self.logger.debug("Command '{}' took {} seconds".
3132d57dc69SVladimir Kotal                              format(self, int(elapsed_time)))
3142d57dc69SVladimir Kotal
3152d57dc69SVladimir Kotal        if orig_work_dir:
3162d57dc69SVladimir Kotal            try:
3172d57dc69SVladimir Kotal                os.chdir(orig_work_dir)
3182d57dc69SVladimir Kotal            except OSError:
3192d57dc69SVladimir Kotal                self.state = Command.ERRORED
3202d57dc69SVladimir Kotal                self.logger.error("Cannot change working directory back to {}".
3212d57dc69SVladimir Kotal                                  format(orig_work_dir), exc_info=True)
3222d57dc69SVladimir Kotal                return
3232d57dc69SVladimir Kotal
3242d57dc69SVladimir Kotal    def fill_arg(self, args_append=None, args_subst=None):
3252d57dc69SVladimir Kotal        """
3262d57dc69SVladimir Kotal        Replace argument names with actual values or append arguments
3272d57dc69SVladimir Kotal        to the command vector.
3282d57dc69SVladimir Kotal
3292d57dc69SVladimir Kotal        The action depends whether exclusive substitution is on.
3302d57dc69SVladimir Kotal        If yes, arguments will be appended only if no substitution was
3312d57dc69SVladimir Kotal        performed.
3322d57dc69SVladimir Kotal        """
3332d57dc69SVladimir Kotal
3342d57dc69SVladimir Kotal        newcmd = []
3352d57dc69SVladimir Kotal        subst_done = -1
3362d57dc69SVladimir Kotal        for i, cmdarg in enumerate(self.cmd):
3372d57dc69SVladimir Kotal            if args_subst:
3382d97c0a2SVladimir Kotal                newarg = cmdarg
3392d57dc69SVladimir Kotal                for pattern in args_subst.keys():
3402d97c0a2SVladimir Kotal                    if pattern in newarg and args_subst[pattern]:
3412d97c0a2SVladimir Kotal                        self.logger.debug("replacing '{}' in '{}' with '{}'".
3422d97c0a2SVladimir Kotal                                          format(pattern, newarg,
3432d97c0a2SVladimir Kotal                                          args_subst[pattern]))
3442d97c0a2SVladimir Kotal                        newarg = newarg.replace(pattern, args_subst[pattern])
3452d97c0a2SVladimir Kotal                        self.logger.debug("replaced argument with {}".
3462d57dc69SVladimir Kotal                                          format(newarg))
3472d57dc69SVladimir Kotal                        subst_done = i
3482d57dc69SVladimir Kotal
3492d57dc69SVladimir Kotal                if subst_done != i:
3502d57dc69SVladimir Kotal                    newcmd.append(self.cmd[i])
3512d57dc69SVladimir Kotal                else:
3522d97c0a2SVladimir Kotal                    newcmd.append(newarg)
3532d97c0a2SVladimir Kotal            else:
3542d57dc69SVladimir Kotal                newcmd.append(self.cmd[i])
3552d57dc69SVladimir Kotal
3562d57dc69SVladimir Kotal        if args_append and (not self.excl_subst or subst_done == -1):
3572d57dc69SVladimir Kotal            self.logger.debug("appending {}".format(args_append))
3582d57dc69SVladimir Kotal            newcmd.extend(args_append)
3592d57dc69SVladimir Kotal
3602d57dc69SVladimir Kotal        self.cmd = newcmd
3612d57dc69SVladimir Kotal
3622d57dc69SVladimir Kotal    def get_resource(self, name):
3632d57dc69SVladimir Kotal        try:
3642d57dc69SVladimir Kotal            import resource
3652d57dc69SVladimir Kotal            if name == "RLIMIT_NOFILE":
3662d57dc69SVladimir Kotal                return resource.RLIMIT_NOFILE
3672d57dc69SVladimir Kotal        except ImportError:
3682d57dc69SVladimir Kotal            raise NotImplementedError("manipulating resources is not "
3692d57dc69SVladimir Kotal                                      "available on your platform")
3702d57dc69SVladimir Kotal
3712d57dc69SVladimir Kotal        raise NotImplementedError("unknown resource")
3722d57dc69SVladimir Kotal
3732d57dc69SVladimir Kotal    def set_resource_limit(self, name, value):
3742d57dc69SVladimir Kotal        try:
3752d57dc69SVladimir Kotal            import resource
3762d57dc69SVladimir Kotal            self.logger.debug("Setting resource {} to {}"
3772d57dc69SVladimir Kotal                              .format(name, value))
3782d57dc69SVladimir Kotal            resource.setrlimit(self.get_resource(name), (value, value))
3792d57dc69SVladimir Kotal        except ImportError:
3802d57dc69SVladimir Kotal            raise NotImplementedError("manipulating resources is not "
3812d57dc69SVladimir Kotal                                      "available on your platform")
3822d57dc69SVladimir Kotal
3832d57dc69SVladimir Kotal    def set_resource_limits(self, limits):
3842d57dc69SVladimir Kotal        self.logger.debug("Setting resource limits")
3852d57dc69SVladimir Kotal        for name, value in limits.items():
3862d57dc69SVladimir Kotal            self.set_resource_limit(name, value)
3872d57dc69SVladimir Kotal
3882d57dc69SVladimir Kotal    def getretcode(self):
3892d57dc69SVladimir Kotal        if self.state != Command.FINISHED:
3902d57dc69SVladimir Kotal            return None
3912d57dc69SVladimir Kotal        else:
3922d57dc69SVladimir Kotal            return self.returncode
3932d57dc69SVladimir Kotal
3942d57dc69SVladimir Kotal    def getoutputstr(self):
3952d57dc69SVladimir Kotal        if self.state == Command.FINISHED:
3962d57dc69SVladimir Kotal            return "".join(self.out).strip()
3972d57dc69SVladimir Kotal        else:
3982d57dc69SVladimir Kotal            return None
3992d57dc69SVladimir Kotal
4002d57dc69SVladimir Kotal    def getoutput(self):
4012d57dc69SVladimir Kotal        if self.state == Command.FINISHED:
4022d57dc69SVladimir Kotal            return self.out
4032d57dc69SVladimir Kotal        else:
4042d57dc69SVladimir Kotal            return None
4052d57dc69SVladimir Kotal
4062d57dc69SVladimir Kotal    def geterroutput(self):
4072d57dc69SVladimir Kotal        return self.err
4082d57dc69SVladimir Kotal
4092d57dc69SVladimir Kotal    def geterroutputstr(self):
4102d57dc69SVladimir Kotal        if self.err:
4112d57dc69SVladimir Kotal            return "".join(self.err).strip()
4122d57dc69SVladimir Kotal        else:
4132d57dc69SVladimir Kotal            return ""
4142d57dc69SVladimir Kotal
4152d57dc69SVladimir Kotal    def getstate(self):
4162d57dc69SVladimir Kotal        return self.state
4172d57dc69SVladimir Kotal
4182d57dc69SVladimir Kotal    def getpid(self):
4192d57dc69SVladimir Kotal        return self.pid
4202d57dc69SVladimir Kotal
4212d57dc69SVladimir Kotal    def log_error(self, msg):
4222d57dc69SVladimir Kotal        if self.state is Command.FINISHED:
4232d57dc69SVladimir Kotal            self.logger.error("{}: command {} in directory {} exited with {}".
4242d57dc69SVladimir Kotal                              format(msg, self.cmd, self.work_dir,
4252d57dc69SVladimir Kotal                                     self.getretcode()))
4262d57dc69SVladimir Kotal        else:
4272d57dc69SVladimir Kotal            self.logger.error("{}: command {} in directory {} ended with "
4282d57dc69SVladimir Kotal                              "invalid state {}".
4292d57dc69SVladimir Kotal                              format(msg, self.cmd, self.work_dir, self.state))
430