blob: 2606e82d4a65e907d6c129682a87def064992c73 [file] [log] [blame]
David Rochberg410ea822011-11-18 11:39:59 -05001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Christopher Wileyffffab72013-03-06 16:14:11 -08005import logging
6import os
7import pipes
8import threading
9
David Rochberg410ea822011-11-18 11:39:59 -050010
11class _HelperThread(threading.Thread):
12 """Make a thread to run the command in."""
Christopher Wileyffffab72013-03-06 16:14:11 -080013 def __init__(self, host, cmd):
14 super(_HelperThread, self).__init__()
15 self._host = host
16 self._cmd = cmd
17 self._result = None
18 self.daemon = True
19
David Rochberg410ea822011-11-18 11:39:59 -050020
21 def run(self):
Christopher Wileyffffab72013-03-06 16:14:11 -080022 logging.info('Helper thread running: %s', self._cmd)
David Rochberg410ea822011-11-18 11:39:59 -050023 # NB: set ignore_status as we're always terminated w/ pkill
Christopher Wileyffffab72013-03-06 16:14:11 -080024 self._result = self._host.run(self._cmd, ignore_status=True)
25
26
27 @property
28 def result(self):
29 """
30 @returns string result of running our command if the command has
31 finished, and None otherwise.
32
33 """
34 return self._result
David Rochberg410ea822011-11-18 11:39:59 -050035
36
37class Command(object):
Christopher Wileyffffab72013-03-06 16:14:11 -080038 """
39 Encapsulates a command run on a remote machine.
David Rochberg410ea822011-11-18 11:39:59 -050040
41 Future work is to have this get the PID (by prepending 'echo $$;
42 exec' to the command and parsing the output).
43
David Rochberg410ea822011-11-18 11:39:59 -050044 """
Christopher Wileyffffab72013-03-06 16:14:11 -080045 def __init__(self, host, cmd, pkill_argument=None):
46 """
47 Run a command on a remote host in the background.
David Rochberg410ea822011-11-18 11:39:59 -050048
Christopher Wileyffffab72013-03-06 16:14:11 -080049 @param host Host object representing the remote machine.
50 @param cmd String command to run on the remote machine.
51 @param pkill_argument String argument to pkill to kill the remote
52 process.
53
54 """
55 if pkill_argument is None:
56 pkill_argument = os.path.basename(cmd)
57 self._command_name = pipes.quote(pkill_argument)
58 self._host = host
59 self._thread = _HelperThread(self._host, cmd)
60 self._thread.start()
61
62
63 def join(self, signal=None):
64 """
65 Kills the remote command and waits until it dies. Takes an optional
66 signal argument to control which signal to send the process to be
67 killed.
68
69 @param signal Signal string to give to pkill (e.g. SIGNAL_INT).
70 """
71 if signal is None:
72 signal_arg = ''
73 else:
74 # In theory, it should be hard to pass something evil for signal if
75 # we make sure it's an integer before passing it to pkill.
76 signal_arg = '-' + str(int(signal))
77
David Rochberg410ea822011-11-18 11:39:59 -050078 # Ignore status because the command may have exited already
Christopher Wileyffffab72013-03-06 16:14:11 -080079 self._host.run("pkill %s %s" % (signal_arg, self._command_name),
80 ignore_status=True)
81 self._thread.join()
82
David Rochberg410ea822011-11-18 11:39:59 -050083
84 def __enter__(self):
85 return self
86
Christopher Wileyffffab72013-03-06 16:14:11 -080087
David Rochberg410ea822011-11-18 11:39:59 -050088 def __exit__(self, exception, value, traceback):
Christopher Wileyffffab72013-03-06 16:14:11 -080089 self.join()
David Rochberg410ea822011-11-18 11:39:59 -050090 return False
Christopher Wileyffffab72013-03-06 16:14:11 -080091
92
93 @property
94 def result(self):
95 """
96 @returns string result of running our command if the command has
97 finished, and None otherwise.
98
99 """
100 return self._thread.result