##// END OF EJS Templates
Attempt to make Popen.wait() interruptible.
Itamar Turner-Trauring -
Show More
@@ -1,191 +1,197 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 # stdlib
18 18 import os
19 19 import sys
20 20 import ctypes
21 21
22 22 from ctypes import c_int, POINTER
23 23 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from subprocess import STDOUT
24 from subprocess import STDOUT, TimeoutExpired
25 25
26 26 # our own imports
27 27 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
28 28 from . import py3compat
29 29 from .encoding import DEFAULT_ENCODING
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Function definitions
33 33 #-----------------------------------------------------------------------------
34 34
35 35 class AvoidUNCPath(object):
36 36 """A context manager to protect command execution from UNC paths.
37 37
38 38 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
39 39 This context manager temporarily changes directory to the 'C:' drive on
40 40 entering, and restores the original working directory on exit.
41 41
42 42 The context manager returns the starting working directory *if* it made a
43 43 change and None otherwise, so that users can apply the necessary adjustment
44 44 to their system calls in the event of a change.
45 45
46 46 Examples
47 47 --------
48 48 ::
49 49 cmd = 'dir'
50 50 with AvoidUNCPath() as path:
51 51 if path is not None:
52 52 cmd = '"pushd %s &&"%s' % (path, cmd)
53 53 os.system(cmd)
54 54 """
55 55 def __enter__(self):
56 56 self.path = os.getcwd()
57 57 self.is_unc_path = self.path.startswith(r"\\")
58 58 if self.is_unc_path:
59 59 # change to c drive (as cmd.exe cannot handle UNC addresses)
60 60 os.chdir("C:")
61 61 return self.path
62 62 else:
63 63 # We return None to signal that there was no change in the working
64 64 # directory
65 65 return None
66 66
67 67 def __exit__(self, exc_type, exc_value, traceback):
68 68 if self.is_unc_path:
69 69 os.chdir(self.path)
70 70
71 71
72 72 def _find_cmd(cmd):
73 73 """Find the full path to a .bat or .exe using the win32api module."""
74 74 try:
75 75 from win32api import SearchPath
76 76 except ImportError:
77 77 raise ImportError('you need to have pywin32 installed for this to work')
78 78 else:
79 79 PATH = os.environ['PATH']
80 80 extensions = ['.exe', '.com', '.bat', '.py']
81 81 path = None
82 82 for ext in extensions:
83 83 try:
84 84 path = SearchPath(PATH, cmd, ext)[0]
85 85 except:
86 86 pass
87 87 if path is None:
88 88 raise OSError("command %r not found" % cmd)
89 89 else:
90 90 return path
91 91
92 92
93 93 def _system_body(p):
94 94 """Callback for _system."""
95 95 enc = DEFAULT_ENCODING
96 96 for line in read_no_interrupt(p.stdout).splitlines():
97 97 line = line.decode(enc, 'replace')
98 98 print(line, file=sys.stdout)
99 99 for line in read_no_interrupt(p.stderr).splitlines():
100 100 line = line.decode(enc, 'replace')
101 101 print(line, file=sys.stderr)
102 102
103 # Wait to finish for returncode
104 return p.wait()
103 # Wait to finish for returncode. Unfortunately, Python has a bug where
104 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
105 # a loop instead of just doing `return p.wait()`.
106 while True:
107 try:
108 return p.wait(0.01)
109 except TimeoutExpired:
110 pass
105 111
106 112
107 113 def system(cmd):
108 114 """Win32 version of os.system() that works with network shares.
109 115
110 116 Note that this implementation returns None, as meant for use in IPython.
111 117
112 118 Parameters
113 119 ----------
114 120 cmd : str or list
115 121 A command to be executed in the system shell.
116 122
117 123 Returns
118 124 -------
119 125 None : we explicitly do NOT return the subprocess status code, as this
120 126 utility is meant to be used extensively in IPython, where any return value
121 127 would trigger :func:`sys.displayhook` calls.
122 128 """
123 129 # The controller provides interactivity with both
124 130 # stdin and stdout
125 131 #import _process_win32_controller
126 132 #_process_win32_controller.system(cmd)
127 133
128 134 with AvoidUNCPath() as path:
129 135 if path is not None:
130 136 cmd = '"pushd %s &&"%s' % (path, cmd)
131 137 return process_handler(cmd, _system_body)
132 138
133 139 def getoutput(cmd):
134 140 """Return standard output of executing cmd in a shell.
135 141
136 142 Accepts the same arguments as os.system().
137 143
138 144 Parameters
139 145 ----------
140 146 cmd : str or list
141 147 A command to be executed in the system shell.
142 148
143 149 Returns
144 150 -------
145 151 stdout : str
146 152 """
147 153
148 154 with AvoidUNCPath() as path:
149 155 if path is not None:
150 156 cmd = '"pushd %s &&"%s' % (path, cmd)
151 157 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
152 158
153 159 if out is None:
154 160 out = b''
155 161 return py3compat.decode(out)
156 162
157 163 try:
158 164 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
159 165 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
160 166 CommandLineToArgvW.restype = POINTER(LPCWSTR)
161 167 LocalFree = ctypes.windll.kernel32.LocalFree
162 168 LocalFree.res_type = HLOCAL
163 169 LocalFree.arg_types = [HLOCAL]
164 170
165 171 def arg_split(commandline, posix=False, strict=True):
166 172 """Split a command line's arguments in a shell-like manner.
167 173
168 174 This is a special version for windows that use a ctypes call to CommandLineToArgvW
169 175 to do the argv splitting. The posix parameter is ignored.
170 176
171 177 If strict=False, process_common.arg_split(...strict=False) is used instead.
172 178 """
173 179 #CommandLineToArgvW returns path to executable if called with empty string.
174 180 if commandline.strip() == "":
175 181 return []
176 182 if not strict:
177 183 # not really a cl-arg, fallback on _process_common
178 184 return py_arg_split(commandline, posix=posix, strict=strict)
179 185 argvn = c_int()
180 186 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
181 187 result_array_type = LPCWSTR * argvn.value
182 188 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
183 189 retval = LocalFree(result_pointer)
184 190 return result
185 191 except AttributeError:
186 192 arg_split = py_arg_split
187 193
188 194 def check_pid(pid):
189 195 # OpenProcess returns 0 if no such process (of ours) exists
190 196 # positive int otherwise
191 197 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
General Comments 0
You need to be logged in to leave comments. Login now