_process_win32_controller.py 21 KB

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