| Index: pylib/mozrunner/winprocess.py | 
| =================================================================== | 
| --- pylib/mozrunner/winprocess.py	(revision 0) | 
| +++ pylib/mozrunner/winprocess.py	(revision 0) | 
| @@ -0,0 +1,381 @@ | 
| +# A module to expose various thread/process/job related structures and | 
| +# methods from kernel32 | 
| +# | 
| +# The MIT License | 
| +# | 
| +# Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se> | 
| +# | 
| +# Additions and modifications written by Benjamin Smedberg | 
| +# <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation | 
| +# <http://www.mozilla.org/> | 
| +# | 
| +# More Modifications | 
| +# Copyright (c) 2006-2007 by Mike Taylor <bear@code-bear.com> | 
| +# Copyright (c) 2007-2008 by Mikeal Rogers <mikeal@mozilla.com> | 
| +# | 
| +# 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. | 
| + | 
| +from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE | 
| +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD | 
| +from qijo import QueryInformationJobObject | 
| + | 
| +LPVOID = c_void_p | 
| +LPBYTE = POINTER(BYTE) | 
| +LPDWORD = POINTER(DWORD) | 
| +LPBOOL = POINTER(BOOL) | 
| + | 
| +def ErrCheckBool(result, func, args): | 
| +    """errcheck function for Windows functions that return a BOOL True | 
| +    on success""" | 
| +    if not result: | 
| +        raise WinError() | 
| +    return args | 
| + | 
| + | 
| +# AutoHANDLE | 
| + | 
| +class AutoHANDLE(HANDLE): | 
| +    """Subclass of HANDLE which will call CloseHandle() on deletion.""" | 
| + | 
| +    CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE) | 
| +    CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32)) | 
| +    CloseHandle.errcheck = ErrCheckBool | 
| + | 
| +    def Close(self): | 
| +        if self.value and self.value != HANDLE(-1).value: | 
| +            self.CloseHandle(self) | 
| +            self.value = 0 | 
| + | 
| +    def __del__(self): | 
| +        self.Close() | 
| + | 
| +    def __int__(self): | 
| +        return self.value | 
| + | 
| +def ErrCheckHandle(result, func, args): | 
| +    """errcheck function for Windows functions that return a HANDLE.""" | 
| +    if not result: | 
| +        raise WinError() | 
| +    return AutoHANDLE(result) | 
| + | 
| +# PROCESS_INFORMATION structure | 
| + | 
| +class PROCESS_INFORMATION(Structure): | 
| +    _fields_ = [("hProcess", HANDLE), | 
| +                ("hThread", HANDLE), | 
| +                ("dwProcessID", DWORD), | 
| +                ("dwThreadID", DWORD)] | 
| + | 
| +    def __init__(self): | 
| +        Structure.__init__(self) | 
| + | 
| +        self.cb = sizeof(self) | 
| + | 
| +LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) | 
| + | 
| +# STARTUPINFO structure | 
| + | 
| +class STARTUPINFO(Structure): | 
| +    _fields_ = [("cb", DWORD), | 
| +                ("lpReserved", LPWSTR), | 
| +                ("lpDesktop", LPWSTR), | 
| +                ("lpTitle", LPWSTR), | 
| +                ("dwX", DWORD), | 
| +                ("dwY", DWORD), | 
| +                ("dwXSize", DWORD), | 
| +                ("dwYSize", DWORD), | 
| +                ("dwXCountChars", DWORD), | 
| +                ("dwYCountChars", DWORD), | 
| +                ("dwFillAttribute", DWORD), | 
| +                ("dwFlags", DWORD), | 
| +                ("wShowWindow", WORD), | 
| +                ("cbReserved2", WORD), | 
| +                ("lpReserved2", LPBYTE), | 
| +                ("hStdInput", HANDLE), | 
| +                ("hStdOutput", HANDLE), | 
| +                ("hStdError", HANDLE) | 
| +                ] | 
| +LPSTARTUPINFO = POINTER(STARTUPINFO) | 
| + | 
| +SW_HIDE                 = 0 | 
| + | 
| +STARTF_USESHOWWINDOW    = 0x01 | 
| +STARTF_USESIZE          = 0x02 | 
| +STARTF_USEPOSITION      = 0x04 | 
| +STARTF_USECOUNTCHARS    = 0x08 | 
| +STARTF_USEFILLATTRIBUTE = 0x10 | 
| +STARTF_RUNFULLSCREEN    = 0x20 | 
| +STARTF_FORCEONFEEDBACK  = 0x40 | 
| +STARTF_FORCEOFFFEEDBACK = 0x80 | 
| +STARTF_USESTDHANDLES    = 0x100 | 
| + | 
| +# EnvironmentBlock | 
| + | 
| +class EnvironmentBlock: | 
| +    """An object which can be passed as the lpEnv parameter of CreateProcess. | 
| +    It is initialized with a dictionary.""" | 
| + | 
| +    def __init__(self, dict): | 
| +        if not dict: | 
| +            self._as_parameter_ = None | 
| +        else: | 
| +            values = ["%s=%s" % (key, value) | 
| +                      for (key, value) in dict.iteritems()] | 
| +            values.append("") | 
| +            self._as_parameter_ = LPCWSTR("\0".join(values)) | 
| + | 
| +# CreateProcess() | 
| + | 
| +CreateProcessProto = WINFUNCTYPE(BOOL,                  # Return type | 
| +                                 LPCWSTR,               # lpApplicationName | 
| +                                 LPWSTR,                # lpCommandLine | 
| +                                 LPVOID,                # lpProcessAttributes | 
| +                                 LPVOID,                # lpThreadAttributes | 
| +                                 BOOL,                  # bInheritHandles | 
| +                                 DWORD,                 # dwCreationFlags | 
| +                                 LPVOID,                # lpEnvironment | 
| +                                 LPCWSTR,               # lpCurrentDirectory | 
| +                                 LPSTARTUPINFO,         # lpStartupInfo | 
| +                                 LPPROCESS_INFORMATION  # lpProcessInformation | 
| +                                 ) | 
| + | 
| +CreateProcessFlags = ((1, "lpApplicationName", None), | 
| +                      (1, "lpCommandLine"), | 
| +                      (1, "lpProcessAttributes", None), | 
| +                      (1, "lpThreadAttributes", None), | 
| +                      (1, "bInheritHandles", True), | 
| +                      (1, "dwCreationFlags", 0), | 
| +                      (1, "lpEnvironment", None), | 
| +                      (1, "lpCurrentDirectory", None), | 
| +                      (1, "lpStartupInfo"), | 
| +                      (2, "lpProcessInformation")) | 
| + | 
| +def ErrCheckCreateProcess(result, func, args): | 
| +    ErrCheckBool(result, func, args) | 
| +    # return a tuple (hProcess, hThread, dwProcessID, dwThreadID) | 
| +    pi = args[9] | 
| +    return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID | 
| + | 
| +CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32), | 
| +                                   CreateProcessFlags) | 
| +CreateProcess.errcheck = ErrCheckCreateProcess | 
| + | 
| +# flags for CreateProcess | 
| +CREATE_BREAKAWAY_FROM_JOB = 0x01000000 | 
| +CREATE_DEFAULT_ERROR_MODE = 0x04000000 | 
| +CREATE_NEW_CONSOLE = 0x00000010 | 
| +CREATE_NEW_PROCESS_GROUP = 0x00000200 | 
| +CREATE_NO_WINDOW = 0x08000000 | 
| +CREATE_SUSPENDED = 0x00000004 | 
| +CREATE_UNICODE_ENVIRONMENT = 0x00000400 | 
| + | 
| +# flags for job limit information | 
| +# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx | 
| +JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 | 
| +JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 | 
| + | 
| +# XXX these flags should be documented | 
| +DEBUG_ONLY_THIS_PROCESS = 0x00000002 | 
| +DEBUG_PROCESS = 0x00000001 | 
| +DETACHED_PROCESS = 0x00000008 | 
| + | 
| +# CreateJobObject() | 
| + | 
| +CreateJobObjectProto = WINFUNCTYPE(HANDLE,             # Return type | 
| +                                   LPVOID,             # lpJobAttributes | 
| +                                   LPCWSTR             # lpName | 
| +                                   ) | 
| + | 
| +CreateJobObjectFlags = ((1, "lpJobAttributes", None), | 
| +                        (1, "lpName", None)) | 
| + | 
| +CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32), | 
| +                                       CreateJobObjectFlags) | 
| +CreateJobObject.errcheck = ErrCheckHandle | 
| + | 
| +# AssignProcessToJobObject() | 
| + | 
| +AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL,      # Return type | 
| +                                            HANDLE,    # hJob | 
| +                                            HANDLE     # hProcess | 
| +                                            ) | 
| +AssignProcessToJobObjectFlags = ((1, "hJob"), | 
| +                                 (1, "hProcess")) | 
| +AssignProcessToJobObject = AssignProcessToJobObjectProto( | 
| +    ("AssignProcessToJobObject", windll.kernel32), | 
| +    AssignProcessToJobObjectFlags) | 
| +AssignProcessToJobObject.errcheck = ErrCheckBool | 
| + | 
| +# GetCurrentProcess() | 
| +# because os.getPid() is way too easy | 
| +GetCurrentProcessProto = WINFUNCTYPE(HANDLE    # Return type | 
| +                                     ) | 
| +GetCurrentProcessFlags = () | 
| +GetCurrentProcess = GetCurrentProcessProto( | 
| +    ("GetCurrentProcess", windll.kernel32), | 
| +    GetCurrentProcessFlags) | 
| +GetCurrentProcess.errcheck = ErrCheckHandle | 
| + | 
| +# IsProcessInJob() | 
| +try: | 
| +    IsProcessInJobProto = WINFUNCTYPE(BOOL,     # Return type | 
| +                                      HANDLE,   # Process Handle | 
| +                                      HANDLE,   # Job Handle | 
| +                                      LPBOOL      # Result | 
| +                                      ) | 
| +    IsProcessInJobFlags = ((1, "ProcessHandle"), | 
| +                           (1, "JobHandle", HANDLE(0)), | 
| +                           (2, "Result")) | 
| +    IsProcessInJob = IsProcessInJobProto( | 
| +        ("IsProcessInJob", windll.kernel32), | 
| +        IsProcessInJobFlags) | 
| +    IsProcessInJob.errcheck = ErrCheckBool | 
| +except AttributeError: | 
| +    # windows 2k doesn't have this API | 
| +    def IsProcessInJob(process): | 
| +        return False | 
| + | 
| + | 
| +# ResumeThread() | 
| + | 
| +def ErrCheckResumeThread(result, func, args): | 
| +    if result == -1: | 
| +        raise WinError() | 
| + | 
| +    return args | 
| + | 
| +ResumeThreadProto = WINFUNCTYPE(DWORD,      # Return type | 
| +                                HANDLE      # hThread | 
| +                                ) | 
| +ResumeThreadFlags = ((1, "hThread"),) | 
| +ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32), | 
| +                                 ResumeThreadFlags) | 
| +ResumeThread.errcheck = ErrCheckResumeThread | 
| + | 
| +# TerminateProcess() | 
| + | 
| +TerminateProcessProto = WINFUNCTYPE(BOOL,   # Return type | 
| +                                    HANDLE, # hProcess | 
| +                                    UINT    # uExitCode | 
| +                                    ) | 
| +TerminateProcessFlags = ((1, "hProcess"), | 
| +                         (1, "uExitCode", 127)) | 
| +TerminateProcess = TerminateProcessProto( | 
| +    ("TerminateProcess", windll.kernel32), | 
| +    TerminateProcessFlags) | 
| +TerminateProcess.errcheck = ErrCheckBool | 
| + | 
| +# TerminateJobObject() | 
| + | 
| +TerminateJobObjectProto = WINFUNCTYPE(BOOL,   # Return type | 
| +                                      HANDLE, # hJob | 
| +                                      UINT    # uExitCode | 
| +                                      ) | 
| +TerminateJobObjectFlags = ((1, "hJob"), | 
| +                           (1, "uExitCode", 127)) | 
| +TerminateJobObject = TerminateJobObjectProto( | 
| +    ("TerminateJobObject", windll.kernel32), | 
| +    TerminateJobObjectFlags) | 
| +TerminateJobObject.errcheck = ErrCheckBool | 
| + | 
| +# WaitForSingleObject() | 
| + | 
| +WaitForSingleObjectProto = WINFUNCTYPE(DWORD,  # Return type | 
| +                                       HANDLE, # hHandle | 
| +                                       DWORD,  # dwMilliseconds | 
| +                                       ) | 
| +WaitForSingleObjectFlags = ((1, "hHandle"), | 
| +                            (1, "dwMilliseconds", -1)) | 
| +WaitForSingleObject = WaitForSingleObjectProto( | 
| +    ("WaitForSingleObject", windll.kernel32), | 
| +    WaitForSingleObjectFlags) | 
| + | 
| +INFINITE = -1 | 
| +WAIT_TIMEOUT = 0x0102 | 
| +WAIT_OBJECT_0 = 0x0 | 
| +WAIT_ABANDONED = 0x0080 | 
| + | 
| +# GetExitCodeProcess() | 
| + | 
| +GetExitCodeProcessProto = WINFUNCTYPE(BOOL,    # Return type | 
| +                                      HANDLE,  # hProcess | 
| +                                      LPDWORD, # lpExitCode | 
| +                                      ) | 
| +GetExitCodeProcessFlags = ((1, "hProcess"), | 
| +                           (2, "lpExitCode")) | 
| +GetExitCodeProcess = GetExitCodeProcessProto( | 
| +    ("GetExitCodeProcess", windll.kernel32), | 
| +    GetExitCodeProcessFlags) | 
| +GetExitCodeProcess.errcheck = ErrCheckBool | 
| + | 
| +def CanCreateJobObject(): | 
| +    currentProc = GetCurrentProcess() | 
| +    if IsProcessInJob(currentProc): | 
| +        jobinfo = QueryInformationJobObject(HANDLE(0), 'JobObjectExtendedLimitInformation') | 
| +        limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] | 
| +        return bool(limitflags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) or bool(limitflags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) | 
| +    else: | 
| +        return True | 
| + | 
| +### testing functions | 
| + | 
| +def parent(): | 
| +    print 'Starting parent' | 
| +    currentProc = GetCurrentProcess() | 
| +    if IsProcessInJob(currentProc): | 
| +        print >> sys.stderr, "You should not be in a job object to test" | 
| +        sys.exit(1) | 
| +    assert CanCreateJobObject() | 
| +    print 'File: %s' % __file__ | 
| +    command = [sys.executable, __file__, '-child'] | 
| +    print 'Running command: %s' % command | 
| +    process = Popen(command) | 
| +    process.kill() | 
| +    code = process.returncode | 
| +    print 'Child code: %s' % code | 
| +    assert code == 127 | 
| + | 
| +def child(): | 
| +    print 'Starting child' | 
| +    currentProc = GetCurrentProcess() | 
| +    injob = IsProcessInJob(currentProc) | 
| +    print "Is in a job?: %s" % injob | 
| +    can_create = CanCreateJobObject() | 
| +    print 'Can create job?: %s' % can_create | 
| +    process = Popen('c:\\windows\\notepad.exe') | 
| +    assert process._job | 
| +    jobinfo = QueryInformationJobObject(process._job, 'JobObjectExtendedLimitInformation') | 
| +    print 'Job info: %s' % jobinfo | 
| +    limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] | 
| +    print 'LimitFlags: %s' % limitflags | 
| +    process.kill() | 
| + | 
| +if __name__ == '__main__': | 
| +    import sys | 
| +    from killableprocess import Popen | 
| +    nargs = len(sys.argv[1:]) | 
| +    if nargs: | 
| +        if nargs != 1 or sys.argv[1] != '-child': | 
| +            raise AssertionError('Wrong flags; run like `python /path/to/winprocess.py`') | 
| +        child() | 
| +    else: | 
| +        parent() | 
|  |