# Addapted from killableprocess.py. #______________________________________________________________________________ # # killableprocess - subprocesses which can be reliably killed # # Parts of this module are copied from the subprocess.py file contained # in the Python distribution. # # Copyright (c) 2003-2004 by Peter Astrand # # Additions and modifications written by Benjamin Smedberg # are Copyright (c) 2006 by the Mozilla Foundation # # # By obtaining, using, and/or copying this software and/or its # associated documentation, you agree that you have read, understood, # and will comply with the following terms and conditions: # # Permission to use, copy, modify, and distribute this software and # its associated documentation for any purpose and without fee is # hereby granted, provided that the above copyright notice appears in # all copies, and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of the # author not be used in advertising or publicity pertaining to # distribution of the software without specific, written prior # permission. # # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. r"""killableprocess - Subprocesses which can be reliably killed This module is a subclass of the builtin "subprocess" module. It allows processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method. It also adds a timeout argument to Wait() for a limited period of time before forcefully killing the process. Note: On Windows, this module requires Windows 2000 or higher (no support for Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with Python 2.5+ or available from http://python.net/crew/theller/ctypes/ """ import subprocess from subprocess import PIPE import sys import os import types try: from subprocess import CalledProcessError except ImportError: # Python 2.4 doesn't implement CalledProcessError class CalledProcessError(Exception): """This exception is raised when a process run by check_call() returns a non-zero exit status. The exit status will be stored in the returncode attribute.""" def __init__(self, returncode, cmd): self.returncode = returncode self.cmd = cmd def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) mswindows = (sys.platform == "win32") skip = False if mswindows: import platform if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000': # Killable process does not work under vista when starting for # something else than cmd. skip = True else: import winprocess else: import signal if not mswindows: def DoNothing(*args): pass if skip: Popen = subprocess.Popen else: class Popen(subprocess.Popen): if not mswindows: # Override __init__ to set a preexec_fn def __init__(self, *args, **kwargs): if len(args) >= 7: raise Exception("Arguments preexec_fn and after must be passed by keyword.") real_preexec_fn = kwargs.pop("preexec_fn", None) def setpgid_preexec_fn(): os.setpgid(0, 0) if real_preexec_fn: apply(real_preexec_fn) kwargs['preexec_fn'] = setpgid_preexec_fn subprocess.Popen.__init__(self, *args, **kwargs) if mswindows: def _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): if not isinstance(args, types.StringTypes): args = subprocess.list2cmdline(args) if startupinfo is None: startupinfo = winprocess.STARTUPINFO() if None not in (p2cread, c2pwrite, errwrite): startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES startupinfo.hStdInput = int(p2cread) startupinfo.hStdOutput = int(c2pwrite) startupinfo.hStdError = int(errwrite) if shell: startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = winprocess.SW_HIDE comspec = os.environ.get("COMSPEC", "cmd.exe") args = comspec + " /c " + args # We create a new job for this process, so that we can kill # the process and any sub-processes self._job = winprocess.CreateJobObject() creationflags |= winprocess.CREATE_SUSPENDED creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT hp, ht, pid, tid = winprocess.CreateProcess( executable, args, None, None, # No special security 1, # Must inherit handles! creationflags, winprocess.EnvironmentBlock(env), cwd, startupinfo) self._child_created = True self._handle = hp self._thread = ht self.pid = pid # XXX: A try/except to fix UAC-related problems under # Windows Vista, when reparenting jobs. try: winprocess.AssignProcessToJobObject(self._job, hp) except WindowsError: pass winprocess.ResumeThread(ht) if p2cread is not None: p2cread.Close() if c2pwrite is not None: c2pwrite.Close() if errwrite is not None: errwrite.Close() def kill(self, group=True): """Kill the process. If group=True, all sub-processes will also be killed.""" if mswindows: if group: winprocess.TerminateJobObject(self._job, 127) else: winprocess.TerminateProcess(self._handle, 127) self.returncode = 127 else: if group: os.killpg(self.pid, signal.SIGKILL) else: os.kill(self.pid, signal.SIGKILL) self.returncode = -9