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