# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import os import pipes import threading from autotest_lib.client.common_lib import error class _HelperThread(threading.Thread): """Make a thread to run the command in.""" def __init__(self, host, cmd): super(_HelperThread, self).__init__() self._host = host self._cmd = cmd self._result = None self.daemon = True def run(self): logging.info('Helper thread running: %s', self._cmd) # NB: set ignore_status as we're always terminated w/ pkill self._result = self._host.run(self._cmd, ignore_status=True) @property def result(self): """ @returns string result of running our command if the command has finished, and None otherwise. """ return self._result class Command(object): """ Encapsulates a command run on a remote machine. Future work is to have this get the PID (by prepending 'echo $$; exec' to the command and parsing the output). """ def __init__(self, host, cmd, pkill_argument=None): """ Run a command on a remote host in the background. @param host Host object representing the remote machine. @param cmd String command to run on the remote machine. @param pkill_argument String argument to pkill to kill the remote process. """ if pkill_argument is None: # Attempt to guess what a suitable pkill argument would look like. pkill_argument = os.path.basename(cmd.split()[0]) self._command_name = pipes.quote(pkill_argument) self._host = host self._thread = _HelperThread(self._host, cmd) self._thread.start() def join(self, signal=None, timeout=5.0): """ Kills the remote command and waits until it dies. Takes an optional signal argument to control which signal to send the process to be killed. @param signal Signal string to give to pkill (e.g. SIGNAL_INT). @param timeout float number of seconds to wait for join to finish. """ if signal is None: signal_arg = '' else: # In theory, it should be hard to pass something evil for signal if # we make sure it's an integer before passing it to pkill. signal_arg = '-' + str(int(signal)) # Ignore status because the command may have exited already self._host.run("pkill %s %s" % (signal_arg, self._command_name), ignore_status=True) self._thread.join(timeout) if self._thread.isAlive(): raise error.TestFail('Failed to kill remote command: %s' % self._command_name) def __enter__(self): return self def __exit__(self, exception, value, traceback): self.join() return False @property def result(self): """ @returns string result of running our command if the command has finished, and None otherwise. """ return self._thread.result