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