xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/command.py (revision ffda442a9997660b969cc7b39d82f07b4236d98f)
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