_process_win32.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Windows-specific implementation of process utilities.
  2. This file is only meant to be imported by process.py, not by end-users.
  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. #-----------------------------------------------------------------------------
  11. # Imports
  12. #-----------------------------------------------------------------------------
  13. # stdlib
  14. import os
  15. import sys
  16. import ctypes
  17. import time
  18. from ctypes import c_int, POINTER
  19. from ctypes.wintypes import LPCWSTR, HLOCAL
  20. from subprocess import STDOUT, TimeoutExpired
  21. from threading import Thread
  22. # our own imports
  23. from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
  24. from . import py3compat
  25. from .encoding import DEFAULT_ENCODING
  26. #-----------------------------------------------------------------------------
  27. # Function definitions
  28. #-----------------------------------------------------------------------------
  29. class AvoidUNCPath(object):
  30. """A context manager to protect command execution from UNC paths.
  31. In the Win32 API, commands can't be invoked with the cwd being a UNC path.
  32. This context manager temporarily changes directory to the 'C:' drive on
  33. entering, and restores the original working directory on exit.
  34. The context manager returns the starting working directory *if* it made a
  35. change and None otherwise, so that users can apply the necessary adjustment
  36. to their system calls in the event of a change.
  37. Examples
  38. --------
  39. ::
  40. cmd = 'dir'
  41. with AvoidUNCPath() as path:
  42. if path is not None:
  43. cmd = '"pushd %s &&"%s' % (path, cmd)
  44. os.system(cmd)
  45. """
  46. def __enter__(self):
  47. self.path = os.getcwd()
  48. self.is_unc_path = self.path.startswith(r"\\")
  49. if self.is_unc_path:
  50. # change to c drive (as cmd.exe cannot handle UNC addresses)
  51. os.chdir("C:")
  52. return self.path
  53. else:
  54. # We return None to signal that there was no change in the working
  55. # directory
  56. return None
  57. def __exit__(self, exc_type, exc_value, traceback):
  58. if self.is_unc_path:
  59. os.chdir(self.path)
  60. def _system_body(p):
  61. """Callback for _system."""
  62. enc = DEFAULT_ENCODING
  63. def stdout_read():
  64. for line in read_no_interrupt(p.stdout).splitlines():
  65. line = line.decode(enc, 'replace')
  66. print(line, file=sys.stdout)
  67. def stderr_read():
  68. for line in read_no_interrupt(p.stderr).splitlines():
  69. line = line.decode(enc, 'replace')
  70. print(line, file=sys.stderr)
  71. Thread(target=stdout_read).start()
  72. Thread(target=stderr_read).start()
  73. # Wait to finish for returncode. Unfortunately, Python has a bug where
  74. # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
  75. # a loop instead of just doing `return p.wait()`.
  76. while True:
  77. result = p.poll()
  78. if result is None:
  79. time.sleep(0.01)
  80. else:
  81. return result
  82. def system(cmd):
  83. """Win32 version of os.system() that works with network shares.
  84. Note that this implementation returns None, as meant for use in IPython.
  85. Parameters
  86. ----------
  87. cmd : str or list
  88. A command to be executed in the system shell.
  89. Returns
  90. -------
  91. int : child process' exit code.
  92. """
  93. # The controller provides interactivity with both
  94. # stdin and stdout
  95. #import _process_win32_controller
  96. #_process_win32_controller.system(cmd)
  97. with AvoidUNCPath() as path:
  98. if path is not None:
  99. cmd = '"pushd %s &&"%s' % (path, cmd)
  100. return process_handler(cmd, _system_body)
  101. def getoutput(cmd):
  102. """Return standard output of executing cmd in a shell.
  103. Accepts the same arguments as os.system().
  104. Parameters
  105. ----------
  106. cmd : str or list
  107. A command to be executed in the system shell.
  108. Returns
  109. -------
  110. stdout : str
  111. """
  112. with AvoidUNCPath() as path:
  113. if path is not None:
  114. cmd = '"pushd %s &&"%s' % (path, cmd)
  115. out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
  116. if out is None:
  117. out = b''
  118. return py3compat.decode(out)
  119. try:
  120. CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
  121. CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
  122. CommandLineToArgvW.restype = POINTER(LPCWSTR)
  123. LocalFree = ctypes.windll.kernel32.LocalFree
  124. LocalFree.res_type = HLOCAL
  125. LocalFree.arg_types = [HLOCAL]
  126. def arg_split(commandline, posix=False, strict=True):
  127. """Split a command line's arguments in a shell-like manner.
  128. This is a special version for windows that use a ctypes call to CommandLineToArgvW
  129. to do the argv splitting. The posix parameter is ignored.
  130. If strict=False, process_common.arg_split(...strict=False) is used instead.
  131. """
  132. #CommandLineToArgvW returns path to executable if called with empty string.
  133. if commandline.strip() == "":
  134. return []
  135. if not strict:
  136. # not really a cl-arg, fallback on _process_common
  137. return py_arg_split(commandline, posix=posix, strict=strict)
  138. argvn = c_int()
  139. result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
  140. result_array_type = LPCWSTR * argvn.value
  141. result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
  142. retval = LocalFree(result_pointer)
  143. return result
  144. except AttributeError:
  145. arg_split = py_arg_split
  146. def check_pid(pid):
  147. # OpenProcess returns 0 if no such process (of ours) exists
  148. # positive int otherwise
  149. return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))