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