blob: 043194f47c5690a12a72c2d02c3ec9c06cd99650 [file] [log] [blame]
[email protected]4860f052011-03-25 20:34:381# coding=utf8
[email protected]4f6852c2012-04-20 20:39:202# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]4860f052011-03-25 20:34:383# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Collection of subprocess wrapper functions.
6
7In theory you shouldn't need anything else in subprocess, or this module failed.
8"""
9
[email protected]94c712f2011-12-01 15:04:5710import cStringIO
[email protected]1d9f6292011-04-07 14:15:3611import errno
[email protected]4860f052011-03-25 20:34:3812import logging
13import os
[email protected]94c712f2011-12-01 15:04:5714import Queue
[email protected]4860f052011-03-25 20:34:3815import subprocess
16import sys
[email protected]4860f052011-03-25 20:34:3817import time
18import threading
19
[email protected]a8e81632011-12-01 00:35:2420
[email protected]4860f052011-03-25 20:34:3821# Constants forwarded from subprocess.
22PIPE = subprocess.PIPE
23STDOUT = subprocess.STDOUT
[email protected]421982f2011-04-01 17:38:0624# Sends stdout or stderr to os.devnull.
[email protected]0d5ef242011-04-18 13:52:5825VOID = object()
[email protected]1d9f6292011-04-07 14:15:3626# Error code when a process was killed because it timed out.
27TIMED_OUT = -2001
[email protected]4860f052011-03-25 20:34:3828
29# Globals.
30# Set to True if you somehow need to disable this hack.
31SUBPROCESS_CLEANUP_HACKED = False
32
33
34class CalledProcessError(subprocess.CalledProcessError):
35 """Augment the standard exception with more data."""
36 def __init__(self, returncode, cmd, cwd, stdout, stderr):
37 super(CalledProcessError, self).__init__(returncode, cmd)
38 self.stdout = stdout
39 self.stderr = stderr
40 self.cwd = cwd
41
42 def __str__(self):
43 out = 'Command %s returned non-zero exit status %s' % (
44 ' '.join(self.cmd), self.returncode)
45 if self.cwd:
46 out += ' in ' + self.cwd
47 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
48
49
[email protected]1d9f6292011-04-07 14:15:3650class CygwinRebaseError(CalledProcessError):
51 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
52
53
[email protected]fb3d3242011-04-01 14:03:0854## Utility functions
55
56
57def kill_pid(pid):
58 """Kills a process by its process id."""
59 try:
60 # Unable to import 'module'
[email protected]c98c0c52011-04-06 13:39:4361 # pylint: disable=E1101,F0401
[email protected]fb3d3242011-04-01 14:03:0862 import signal
63 return os.kill(pid, signal.SIGKILL)
64 except ImportError:
65 pass
66
67
68def kill_win(process):
69 """Kills a process with its windows handle.
70
71 Has no effect on other platforms.
72 """
73 try:
74 # Unable to import 'module'
75 # pylint: disable=F0401
76 import win32process
77 # Access to a protected member _handle of a client class
78 # pylint: disable=W0212
79 return win32process.TerminateProcess(process._handle, -1)
80 except ImportError:
81 pass
82
83
84def add_kill():
85 """Adds kill() method to subprocess.Popen for python <2.6"""
86 if hasattr(subprocess.Popen, 'kill'):
87 return
88
89 if sys.platform == 'win32':
90 subprocess.Popen.kill = kill_win
91 else:
92 subprocess.Popen.kill = lambda process: kill_pid(process.pid)
93
94
[email protected]4860f052011-03-25 20:34:3895def hack_subprocess():
96 """subprocess functions may throw exceptions when used in multiple threads.
97
98 See http://bugs.python.org/issue1731717 for more information.
99 """
100 global SUBPROCESS_CLEANUP_HACKED
101 if not SUBPROCESS_CLEANUP_HACKED and threading.activeCount() != 1:
102 # Only hack if there is ever multiple threads.
103 # There is no point to leak with only one thread.
104 subprocess._cleanup = lambda: None
105 SUBPROCESS_CLEANUP_HACKED = True
106
107
108def get_english_env(env):
109 """Forces LANG and/or LANGUAGE to be English.
110
111 Forces encoding to utf-8 for subprocesses.
112
113 Returns None if it is unnecessary.
114 """
[email protected]c98c0c52011-04-06 13:39:43115 if sys.platform == 'win32':
116 return None
[email protected]4860f052011-03-25 20:34:38117 env = env or os.environ
118
119 # Test if it is necessary at all.
120 is_english = lambda name: env.get(name, 'en').startswith('en')
121
122 if is_english('LANG') and is_english('LANGUAGE'):
123 return None
124
125 # Requires modifications.
126 env = env.copy()
127 def fix_lang(name):
128 if not is_english(name):
129 env[name] = 'en_US.UTF-8'
130 fix_lang('LANG')
131 fix_lang('LANGUAGE')
132 return env
133
134
[email protected]ef77f9e2011-11-24 15:24:02135class Popen(subprocess.Popen):
[email protected]57bf78d2011-09-08 18:57:33136 """Wraps subprocess.Popen() with various workarounds.
[email protected]4860f052011-03-25 20:34:38137
[email protected]421982f2011-04-01 17:38:06138 - Forces English output since it's easier to parse the stdout if it is always
139 in English.
140 - Sets shell=True on windows by default. You can override this by forcing
141 shell parameter to a value.
142 - Adds support for VOID to not buffer when not needed.
[email protected]dd9837f2011-11-30 01:55:22143 - Adds self.start property.
[email protected]4860f052011-03-25 20:34:38144
[email protected]57bf78d2011-09-08 18:57:33145 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
146 exceptions generated by cygwin when it fails trying to emulate fork().
[email protected]4860f052011-03-25 20:34:38147 """
[email protected]ef77f9e2011-11-24 15:24:02148 def __init__(self, args, **kwargs):
149 # Make sure we hack subprocess if necessary.
150 hack_subprocess()
151 add_kill()
[email protected]4860f052011-03-25 20:34:38152
[email protected]ef77f9e2011-11-24 15:24:02153 env = get_english_env(kwargs.get('env'))
154 if env:
155 kwargs['env'] = env
156 if kwargs.get('shell') is None:
157 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
158 # the executable, but shell=True makes subprocess on Linux fail when it's
159 # called with a list because it only tries to execute the first item in
160 # the list.
161 kwargs['shell'] = bool(sys.platform=='win32')
[email protected]4860f052011-03-25 20:34:38162
[email protected]ef77f9e2011-11-24 15:24:02163 if isinstance(args, basestring):
164 tmp_str = args
165 elif isinstance(args, (list, tuple)):
166 tmp_str = ' '.join(args)
167 else:
168 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
169 if kwargs.get('cwd', None):
170 tmp_str += '; cwd=%s' % kwargs['cwd']
171 logging.debug(tmp_str)
[email protected]421982f2011-04-01 17:38:06172
[email protected]94c712f2011-12-01 15:04:57173 self.stdout_cb = None
174 self.stderr_cb = None
[email protected]740a6c02011-12-05 23:46:44175 self.stdin_is_void = False
176 self.stdout_is_void = False
177 self.stderr_is_void = False
178
179 if kwargs.get('stdin') is VOID:
180 kwargs['stdin'] = open(os.devnull, 'r')
181 self.stdin_is_void = True
182
183 for stream in ('stdout', 'stderr'):
[email protected]ef77f9e2011-11-24 15:24:02184 if kwargs.get(stream) in (VOID, os.devnull):
[email protected]ef77f9e2011-11-24 15:24:02185 kwargs[stream] = open(os.devnull, 'w')
[email protected]740a6c02011-12-05 23:46:44186 setattr(self, stream + '_is_void', True)
[email protected]94c712f2011-12-01 15:04:57187 if callable(kwargs.get(stream)):
[email protected]94c712f2011-12-01 15:04:57188 setattr(self, stream + '_cb', kwargs[stream])
189 kwargs[stream] = PIPE
[email protected]1d9f6292011-04-07 14:15:36190
[email protected]dd9837f2011-11-30 01:55:22191 self.start = time.time()
[email protected]94c712f2011-12-01 15:04:57192 self.timeout = None
[email protected]a8e81632011-12-01 00:35:24193 self.shell = kwargs.get('shell', None)
[email protected]14e37ad2011-11-30 20:26:16194 # Silence pylint on MacOSX
195 self.returncode = None
[email protected]a8e81632011-12-01 00:35:24196
[email protected]ef77f9e2011-11-24 15:24:02197 try:
198 super(Popen, self).__init__(args, **kwargs)
199 except OSError, e:
200 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
201 # Convert fork() emulation failure into a CygwinRebaseError().
202 raise CygwinRebaseError(
203 e.errno,
204 args,
205 kwargs.get('cwd'),
206 None,
207 'Visit '
208 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
209 'to learn how to fix this error; you need to rebase your cygwin '
210 'dlls')
211 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
212 # through
213 raise
[email protected]4860f052011-03-25 20:34:38214
[email protected]94c712f2011-12-01 15:04:57215 def _tee_threads(self, input): # pylint: disable=W0622
216 """Does I/O for a process's pipes using threads.
217
218 It's the simplest and slowest implementation. Expect very slow behavior.
219
220 If there is a callback and it doesn't keep up with the calls, the timeout
221 effectiveness will be delayed accordingly.
222 """
223 # Queue of either of <threadname> when done or (<threadname>, data). In
224 # theory we would like to limit to ~64kb items to not cause large memory
225 # usage when the callback blocks. It is not done because it slows down
226 # processing on OSX10.6 by a factor of 2x, making it even slower than
227 # Windows! Revisit this decision if it becomes a problem, e.g. crash
228 # because of memory exhaustion.
229 queue = Queue.Queue()
230 done = threading.Event()
231
232 def write_stdin():
233 try:
234 stdin_io = cStringIO.StringIO(input)
235 while True:
236 data = stdin_io.read(1024)
237 if data:
238 self.stdin.write(data)
239 else:
240 self.stdin.close()
241 break
242 finally:
243 queue.put('stdin')
244
245 def _queue_pipe_read(pipe, name):
246 """Queues characters read from a pipe into a queue."""
247 try:
248 while True:
249 data = pipe.read(1)
250 if not data:
251 break
252 queue.put((name, data))
253 finally:
254 queue.put(name)
255
256 def timeout_fn():
257 try:
258 done.wait(self.timeout)
259 finally:
260 queue.put('timeout')
261
262 def wait_fn():
263 try:
264 self.wait()
265 finally:
266 queue.put('wait')
267
268 # Starts up to 5 threads:
269 # Wait for the process to quit
270 # Read stdout
271 # Read stderr
272 # Write stdin
273 # Timeout
274 threads = {
275 'wait': threading.Thread(target=wait_fn),
276 }
277 if self.timeout is not None:
278 threads['timeout'] = threading.Thread(target=timeout_fn)
279 if self.stdout_cb:
280 threads['stdout'] = threading.Thread(
281 target=_queue_pipe_read, args=(self.stdout, 'stdout'))
282 if self.stderr_cb:
283 threads['stderr'] = threading.Thread(
284 target=_queue_pipe_read, args=(self.stderr, 'stderr'))
285 if input:
286 threads['stdin'] = threading.Thread(target=write_stdin)
[email protected]740a6c02011-12-05 23:46:44287 elif self.stdin:
288 # Pipe but no input, make sure it's closed.
289 self.stdin.close()
[email protected]94c712f2011-12-01 15:04:57290 for t in threads.itervalues():
291 t.start()
292
293 timed_out = False
294 try:
295 # This thread needs to be optimized for speed.
296 while threads:
297 item = queue.get()
298 if item[0] is 'stdout':
299 self.stdout_cb(item[1])
300 elif item[0] is 'stderr':
301 self.stderr_cb(item[1])
302 else:
303 # A thread terminated.
304 threads[item].join()
305 del threads[item]
306 if item == 'wait':
307 # Terminate the timeout thread if necessary.
308 done.set()
309 elif item == 'timeout' and not timed_out and self.poll() is None:
310 logging.debug('Timed out after %fs: killing' % self.timeout)
311 self.kill()
312 timed_out = True
313 finally:
314 # Stop the threads.
315 done.set()
316 if 'wait' in threads:
317 # Accelerate things, otherwise it would hang until the child process is
318 # done.
319 logging.debug('Killing child because of an exception')
320 self.kill()
321 # Join threads.
322 for thread in threads.itervalues():
323 thread.join()
324 if timed_out:
325 self.returncode = TIMED_OUT
326
327 def communicate(self, input=None, timeout=None): # pylint: disable=W0221,W0622
328 """Adds timeout and callbacks support.
329
330 Returns (stdout, stderr) like subprocess.Popen().communicate().
331
332 - The process will be killed after |timeout| seconds and returncode set to
333 TIMED_OUT.
334 """
335 self.timeout = timeout
336 if not self.timeout and not self.stdout_cb and not self.stderr_cb:
337 return super(Popen, self).communicate(input)
338
339 if self.timeout and self.shell:
340 raise TypeError(
341 'Using timeout and shell simultaneously will cause a process leak '
342 'since the shell will be killed instead of the child process.')
343
344 stdout = None
345 stderr = None
346 # Convert to a lambda to workaround python's deadlock.
347 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
[email protected]740a6c02011-12-05 23:46:44348 # When the pipe fills up, it would deadlock this process.
349 if self.stdout and not self.stdout_cb and not self.stdout_is_void:
350 stdout = []
351 self.stdout_cb = stdout.append
352 if self.stderr and not self.stderr_cb and not self.stderr_is_void:
353 stderr = []
354 self.stderr_cb = stderr.append
[email protected]94c712f2011-12-01 15:04:57355 self._tee_threads(input)
[email protected]740a6c02011-12-05 23:46:44356 if stdout is not None:
357 stdout = ''.join(stdout)
358 stderr = None
359 if stderr is not None:
360 stderr = ''.join(stderr)
[email protected]94c712f2011-12-01 15:04:57361 return (stdout, stderr)
362
[email protected]4860f052011-03-25 20:34:38363
[email protected]1f063db2011-04-18 19:04:52364def communicate(args, timeout=None, **kwargs):
[email protected]dd9837f2011-11-30 01:55:22365 """Wraps subprocess.Popen().communicate() and add timeout support.
[email protected]4860f052011-03-25 20:34:38366
[email protected]421982f2011-04-01 17:38:06367 Returns ((stdout, stderr), returncode).
[email protected]4860f052011-03-25 20:34:38368
[email protected]1d9f6292011-04-07 14:15:36369 - The process will be killed after |timeout| seconds and returncode set to
370 TIMED_OUT.
[email protected]421982f2011-04-01 17:38:06371 - Automatically passes stdin content as input so do not specify stdin=PIPE.
[email protected]4860f052011-03-25 20:34:38372 """
373 stdin = kwargs.pop('stdin', None)
374 if stdin is not None:
[email protected]740a6c02011-12-05 23:46:44375 if isinstance(stdin, basestring):
[email protected]0d5ef242011-04-18 13:52:58376 # When stdin is passed as an argument, use it as the actual input data and
377 # set the Popen() parameter accordingly.
378 kwargs['stdin'] = PIPE
[email protected]740a6c02011-12-05 23:46:44379 else:
380 kwargs['stdin'] = stdin
381 stdin = None
[email protected]4860f052011-03-25 20:34:38382
[email protected]94c712f2011-12-01 15:04:57383 proc = Popen(args, **kwargs)
[email protected]740a6c02011-12-05 23:46:44384 if stdin:
[email protected]94c712f2011-12-01 15:04:57385 return proc.communicate(stdin, timeout), proc.returncode
386 else:
387 return proc.communicate(None, timeout), proc.returncode
[email protected]4860f052011-03-25 20:34:38388
389
[email protected]1f063db2011-04-18 19:04:52390def call(args, **kwargs):
391 """Emulates subprocess.call().
392
393 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
[email protected]87e6d332011-09-09 19:01:28394 In no case they can be returned since no code path raises
395 subprocess2.CalledProcessError.
[email protected]1f063db2011-04-18 19:04:52396 """
397 if kwargs.get('stdout') == PIPE:
398 kwargs['stdout'] = VOID
399 if kwargs.get('stderr') == PIPE:
400 kwargs['stderr'] = VOID
401 return communicate(args, **kwargs)[1]
402
403
[email protected]0bcd1d32011-04-26 15:55:49404def check_call_out(args, **kwargs):
[email protected]421982f2011-04-01 17:38:06405 """Improved version of subprocess.check_call().
[email protected]4860f052011-03-25 20:34:38406
[email protected]421982f2011-04-01 17:38:06407 Returns (stdout, stderr), unlike subprocess.check_call().
[email protected]4860f052011-03-25 20:34:38408 """
[email protected]1f063db2011-04-18 19:04:52409 out, returncode = communicate(args, **kwargs)
[email protected]4860f052011-03-25 20:34:38410 if returncode:
411 raise CalledProcessError(
412 returncode, args, kwargs.get('cwd'), out[0], out[1])
413 return out
414
415
[email protected]0bcd1d32011-04-26 15:55:49416def check_call(args, **kwargs):
417 """Emulate subprocess.check_call()."""
418 check_call_out(args, **kwargs)
419 return 0
420
421
[email protected]4860f052011-03-25 20:34:38422def capture(args, **kwargs):
423 """Captures stdout of a process call and returns it.
424
[email protected]421982f2011-04-01 17:38:06425 Returns stdout.
[email protected]4860f052011-03-25 20:34:38426
[email protected]421982f2011-04-01 17:38:06427 - Discards returncode.
[email protected]87e6d332011-09-09 19:01:28428 - Blocks stdin by default if not specified since no output will be visible.
[email protected]4860f052011-03-25 20:34:38429 """
[email protected]87e6d332011-09-09 19:01:28430 kwargs.setdefault('stdin', VOID)
431
432 # Like check_output, deny the caller from using stdout arg.
433 return communicate(args, stdout=PIPE, **kwargs)[0][0]
[email protected]4860f052011-03-25 20:34:38434
435
436def check_output(args, **kwargs):
[email protected]0bcd1d32011-04-26 15:55:49437 """Emulates subprocess.check_output().
[email protected]4860f052011-03-25 20:34:38438
[email protected]0bcd1d32011-04-26 15:55:49439 Captures stdout of a process call and returns stdout only.
[email protected]4860f052011-03-25 20:34:38440
[email protected]421982f2011-04-01 17:38:06441 - Throws if return code is not 0.
442 - Works even prior to python 2.7.
[email protected]87e6d332011-09-09 19:01:28443 - Blocks stdin by default if not specified since no output will be visible.
444 - As per doc, "The stdout argument is not allowed as it is used internally."
[email protected]4860f052011-03-25 20:34:38445 """
[email protected]87e6d332011-09-09 19:01:28446 kwargs.setdefault('stdin', VOID)
[email protected]db59bfc2011-11-30 14:03:14447 if 'stdout' in kwargs:
448 raise ValueError('stdout argument not allowed, it will be overridden.')
[email protected]87e6d332011-09-09 19:01:28449 return check_call_out(args, stdout=PIPE, **kwargs)[0]