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