diff --git a/mercurial/win32.py b/mercurial/win32.py --- a/mercurial/win32.py +++ b/mercurial/win32.py @@ -119,6 +119,27 @@ class _CONSOLE_SCREEN_BUFFER_INFO(ctypes _STD_ERROR_HANDLE = _DWORD(-12).value +# CreateToolhelp32Snapshot, Process32First, Process32Next +_TH32CS_SNAPPROCESS = 0x00000002 +_MAX_PATH = 260 + +class _tagPROCESSENTRY32(ctypes.Structure): + _fields_ = [('dwsize', _DWORD), + ('cntUsage', _DWORD), + ('th32ProcessID', _DWORD), + ('th32DefaultHeapID', ctypes.c_void_p), + ('th32ModuleID', _DWORD), + ('cntThreads', _DWORD), + ('th32ParentProcessID', _DWORD), + ('pcPriClassBase', _LONG), + ('dwFlags', _DWORD), + ('szExeFile', ctypes.c_char * _MAX_PATH)] + + def __init__(self): + super(_tagPROCESSENTRY32, self).__init__() + self.dwsize = ctypes.sizeof(self) + + # types of parameters of C functions used (required by pypy) _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, @@ -186,6 +207,15 @@ except AttributeError: _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] _user32.EnumWindows.restype = _BOOL +_kernel32.CreateToolhelp32Snapshot.argtypes = [_DWORD, _DWORD] +_kernel32.CreateToolhelp32Snapshot.restype = _BOOL + +_kernel32.Process32First.argtypes = [_HANDLE, ctypes.c_void_p] +_kernel32.Process32First.restype = _BOOL + +_kernel32.Process32Next.argtypes = [_HANDLE, ctypes.c_void_p] +_kernel32.Process32Next.restype = _BOOL + def _raiseoserror(name): err = ctypes.WinError() raise OSError(err.errno, '%s: %s' % (name, err.strerror)) @@ -309,6 +339,51 @@ def termwidth(): width = csbi.srWindow.Right - csbi.srWindow.Left return width +def _1stchild(pid): + '''return the 1st found child of the given pid + + None is returned when no child is found''' + pe = _tagPROCESSENTRY32() + + # create handle to list all processes + ph = _kernel32.CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0) + if ph == _INVALID_HANDLE_VALUE: + raise ctypes.WinError + try: + r = _kernel32.Process32First(ph, ctypes.byref(pe)) + # loop over all processes + while r: + if pe.th32ParentProcessID == pid: + # return first child found + return pe.th32ProcessID + r = _kernel32.Process32Next(ph, ctypes.byref(pe)) + finally: + _kernel32.CloseHandle(ph) + if _kernel32.GetLastError() != _ERROR_NO_MORE_FILES: + raise ctypes.WinError + return None # no child found + +class _tochildpid(int): # pid is _DWORD, which always matches in an int + '''helper for spawndetached, returns the child pid on conversion to string + + Does not resolve the child pid immediately because the child may not yet be + started. + ''' + def childpid(self): + '''returns the child pid of the first found child of the process + with this pid''' + return _1stchild(self) + def __str__(self): + # run when the pid is written to the file + ppid = self.childpid() + if ppid is None: + # race, child has exited since check + # fall back to this pid. Its process will also have disappeared, + # raising the same error type later as when the child pid would + # be returned. + return " %d" % self + return str(ppid) + def spawndetached(args): # No standard library function really spawns a fully detached # process under win32 because they allocate pipes or other objects @@ -339,7 +414,8 @@ def spawndetached(args): if not res: raise ctypes.WinError - return pi.dwProcessId + # _tochildpid because the process is the child of COMSPEC + return _tochildpid(pi.dwProcessId) def unlink(f): '''try to implement POSIX' unlink semantics on Windows'''