1# flake8: noqa 2# fmt: off 3## issue: https://bugs.python.org/issue19264 4 5import ctypes 6import os 7import platform 8import subprocess 9from ctypes import Structure, WinError, byref, c_char_p, c_void_p, c_wchar, c_wchar_p, sizeof, windll 10from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR, WORD 11 12import _subprocess 13 14## 15## Types 16## 17 18CREATE_UNICODE_ENVIRONMENT = 0x00000400 19LPCTSTR = c_char_p 20LPTSTR = c_wchar_p 21LPSECURITY_ATTRIBUTES = c_void_p 22LPBYTE = ctypes.POINTER(BYTE) 23 24class STARTUPINFOW(Structure): 25 _fields_ = [ 26 ("cb", DWORD), ("lpReserved", LPWSTR), 27 ("lpDesktop", LPWSTR), ("lpTitle", LPWSTR), 28 ("dwX", DWORD), ("dwY", DWORD), 29 ("dwXSize", DWORD), ("dwYSize", DWORD), 30 ("dwXCountChars", DWORD), ("dwYCountChars", DWORD), 31 ("dwFillAtrribute", DWORD), ("dwFlags", DWORD), 32 ("wShowWindow", WORD), ("cbReserved2", WORD), 33 ("lpReserved2", LPBYTE), ("hStdInput", HANDLE), 34 ("hStdOutput", HANDLE), ("hStdError", HANDLE), 35 ] 36 37LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW) 38 39 40class PROCESS_INFORMATION(Structure): 41 _fields_ = [ 42 ("hProcess", HANDLE), ("hThread", HANDLE), 43 ("dwProcessId", DWORD), ("dwThreadId", DWORD), 44 ] 45 46LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION) 47 48 49class DUMMY_HANDLE(ctypes.c_void_p): 50 51 def __init__(self, *a, **kw): 52 super(DUMMY_HANDLE, self).__init__(*a, **kw) 53 self.closed = False 54 55 def Close(self): 56 if not self.closed: 57 windll.kernel32.CloseHandle(self) 58 self.closed = True 59 60 def __int__(self): 61 return self.value 62 63 64CreateProcessW = windll.kernel32.CreateProcessW 65CreateProcessW.argtypes = [ 66 LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, 67 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, 68 LPSTARTUPINFOW, LPPROCESS_INFORMATION, 69] 70CreateProcessW.restype = BOOL 71 72 73## 74## Patched functions/classes 75## 76 77def CreateProcess( 78 executable, args, _p_attr, _t_attr, 79 inherit_handles, creation_flags, env, cwd, 80 startup_info, 81): 82 """Create a process supporting unicode executable and args for win32 83 84 Python implementation of CreateProcess using CreateProcessW for Win32 85 86 """ 87 88 si = STARTUPINFOW( 89 dwFlags=startup_info.dwFlags, 90 wShowWindow=startup_info.wShowWindow, 91 cb=sizeof(STARTUPINFOW), 92 ## XXXvlab: not sure of the casting here to ints. 93 hStdInput=startup_info.hStdInput if startup_info.hStdInput is None else int(startup_info.hStdInput), 94 hStdOutput=startup_info.hStdOutput if startup_info.hStdOutput is None else int(startup_info.hStdOutput), 95 hStdError=startup_info.hStdError if startup_info.hStdError is None else int(startup_info.hStdError), 96 ) 97 98 wenv = None 99 if env is not None: 100 ## LPCWSTR seems to be c_wchar_p, so let's say CWSTR is c_wchar 101 env = ( 102 unicode("").join([ 103 unicode("%s=%s\0") % (k, v) 104 for k, v in env.items() 105 ]) 106 ) + unicode("\0") 107 wenv = (c_wchar * len(env))() 108 wenv.value = env 109 110 wcwd = None 111 if cwd is not None: 112 wcwd = unicode(cwd) 113 114 pi = PROCESS_INFORMATION() 115 creation_flags |= CREATE_UNICODE_ENVIRONMENT 116 117 if CreateProcessW( 118 executable, args, None, None, 119 inherit_handles, creation_flags, 120 wenv, wcwd, byref(si), byref(pi), 121 ): 122 return ( 123 DUMMY_HANDLE(pi.hProcess), DUMMY_HANDLE(pi.hThread), 124 pi.dwProcessId, pi.dwThreadId, 125 ) 126 raise WinError() 127 128 129class Popen(subprocess.Popen): 130 """This superseeds Popen and corrects a bug in cPython 2.7 implem""" 131 132 def _execute_child( 133 self, args, executable, preexec_fn, close_fds, 134 cwd, env, universal_newlines, 135 startupinfo, creationflags, shell, to_close, 136 p2cread, p2cwrite, 137 c2pread, c2pwrite, 138 errread, errwrite, 139 ): 140 """Code from part of _execute_child from Python 2.7 (9fbb65e) 141 142 There are only 2 little changes concerning the construction of 143 the the final string in shell mode: we preempt the creation of 144 the command string when shell is True, because original function 145 will try to encode unicode args which we want to avoid to be able to 146 sending it as-is to ``CreateProcess``. 147 148 """ 149 if startupinfo is None: 150 startupinfo = subprocess.STARTUPINFO() 151 if not isinstance(args, subprocess.types.StringTypes): 152 args = [i if isinstance(i, bytes) else i.encode('utf-8') for i in args] 153 args = subprocess.list2cmdline(args) 154 if platform.python_implementation() == "CPython": 155 args = args.decode('utf-8') 156 startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW 157 startupinfo.wShowWindow = _subprocess.SW_HIDE 158 env = os.environ if env is None else env 159 comspec = env.get("COMSPEC", unicode("cmd.exe")) 160 if ( 161 _subprocess.GetVersion() >= 0x80000000 or 162 os.path.basename(comspec).lower() == "command.com" 163 ): 164 w9xpopen = self._find_w9xpopen() 165 args = unicode('"%s" %s') % (w9xpopen, args) 166 creationflags |= _subprocess.CREATE_NEW_CONSOLE 167 168 super(Popen, self)._execute_child( 169 args, executable, 170 preexec_fn, close_fds, cwd, env, universal_newlines, 171 startupinfo, creationflags, False, to_close, p2cread, 172 p2cwrite, c2pread, c2pwrite, errread, errwrite, 173 ) 174 175_subprocess.CreateProcess = CreateProcess 176# fmt: on 177