1#!/usr/bin/env python3 2# This file is part of Xpra. 3# This code is released under the terms of the CC-BY-SA license: 4# https://creativecommons.org/licenses/by-sa/2.0/legalcode 5# This subprocess.Popen code was found here: 6# https://stackoverflow.com/questions/29566330/ 7 8#@PydevCodeAnalysisIgnore 9#pylint: disable=unused-argument 10 11import os 12import subprocess 13import ctypes 14from ctypes import wintypes 15 16kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) 17advapi32 = ctypes.WinDLL('advapi32', use_last_error=True) 18 19ERROR_INVALID_HANDLE = 0x0006 20INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value 21INVALID_DWORD_VALUE = wintypes.DWORD(-1).value 22 23DEBUG_PROCESS = 0x00000001 24DEBUG_ONLY_THIS_PROCESS = 0x00000002 25CREATE_SUSPENDED = 0x00000004 26DETACHED_PROCESS = 0x00000008 27CREATE_NEW_CONSOLE = 0x00000010 28CREATE_NEW_PROCESS_GROUP = 0x00000200 29CREATE_UNICODE_ENVIRONMENT = 0x00000400 30CREATE_SEPARATE_WOW_VDM = 0x00000800 31CREATE_SHARED_WOW_VDM = 0x00001000 32INHERIT_PARENT_AFFINITY = 0x00010000 33CREATE_PROTECTED_PROCESS = 0x00040000 34EXTENDED_STARTUPINFO_PRESENT = 0x00080000 35CREATE_BREAKAWAY_FROM_JOB = 0x01000000 36CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000 37CREATE_DEFAULT_ERROR_MODE = 0x04000000 38CREATE_NO_WINDOW = 0x08000000 39 40STARTF_USESHOWWINDOW = 0x00000001 41STARTF_USESIZE = 0x00000002 42STARTF_USEPOSITION = 0x00000004 43STARTF_USECOUNTCHARS = 0x00000008 44STARTF_USEFILLATTRIBUTE = 0x00000010 45STARTF_RUNFULLSCREEN = 0x00000020 46STARTF_FORCEONFEEDBACK = 0x00000040 47STARTF_FORCEOFFFEEDBACK = 0x00000080 48STARTF_USESTDHANDLES = 0x00000100 49STARTF_USEHOTKEY = 0x00000200 50STARTF_TITLEISLINKNAME = 0x00000800 51STARTF_TITLEISAPPID = 0x00001000 52STARTF_PREVENTPINNING = 0x00002000 53 54SW_HIDE = 0 55SW_SHOWNORMAL = 1 56SW_SHOWMINIMIZED = 2 57SW_SHOWMAXIMIZED = 3 58SW_SHOWNOACTIVATE = 4 59SW_SHOW = 5 60SW_MINIMIZE = 6 61SW_SHOWMINNOACTIVE = 7 62SW_SHOWNA = 8 63SW_RESTORE = 9 64SW_SHOWDEFAULT = 10 # ~STARTUPINFO 65SW_FORCEMINIMIZE = 11 66 67LOGON_WITH_PROFILE = 0x00000001 68LOGON_NETCREDENTIALS_ONLY = 0x00000002 69 70STD_INPUT_HANDLE = wintypes.DWORD(-10).value 71STD_OUTPUT_HANDLE = wintypes.DWORD(-11).value 72STD_ERROR_HANDLE = wintypes.DWORD(-12).value 73 74class HANDLE(wintypes.HANDLE): 75 __slots__ = 'closed', 76 77 def __int__(self): 78 return self.value or 0 79 80 def Detach(self): 81 if not getattr(self, 'closed', False): 82 self.closed = True 83 value = int(self) 84 self.value = None 85 return value 86 raise ValueError("already closed") 87 88 def Close(self, CloseHandle=kernel32.CloseHandle): 89 if self and not getattr(self, 'closed', False): 90 CloseHandle(self.Detach()) 91 92 __del__ = Close 93 94 def __repr__(self): 95 return "%s(%d)" % (self.__class__.__name__, int(self)) 96 97class PROCESS_INFORMATION(ctypes.Structure): 98 """https://msdn.microsoft.com/en-us/library/ms684873""" 99 __slots__ = '_cached_hProcess', '_cached_hThread' 100 101 _fields_ = (('_hProcess', HANDLE), 102 ('_hThread', HANDLE), 103 ('dwProcessId', wintypes.DWORD), 104 ('dwThreadId', wintypes.DWORD)) 105 106 @property 107 def hProcess(self): 108 if not hasattr(self, '_cached_hProcess'): 109 self._cached_hProcess = self._hProcess 110 return self._cached_hProcess 111 112 @property 113 def hThread(self): 114 if not hasattr(self, '_cached_hThread'): 115 self._cached_hThread = self._hThread 116 return self._cached_hThread 117 118 def __del__(self): 119 try: 120 self.hProcess.Close() 121 finally: 122 self.hThread.Close() 123 124LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION) 125 126LPBYTE = ctypes.POINTER(wintypes.BYTE) 127 128class STARTUPINFO(ctypes.Structure): 129 """https://msdn.microsoft.com/en-us/library/ms686331""" 130 _fields_ = (('cb', wintypes.DWORD), 131 ('lpReserved', wintypes.LPWSTR), 132 ('lpDesktop', wintypes.LPWSTR), 133 ('lpTitle', wintypes.LPWSTR), 134 ('dwX', wintypes.DWORD), 135 ('dwY', wintypes.DWORD), 136 ('dwXSize', wintypes.DWORD), 137 ('dwYSize', wintypes.DWORD), 138 ('dwXCountChars', wintypes.DWORD), 139 ('dwYCountChars', wintypes.DWORD), 140 ('dwFillAttribute', wintypes.DWORD), 141 ('dwFlags', wintypes.DWORD), 142 ('wShowWindow', wintypes.WORD), 143 ('cbReserved2', wintypes.WORD), 144 ('lpReserved2', LPBYTE), 145 ('hStdInput', wintypes.HANDLE), 146 ('hStdOutput', wintypes.HANDLE), 147 ('hStdError', wintypes.HANDLE)) 148 149 def __init__(self, **kwds): 150 self.cb = ctypes.sizeof(self) 151 super().__init__(**kwds) 152 153class PROC_THREAD_ATTRIBUTE_LIST(ctypes.Structure): 154 pass 155 156PPROC_THREAD_ATTRIBUTE_LIST = ctypes.POINTER(PROC_THREAD_ATTRIBUTE_LIST) 157 158class STARTUPINFOEX(STARTUPINFO): 159 _fields_ = (('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST),) 160 161LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO) 162LPSTARTUPINFOEX = ctypes.POINTER(STARTUPINFOEX) 163 164class SECURITY_ATTRIBUTES(ctypes.Structure): 165 _fields_ = (('nLength', wintypes.DWORD), 166 ('lpSecurityDescriptor', wintypes.LPVOID), 167 ('bInheritHandle', wintypes.BOOL)) 168 def __init__(self, **kwds): 169 self.nLength = ctypes.sizeof(self) 170 super().__init__(**kwds) 171 172LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES) 173 174class HANDLE_IHV(HANDLE): 175 pass 176 177class DWORD_IDV(wintypes.DWORD): 178 pass 179 180def _check_ihv(result, func, args): 181 if result.value == INVALID_HANDLE_VALUE: 182 raise ctypes.WinError(ctypes.get_last_error()) 183 return result.value 184 185def _check_idv(result, func, args): 186 if result.value == INVALID_DWORD_VALUE: 187 raise ctypes.WinError(ctypes.get_last_error()) 188 return result.value 189 190def _check_bool(result, func, args): 191 if not result: 192 raise ctypes.WinError(ctypes.get_last_error()) 193 return args 194 195def WIN(func, restype, *argtypes): 196 func.restype = restype 197 func.argtypes = argtypes 198 if issubclass(restype, HANDLE_IHV): 199 func.errcheck = _check_ihv 200 elif issubclass(restype, DWORD_IDV): 201 func.errcheck = _check_idv 202 else: 203 func.errcheck = _check_bool 204 205# https://msdn.microsoft.com/en-us/library/ms724211 206WIN(kernel32.CloseHandle, wintypes.BOOL, 207 wintypes.HANDLE,) # _In_ HANDLE hObject 208 209# https://msdn.microsoft.com/en-us/library/ms685086 210WIN(kernel32.ResumeThread, DWORD_IDV, 211 wintypes.HANDLE,) # _In_ hThread 212 213# https://msdn.microsoft.com/en-us/library/ms682425 214WIN(kernel32.CreateProcessW, wintypes.BOOL, 215 wintypes.LPCWSTR, # _In_opt_ lpApplicationName 216 wintypes.LPWSTR, # _Inout_opt_ lpCommandLine 217 LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes 218 LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes 219 wintypes.BOOL, # _In_ bInheritHandles 220 wintypes.DWORD, # _In_ dwCreationFlags 221 wintypes.LPCWSTR, # _In_opt_ lpEnvironment 222 wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory 223 LPSTARTUPINFO, # _In_ lpStartupInfo 224 LPPROCESS_INFORMATION) # _Out_ lpProcessInformation 225 226# https://msdn.microsoft.com/en-us/library/ms682429 227WIN(advapi32.CreateProcessAsUserW, wintypes.BOOL, 228 wintypes.HANDLE, # _In_opt_ hToken 229 wintypes.LPCWSTR, # _In_opt_ lpApplicationName 230 wintypes.LPWSTR, # _Inout_opt_ lpCommandLine 231 LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes 232 LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes 233 wintypes.BOOL, # _In_ bInheritHandles 234 wintypes.DWORD, # _In_ dwCreationFlags 235 wintypes.LPCWSTR, # _In_opt_ lpEnvironment 236 wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory 237 LPSTARTUPINFO, # _In_ lpStartupInfo 238 LPPROCESS_INFORMATION) # _Out_ lpProcessInformation 239 240# https://msdn.microsoft.com/en-us/library/ms682434 241WIN(advapi32.CreateProcessWithTokenW, wintypes.BOOL, 242 wintypes.HANDLE, # _In_ hToken 243 wintypes.DWORD, # _In_ dwLogonFlags 244 wintypes.LPCWSTR, # _In_opt_ lpApplicationName 245 wintypes.LPWSTR, # _Inout_opt_ lpCommandLine 246 wintypes.DWORD, # _In_ dwCreationFlags 247 wintypes.LPCWSTR, # _In_opt_ lpEnvironment 248 wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory 249 LPSTARTUPINFO, # _In_ lpStartupInfo 250 LPPROCESS_INFORMATION) # _Out_ lpProcessInformation 251 252# https://msdn.microsoft.com/en-us/library/ms682431 253WIN(advapi32.CreateProcessWithLogonW, wintypes.BOOL, 254 wintypes.LPCWSTR, # _In_ lpUsername 255 wintypes.LPCWSTR, # _In_opt_ lpDomain 256 wintypes.LPCWSTR, # _In_ lpPassword 257 wintypes.DWORD, # _In_ dwLogonFlags 258 wintypes.LPCWSTR, # _In_opt_ lpApplicationName 259 wintypes.LPWSTR, # _Inout_opt_ lpCommandLine 260 wintypes.DWORD, # _In_ dwCreationFlags 261 wintypes.LPCWSTR, # _In_opt_ lpEnvironment 262 wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory 263 LPSTARTUPINFO, # _In_ lpStartupInfo 264 LPPROCESS_INFORMATION) # _Out_ lpProcessInformation 265 266 267CREATION_TYPE_NORMAL = 0 268CREATION_TYPE_LOGON = 1 269CREATION_TYPE_TOKEN = 2 270CREATION_TYPE_USER = 3 271 272class CREATIONINFO: 273 __slots__ = ('dwCreationType', 274 'lpApplicationName', 'lpCommandLine', 'bUseShell', 275 'lpProcessAttributes', 'lpThreadAttributes', 'bInheritHandles', 276 'dwCreationFlags', 'lpEnvironment', 'lpCurrentDirectory', 277 'hToken', 'lpUsername', 'lpDomain', 'lpPassword', 'dwLogonFlags') 278 279 def __init__(self, dwCreationType=CREATION_TYPE_NORMAL, 280 lpApplicationName=None, lpCommandLine=None, bUseShell=False, 281 lpProcessAttributes=None, lpThreadAttributes=None, 282 bInheritHandles=False, dwCreationFlags=0, lpEnvironment=None, 283 lpCurrentDirectory=None, hToken=None, dwLogonFlags=0, 284 lpUsername=None, lpDomain=None, lpPassword=None): 285 self.dwCreationType = dwCreationType 286 self.lpApplicationName = lpApplicationName 287 self.lpCommandLine = lpCommandLine 288 self.bUseShell = bUseShell 289 self.lpProcessAttributes = lpProcessAttributes 290 self.lpThreadAttributes = lpThreadAttributes 291 self.bInheritHandles = bInheritHandles 292 self.dwCreationFlags = dwCreationFlags 293 self.lpEnvironment = lpEnvironment 294 self.lpCurrentDirectory = lpCurrentDirectory 295 self.hToken = hToken 296 self.lpUsername = lpUsername 297 self.lpDomain = lpDomain 298 self.lpPassword = lpPassword 299 self.dwLogonFlags = dwLogonFlags 300 301def create_environment(environ): 302 if environ is not None: 303 items = ['%s=%s' % (k, environ[k]) for k in sorted(environ)] 304 buf = '\x00'.join(items) 305 length = len(buf) + 2 if buf else 1 306 return ctypes.create_unicode_buffer(buf, length) 307 308def create_process(commandline=None, creationinfo=None, startupinfo=None): 309 if creationinfo is None: 310 creationinfo = CREATIONINFO() 311 312 if startupinfo is None: 313 startupinfo = STARTUPINFO() 314 elif isinstance(startupinfo, subprocess.STARTUPINFO): 315 startupinfo = STARTUPINFO(dwFlags=startupinfo.dwFlags, 316 hStdInput=startupinfo.hStdInput, 317 hStdOutput=startupinfo.hStdOutput, 318 hStdError=startupinfo.hStdError, 319 wShowWindow=startupinfo.wShowWindow) 320 321 si, ci, pi = startupinfo, creationinfo, PROCESS_INFORMATION() 322 323 if commandline is None: 324 commandline = ci.lpCommandLine 325 326 if commandline is not None: 327 if ci.bUseShell: 328 si.dwFlags |= STARTF_USESHOWWINDOW 329 si.wShowWindow = SW_HIDE 330 comspec = os.environ.get("ComSpec", os.path.join( 331 os.environ["SystemRoot"], "System32", "cmd.exe")) 332 commandline = '"{}" /c "{}"'.format(comspec, commandline) 333 commandline = ctypes.create_unicode_buffer(commandline) 334 335 dwCreationFlags = ci.dwCreationFlags | CREATE_UNICODE_ENVIRONMENT 336 lpEnvironment = create_environment(ci.lpEnvironment) 337 338 if (dwCreationFlags & DETACHED_PROCESS and 339 ((dwCreationFlags & CREATE_NEW_CONSOLE) or 340 (ci.dwCreationType == CREATION_TYPE_LOGON) or 341 (ci.dwCreationType == CREATION_TYPE_TOKEN))): 342 raise RuntimeError('DETACHED_PROCESS is incompatible with ' 343 'CREATE_NEW_CONSOLE, which is implied for ' 344 'the logon and token creation types') 345 346 if ci.dwCreationType == CREATION_TYPE_NORMAL: 347 348 kernel32.CreateProcessW( 349 ci.lpApplicationName, commandline, 350 ci.lpProcessAttributes, ci.lpThreadAttributes, ci.bInheritHandles, 351 dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, 352 ctypes.byref(si), ctypes.byref(pi)) 353 354 elif ci.dwCreationType == CREATION_TYPE_LOGON: 355 356 advapi32.CreateProcessWithLogonW( 357 ci.lpUsername, ci.lpDomain, ci.lpPassword, ci.dwLogonFlags, 358 ci.lpApplicationName, commandline, 359 dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, 360 ctypes.byref(si), ctypes.byref(pi)) 361 362 elif ci.dwCreationType == CREATION_TYPE_TOKEN: 363 364 advapi32.CreateProcessWithTokenW( 365 ci.hToken, ci.dwLogonFlags, 366 ci.lpApplicationName, commandline, 367 dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, 368 ctypes.byref(si), ctypes.byref(pi)) 369 370 elif ci.dwCreationType == CREATION_TYPE_USER: 371 372 advapi32.CreateProcessAsUserW( 373 ci.hToken, 374 ci.lpApplicationName, commandline, 375 ci.lpProcessAttributes, ci.lpThreadAttributes, ci.bInheritHandles, 376 dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, 377 ctypes.byref(si), ctypes.byref(pi)) 378 379 else: 380 raise ValueError('invalid process creation type') 381 382 return pi 383 384 385class Popen(subprocess.Popen): 386 def __init__(self, *args, **kwds): 387 ci = self._creationinfo = kwds.pop('creationinfo', CREATIONINFO()) 388 if kwds.pop('suspended', False): 389 ci.dwCreationFlags |= CREATE_SUSPENDED 390 self._child_started = False 391 super().__init__(*args, **kwds) 392 393 def _execute_child(self, args, executable, preexec_fn, close_fds, 394 pass_fds, cwd, env, startupinfo, creationflags, 395 shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, 396 errwrite, restore_signals, start_new_session): 397 """Execute program (MS Windows version)""" 398 assert not pass_fds, "pass_fds not supported on Windows." 399 commandline = (args if isinstance(args, str) else 400 subprocess.list2cmdline(args)) 401 self._common_execute_child(executable, commandline, shell, 402 close_fds, creationflags, env, cwd, 403 startupinfo, p2cread, c2pwrite, errwrite) 404 405 def _common_execute_child(self, executable, commandline, shell, 406 close_fds, creationflags, env, cwd, 407 startupinfo, p2cread, c2pwrite, errwrite, 408 to_close=()): 409 410 ci = self._creationinfo 411 if executable is not None: 412 ci.lpApplicationName = executable 413 if commandline: 414 ci.lpCommandLine = commandline 415 if shell: 416 ci.bUseShell = shell 417 if not close_fds: 418 ci.bInheritHandles = int(not close_fds) 419 if creationflags: 420 ci.dwCreationFlags |= creationflags 421 if env is not None: 422 ci.lpEnvironment = env 423 if cwd is not None: 424 ci.lpCurrentDirectory = cwd 425 426 if startupinfo is None: 427 startupinfo = STARTUPINFO() 428 si = self._startupinfo = startupinfo 429 430 default = -1 431 if default not in (p2cread, c2pwrite, errwrite): 432 si.dwFlags |= STARTF_USESTDHANDLES 433 si.hStdInput = int( p2cread) 434 si.hStdOutput = int(c2pwrite) 435 si.hStdError = int(errwrite) 436 437 try: 438 pi = create_process(creationinfo=ci, startupinfo=si) 439 finally: 440 if p2cread != -1: 441 p2cread.Close() 442 if c2pwrite != -1: 443 c2pwrite.Close() 444 if errwrite != -1: 445 errwrite.Close() 446 if hasattr(self, '_devnull'): 447 os.close(self._devnull) #pylint: disable=no-member 448 449 if not ci.dwCreationFlags & CREATE_SUSPENDED: 450 self._child_started = True 451 452 # Retain the process handle, but close the thread handle 453 # if it's no longer needed. 454 self._processinfo = pi 455 self._handle = pi.hProcess.Detach() 456 self.pid = pi.dwProcessId 457 if self._child_started: 458 pi.hThread.Close() 459 460 def start(self): 461 if self._child_started: 462 raise RuntimeError("processes can only be started once") 463 hThread = self._processinfo.hThread 464 prev_count = kernel32.ResumeThread(hThread) 465 if prev_count > 1: 466 for _ in range(1, prev_count): 467 if kernel32.ResumeThread(hThread) <= 1: 468 break 469 else: 470 raise RuntimeError('cannot start the main thread') 471 # The thread's previous suspend count was 0 or 1, 472 # so it should be running now. 473 self._child_started = True 474 hThread.Close() 475 476 def __del__(self): 477 if not self._child_started: 478 try: 479 if hasattr(self, '_processinfo'): 480 self._processinfo.hThread.Close() 481 finally: 482 if hasattr(self, '_handle'): 483 self.terminate() 484 super().__del__() #pylint: disable=no-member 485