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