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