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