_process_win32_controller.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. """Windows-specific implementation of process utilities with direct WinAPI.
  2. This file is meant to be used by process.py
  3. """
  4. #-----------------------------------------------------------------------------
  5. # Copyright (C) 2010-2011 The IPython Development Team
  6. #
  7. # Distributed under the terms of the BSD License. The full license is in
  8. # the file COPYING, distributed as part of this software.
  9. #-----------------------------------------------------------------------------
  10. from __future__ import print_function
  11. # stdlib
  12. import os, sys, threading
  13. import ctypes, msvcrt
  14. # local imports
  15. from . import py3compat
  16. # Win32 API types needed for the API calls
  17. from ctypes import POINTER
  18. from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
  19. ULONG, LPCWSTR
  20. LPDWORD = POINTER(DWORD)
  21. LPHANDLE = POINTER(HANDLE)
  22. ULONG_PTR = POINTER(ULONG)
  23. class SECURITY_ATTRIBUTES(ctypes.Structure):
  24. _fields_ = [("nLength", DWORD),
  25. ("lpSecurityDescriptor", LPVOID),
  26. ("bInheritHandle", BOOL)]
  27. LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
  28. class STARTUPINFO(ctypes.Structure):
  29. _fields_ = [("cb", DWORD),
  30. ("lpReserved", LPCWSTR),
  31. ("lpDesktop", LPCWSTR),
  32. ("lpTitle", LPCWSTR),
  33. ("dwX", DWORD),
  34. ("dwY", DWORD),
  35. ("dwXSize", DWORD),
  36. ("dwYSize", DWORD),
  37. ("dwXCountChars", DWORD),
  38. ("dwYCountChars", DWORD),
  39. ("dwFillAttribute", DWORD),
  40. ("dwFlags", DWORD),
  41. ("wShowWindow", WORD),
  42. ("cbReserved2", WORD),
  43. ("lpReserved2", LPVOID),
  44. ("hStdInput", HANDLE),
  45. ("hStdOutput", HANDLE),
  46. ("hStdError", HANDLE)]
  47. LPSTARTUPINFO = POINTER(STARTUPINFO)
  48. class PROCESS_INFORMATION(ctypes.Structure):
  49. _fields_ = [("hProcess", HANDLE),
  50. ("hThread", HANDLE),
  51. ("dwProcessId", DWORD),
  52. ("dwThreadId", DWORD)]
  53. LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
  54. # Win32 API constants needed
  55. ERROR_HANDLE_EOF = 38
  56. ERROR_BROKEN_PIPE = 109
  57. ERROR_NO_DATA = 232
  58. HANDLE_FLAG_INHERIT = 0x0001
  59. STARTF_USESTDHANDLES = 0x0100
  60. CREATE_SUSPENDED = 0x0004
  61. CREATE_NEW_CONSOLE = 0x0010
  62. CREATE_NO_WINDOW = 0x08000000
  63. STILL_ACTIVE = 259
  64. WAIT_TIMEOUT = 0x0102
  65. WAIT_FAILED = 0xFFFFFFFF
  66. INFINITE = 0xFFFFFFFF
  67. DUPLICATE_SAME_ACCESS = 0x00000002
  68. ENABLE_ECHO_INPUT = 0x0004
  69. ENABLE_LINE_INPUT = 0x0002
  70. ENABLE_PROCESSED_INPUT = 0x0001
  71. # Win32 API functions needed
  72. GetLastError = ctypes.windll.kernel32.GetLastError
  73. GetLastError.argtypes = []
  74. GetLastError.restype = DWORD
  75. CreateFile = ctypes.windll.kernel32.CreateFileW
  76. CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
  77. CreateFile.restype = HANDLE
  78. CreatePipe = ctypes.windll.kernel32.CreatePipe
  79. CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
  80. LPSECURITY_ATTRIBUTES, DWORD]
  81. CreatePipe.restype = BOOL
  82. CreateProcess = ctypes.windll.kernel32.CreateProcessW
  83. CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
  84. LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
  85. LPPROCESS_INFORMATION]
  86. CreateProcess.restype = BOOL
  87. GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
  88. GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
  89. GetExitCodeProcess.restype = BOOL
  90. GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
  91. GetCurrentProcess.argtypes = []
  92. GetCurrentProcess.restype = HANDLE
  93. ResumeThread = ctypes.windll.kernel32.ResumeThread
  94. ResumeThread.argtypes = [HANDLE]
  95. ResumeThread.restype = DWORD
  96. ReadFile = ctypes.windll.kernel32.ReadFile
  97. ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
  98. ReadFile.restype = BOOL
  99. WriteFile = ctypes.windll.kernel32.WriteFile
  100. WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
  101. WriteFile.restype = BOOL
  102. GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
  103. GetConsoleMode.argtypes = [HANDLE, LPDWORD]
  104. GetConsoleMode.restype = BOOL
  105. SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
  106. SetConsoleMode.argtypes = [HANDLE, DWORD]
  107. SetConsoleMode.restype = BOOL
  108. FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
  109. FlushConsoleInputBuffer.argtypes = [HANDLE]
  110. FlushConsoleInputBuffer.restype = BOOL
  111. WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
  112. WaitForSingleObject.argtypes = [HANDLE, DWORD]
  113. WaitForSingleObject.restype = DWORD
  114. DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
  115. DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
  116. DWORD, BOOL, DWORD]
  117. DuplicateHandle.restype = BOOL
  118. SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
  119. SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
  120. SetHandleInformation.restype = BOOL
  121. CloseHandle = ctypes.windll.kernel32.CloseHandle
  122. CloseHandle.argtypes = [HANDLE]
  123. CloseHandle.restype = BOOL
  124. CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
  125. CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
  126. CommandLineToArgvW.restype = POINTER(LPCWSTR)
  127. LocalFree = ctypes.windll.kernel32.LocalFree
  128. LocalFree.argtypes = [HLOCAL]
  129. LocalFree.restype = HLOCAL
  130. class AvoidUNCPath(object):
  131. """A context manager to protect command execution from UNC paths.
  132. In the Win32 API, commands can't be invoked with the cwd being a UNC path.
  133. This context manager temporarily changes directory to the 'C:' drive on
  134. entering, and restores the original working directory on exit.
  135. The context manager returns the starting working directory *if* it made a
  136. change and None otherwise, so that users can apply the necessary adjustment
  137. to their system calls in the event of a change.
  138. Examples
  139. --------
  140. ::
  141. cmd = 'dir'
  142. with AvoidUNCPath() as path:
  143. if path is not None:
  144. cmd = '"pushd %s &&"%s' % (path, cmd)
  145. os.system(cmd)
  146. """
  147. def __enter__(self):
  148. self.path = py3compat.getcwd()
  149. self.is_unc_path = self.path.startswith(r"\\")
  150. if self.is_unc_path:
  151. # change to c drive (as cmd.exe cannot handle UNC addresses)
  152. os.chdir("C:")
  153. return self.path
  154. else:
  155. # We return None to signal that there was no change in the working
  156. # directory
  157. return None
  158. def __exit__(self, exc_type, exc_value, traceback):
  159. if self.is_unc_path:
  160. os.chdir(self.path)
  161. class Win32ShellCommandController(object):
  162. """Runs a shell command in a 'with' context.
  163. This implementation is Win32-specific.
  164. Example:
  165. # Runs the command interactively with default console stdin/stdout
  166. with ShellCommandController('python -i') as scc:
  167. scc.run()
  168. # Runs the command using the provided functions for stdin/stdout
  169. def my_stdout_func(s):
  170. # print or save the string 's'
  171. write_to_stdout(s)
  172. def my_stdin_func():
  173. # If input is available, return it as a string.
  174. if input_available():
  175. return get_input()
  176. # If no input available, return None after a short delay to
  177. # keep from blocking.
  178. else:
  179. time.sleep(0.01)
  180. return None
  181. with ShellCommandController('python -i') as scc:
  182. scc.run(my_stdout_func, my_stdin_func)
  183. """
  184. def __init__(self, cmd, mergeout = True):
  185. """Initializes the shell command controller.
  186. The cmd is the program to execute, and mergeout is
  187. whether to blend stdout and stderr into one output
  188. in stdout. Merging them together in this fashion more
  189. reliably keeps stdout and stderr in the correct order
  190. especially for interactive shell usage.
  191. """
  192. self.cmd = cmd
  193. self.mergeout = mergeout
  194. def __enter__(self):
  195. cmd = self.cmd
  196. mergeout = self.mergeout
  197. self.hstdout, self.hstdin, self.hstderr = None, None, None
  198. self.piProcInfo = None
  199. try:
  200. p_hstdout, c_hstdout, p_hstderr, \
  201. c_hstderr, p_hstdin, c_hstdin = [None]*6
  202. # SECURITY_ATTRIBUTES with inherit handle set to True
  203. saAttr = SECURITY_ATTRIBUTES()
  204. saAttr.nLength = ctypes.sizeof(saAttr)
  205. saAttr.bInheritHandle = True
  206. saAttr.lpSecurityDescriptor = None
  207. def create_pipe(uninherit):
  208. """Creates a Windows pipe, which consists of two handles.
  209. The 'uninherit' parameter controls which handle is not
  210. inherited by the child process.
  211. """
  212. handles = HANDLE(), HANDLE()
  213. if not CreatePipe(ctypes.byref(handles[0]),
  214. ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
  215. raise ctypes.WinError()
  216. if not SetHandleInformation(handles[uninherit],
  217. HANDLE_FLAG_INHERIT, 0):
  218. raise ctypes.WinError()
  219. return handles[0].value, handles[1].value
  220. p_hstdout, c_hstdout = create_pipe(uninherit=0)
  221. # 'mergeout' signals that stdout and stderr should be merged.
  222. # We do that by using one pipe for both of them.
  223. if mergeout:
  224. c_hstderr = HANDLE()
  225. if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
  226. GetCurrentProcess(), ctypes.byref(c_hstderr),
  227. 0, True, DUPLICATE_SAME_ACCESS):
  228. raise ctypes.WinError()
  229. else:
  230. p_hstderr, c_hstderr = create_pipe(uninherit=0)
  231. c_hstdin, p_hstdin = create_pipe(uninherit=1)
  232. # Create the process object
  233. piProcInfo = PROCESS_INFORMATION()
  234. siStartInfo = STARTUPINFO()
  235. siStartInfo.cb = ctypes.sizeof(siStartInfo)
  236. siStartInfo.hStdInput = c_hstdin
  237. siStartInfo.hStdOutput = c_hstdout
  238. siStartInfo.hStdError = c_hstderr
  239. siStartInfo.dwFlags = STARTF_USESTDHANDLES
  240. dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
  241. if not CreateProcess(None,
  242. u"cmd.exe /c " + cmd,
  243. None, None, True, dwCreationFlags,
  244. None, None, ctypes.byref(siStartInfo),
  245. ctypes.byref(piProcInfo)):
  246. raise ctypes.WinError()
  247. # Close this process's versions of the child handles
  248. CloseHandle(c_hstdin)
  249. c_hstdin = None
  250. CloseHandle(c_hstdout)
  251. c_hstdout = None
  252. if c_hstderr is not None:
  253. CloseHandle(c_hstderr)
  254. c_hstderr = None
  255. # Transfer ownership of the parent handles to the object
  256. self.hstdin = p_hstdin
  257. p_hstdin = None
  258. self.hstdout = p_hstdout
  259. p_hstdout = None
  260. if not mergeout:
  261. self.hstderr = p_hstderr
  262. p_hstderr = None
  263. self.piProcInfo = piProcInfo
  264. finally:
  265. if p_hstdin:
  266. CloseHandle(p_hstdin)
  267. if c_hstdin:
  268. CloseHandle(c_hstdin)
  269. if p_hstdout:
  270. CloseHandle(p_hstdout)
  271. if c_hstdout:
  272. CloseHandle(c_hstdout)
  273. if p_hstderr:
  274. CloseHandle(p_hstderr)
  275. if c_hstderr:
  276. CloseHandle(c_hstderr)
  277. return self
  278. def _stdin_thread(self, handle, hprocess, func, stdout_func):
  279. exitCode = DWORD()
  280. bytesWritten = DWORD(0)
  281. while True:
  282. #print("stdin thread loop start")
  283. # Get the input string (may be bytes or unicode)
  284. data = func()
  285. # None signals to poll whether the process has exited
  286. if data is None:
  287. #print("checking for process completion")
  288. if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
  289. raise ctypes.WinError()
  290. if exitCode.value != STILL_ACTIVE:
  291. return
  292. # TESTING: Does zero-sized writefile help?
  293. if not WriteFile(handle, "", 0,
  294. ctypes.byref(bytesWritten), None):
  295. raise ctypes.WinError()
  296. continue
  297. #print("\nGot str %s\n" % repr(data), file=sys.stderr)
  298. # Encode the string to the console encoding
  299. if isinstance(data, unicode): #FIXME: Python3
  300. data = data.encode('utf_8')
  301. # What we have now must be a string of bytes
  302. if not isinstance(data, str): #FIXME: Python3
  303. raise RuntimeError("internal stdin function string error")
  304. # An empty string signals EOF
  305. if len(data) == 0:
  306. return
  307. # In a windows console, sometimes the input is echoed,
  308. # but sometimes not. How do we determine when to do this?
  309. stdout_func(data)
  310. # WriteFile may not accept all the data at once.
  311. # Loop until everything is processed
  312. while len(data) != 0:
  313. #print("Calling writefile")
  314. if not WriteFile(handle, data, len(data),
  315. ctypes.byref(bytesWritten), None):
  316. # This occurs at exit
  317. if GetLastError() == ERROR_NO_DATA:
  318. return
  319. raise ctypes.WinError()
  320. #print("Called writefile")
  321. data = data[bytesWritten.value:]
  322. def _stdout_thread(self, handle, func):
  323. # Allocate the output buffer
  324. data = ctypes.create_string_buffer(4096)
  325. while True:
  326. bytesRead = DWORD(0)
  327. if not ReadFile(handle, data, 4096,
  328. ctypes.byref(bytesRead), None):
  329. le = GetLastError()
  330. if le == ERROR_BROKEN_PIPE:
  331. return
  332. else:
  333. raise ctypes.WinError()
  334. # FIXME: Python3
  335. s = data.value[0:bytesRead.value]
  336. #print("\nv: %s" % repr(s), file=sys.stderr)
  337. func(s.decode('utf_8', 'replace'))
  338. def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
  339. """Runs the process, using the provided functions for I/O.
  340. The function stdin_func should return strings whenever a
  341. character or characters become available.
  342. The functions stdout_func and stderr_func are called whenever
  343. something is printed to stdout or stderr, respectively.
  344. These functions are called from different threads (but not
  345. concurrently, because of the GIL).
  346. """
  347. if stdout_func is None and stdin_func is None and stderr_func is None:
  348. return self._run_stdio()
  349. if stderr_func is not None and self.mergeout:
  350. raise RuntimeError("Shell command was initiated with "
  351. "merged stdin/stdout, but a separate stderr_func "
  352. "was provided to the run() method")
  353. # Create a thread for each input/output handle
  354. stdin_thread = None
  355. threads = []
  356. if stdin_func:
  357. stdin_thread = threading.Thread(target=self._stdin_thread,
  358. args=(self.hstdin, self.piProcInfo.hProcess,
  359. stdin_func, stdout_func))
  360. threads.append(threading.Thread(target=self._stdout_thread,
  361. args=(self.hstdout, stdout_func)))
  362. if not self.mergeout:
  363. if stderr_func is None:
  364. stderr_func = stdout_func
  365. threads.append(threading.Thread(target=self._stdout_thread,
  366. args=(self.hstderr, stderr_func)))
  367. # Start the I/O threads and the process
  368. if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
  369. raise ctypes.WinError()
  370. if stdin_thread is not None:
  371. stdin_thread.start()
  372. for thread in threads:
  373. thread.start()
  374. # Wait for the process to complete
  375. if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
  376. WAIT_FAILED:
  377. raise ctypes.WinError()
  378. # Wait for the I/O threads to complete
  379. for thread in threads:
  380. thread.join()
  381. # Wait for the stdin thread to complete
  382. if stdin_thread is not None:
  383. stdin_thread.join()
  384. def _stdin_raw_nonblock(self):
  385. """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
  386. # WARNING: This is experimental, and produces inconsistent results.
  387. # It's possible for the handle not to be appropriate for use
  388. # with WaitForSingleObject, among other things.
  389. handle = msvcrt.get_osfhandle(sys.stdin.fileno())
  390. result = WaitForSingleObject(handle, 100)
  391. if result == WAIT_FAILED:
  392. raise ctypes.WinError()
  393. elif result == WAIT_TIMEOUT:
  394. print(".", end='')
  395. return None
  396. else:
  397. data = ctypes.create_string_buffer(256)
  398. bytesRead = DWORD(0)
  399. print('?', end='')
  400. if not ReadFile(handle, data, 256,
  401. ctypes.byref(bytesRead), None):
  402. raise ctypes.WinError()
  403. # This ensures the non-blocking works with an actual console
  404. # Not checking the error, so the processing will still work with
  405. # other handle types
  406. FlushConsoleInputBuffer(handle)
  407. data = data.value
  408. data = data.replace('\r\n', '\n')
  409. data = data.replace('\r', '\n')
  410. print(repr(data) + " ", end='')
  411. return data
  412. def _stdin_raw_block(self):
  413. """Use a blocking stdin read"""
  414. # The big problem with the blocking read is that it doesn't
  415. # exit when it's supposed to in all contexts. An extra
  416. # key-press may be required to trigger the exit.
  417. try:
  418. data = sys.stdin.read(1)
  419. data = data.replace('\r', '\n')
  420. return data
  421. except WindowsError as we:
  422. if we.winerror == ERROR_NO_DATA:
  423. # This error occurs when the pipe is closed
  424. return None
  425. else:
  426. # Otherwise let the error propagate
  427. raise we
  428. def _stdout_raw(self, s):
  429. """Writes the string to stdout"""
  430. print(s, end='', file=sys.stdout)
  431. sys.stdout.flush()
  432. def _stderr_raw(self, s):
  433. """Writes the string to stdout"""
  434. print(s, end='', file=sys.stderr)
  435. sys.stderr.flush()
  436. def _run_stdio(self):
  437. """Runs the process using the system standard I/O.
  438. IMPORTANT: stdin needs to be asynchronous, so the Python
  439. sys.stdin object is not used. Instead,
  440. msvcrt.kbhit/getwch are used asynchronously.
  441. """
  442. # Disable Line and Echo mode
  443. #lpMode = DWORD()
  444. #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
  445. #if GetConsoleMode(handle, ctypes.byref(lpMode)):
  446. # set_console_mode = True
  447. # if not SetConsoleMode(handle, lpMode.value &
  448. # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
  449. # raise ctypes.WinError()
  450. if self.mergeout:
  451. return self.run(stdout_func = self._stdout_raw,
  452. stdin_func = self._stdin_raw_block)
  453. else:
  454. return self.run(stdout_func = self._stdout_raw,
  455. stdin_func = self._stdin_raw_block,
  456. stderr_func = self._stderr_raw)
  457. # Restore the previous console mode
  458. #if set_console_mode:
  459. # if not SetConsoleMode(handle, lpMode.value):
  460. # raise ctypes.WinError()
  461. def __exit__(self, exc_type, exc_value, traceback):
  462. if self.hstdin:
  463. CloseHandle(self.hstdin)
  464. self.hstdin = None
  465. if self.hstdout:
  466. CloseHandle(self.hstdout)
  467. self.hstdout = None
  468. if self.hstderr:
  469. CloseHandle(self.hstderr)
  470. self.hstderr = None
  471. if self.piProcInfo is not None:
  472. CloseHandle(self.piProcInfo.hProcess)
  473. CloseHandle(self.piProcInfo.hThread)
  474. self.piProcInfo = None
  475. def system(cmd):
  476. """Win32 version of os.system() that works with network shares.
  477. Note that this implementation returns None, as meant for use in IPython.
  478. Parameters
  479. ----------
  480. cmd : str
  481. A command to be executed in the system shell.
  482. Returns
  483. -------
  484. None : we explicitly do NOT return the subprocess status code, as this
  485. utility is meant to be used extensively in IPython, where any return value
  486. would trigger :func:`sys.displayhook` calls.
  487. """
  488. with AvoidUNCPath() as path:
  489. if path is not None:
  490. cmd = '"pushd %s &&"%s' % (path, cmd)
  491. with Win32ShellCommandController(cmd) as scc:
  492. scc.run()
  493. if __name__ == "__main__":
  494. print("Test starting!")
  495. #system("cmd")
  496. system("python -i")
  497. print("Test finished!")