_process_win32.py 6.3 KB

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