blob: 3ad804791fcabb0b423be212f31ef16c5d060064 [file] [log] [blame]
maruel@chromium.org4860f052011-03-25 20:34:38 +00001# coding=utf8
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
3# 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
maruel@chromium.org45d8db02011-03-31 20:43:56 +000010from __future__ import with_statement
maruel@chromium.org94c712f2011-12-01 15:04:57 +000011import cStringIO
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000012import errno
maruel@chromium.org4860f052011-03-25 20:34:38 +000013import logging
14import os
maruel@chromium.org94c712f2011-12-01 15:04:57 +000015import Queue
maruel@chromium.org4860f052011-03-25 20:34:38 +000016import subprocess
17import sys
maruel@chromium.org4860f052011-03-25 20:34:38 +000018import time
19import threading
20
maruel@chromium.orga8e81632011-12-01 00:35:24 +000021
maruel@chromium.org4860f052011-03-25 20:34:38 +000022# Constants forwarded from subprocess.
23PIPE = subprocess.PIPE
24STDOUT = subprocess.STDOUT
maruel@chromium.org421982f2011-04-01 17:38:06 +000025# Sends stdout or stderr to os.devnull.
maruel@chromium.org0d5ef242011-04-18 13:52:58 +000026VOID = object()
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000027# Error code when a process was killed because it timed out.
28TIMED_OUT = -2001
maruel@chromium.org4860f052011-03-25 20:34:38 +000029
30# Globals.
31# Set to True if you somehow need to disable this hack.
32SUBPROCESS_CLEANUP_HACKED = False
33
34
35class CalledProcessError(subprocess.CalledProcessError):
36 """Augment the standard exception with more data."""
37 def __init__(self, returncode, cmd, cwd, stdout, stderr):
38 super(CalledProcessError, self).__init__(returncode, cmd)
39 self.stdout = stdout
40 self.stderr = stderr
41 self.cwd = cwd
42
43 def __str__(self):
44 out = 'Command %s returned non-zero exit status %s' % (
45 ' '.join(self.cmd), self.returncode)
46 if self.cwd:
47 out += ' in ' + self.cwd
48 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
49
50
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000051class CygwinRebaseError(CalledProcessError):
52 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
53
54
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000055## Utility functions
56
57
58def kill_pid(pid):
59 """Kills a process by its process id."""
60 try:
61 # Unable to import 'module'
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000062 # pylint: disable=E1101,F0401
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000063 import signal
64 return os.kill(pid, signal.SIGKILL)
65 except ImportError:
66 pass
67
68
69def kill_win(process):
70 """Kills a process with its windows handle.
71
72 Has no effect on other platforms.
73 """
74 try:
75 # Unable to import 'module'
76 # pylint: disable=F0401
77 import win32process
78 # Access to a protected member _handle of a client class
79 # pylint: disable=W0212
80 return win32process.TerminateProcess(process._handle, -1)
81 except ImportError:
82 pass
83
84
85def add_kill():
86 """Adds kill() method to subprocess.Popen for python <2.6"""
87 if hasattr(subprocess.Popen, 'kill'):
88 return
89
90 if sys.platform == 'win32':
91 subprocess.Popen.kill = kill_win
92 else:
93 subprocess.Popen.kill = lambda process: kill_pid(process.pid)
94
95
maruel@chromium.org4860f052011-03-25 20:34:38 +000096def hack_subprocess():
97 """subprocess functions may throw exceptions when used in multiple threads.
98
99 See http://bugs.python.org/issue1731717 for more information.
100 """
101 global SUBPROCESS_CLEANUP_HACKED
102 if not SUBPROCESS_CLEANUP_HACKED and threading.activeCount() != 1:
103 # Only hack if there is ever multiple threads.
104 # There is no point to leak with only one thread.
105 subprocess._cleanup = lambda: None
106 SUBPROCESS_CLEANUP_HACKED = True
107
108
109def get_english_env(env):
110 """Forces LANG and/or LANGUAGE to be English.
111
112 Forces encoding to utf-8 for subprocesses.
113
114 Returns None if it is unnecessary.
115 """
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +0000116 if sys.platform == 'win32':
117 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000118 env = env or os.environ
119
120 # Test if it is necessary at all.
121 is_english = lambda name: env.get(name, 'en').startswith('en')
122
123 if is_english('LANG') and is_english('LANGUAGE'):
124 return None
125
126 # Requires modifications.
127 env = env.copy()
128 def fix_lang(name):
129 if not is_english(name):
130 env[name] = 'en_US.UTF-8'
131 fix_lang('LANG')
132 fix_lang('LANGUAGE')
133 return env
134
135
maruel@google.comef77f9e2011-11-24 15:24:02 +0000136class Popen(subprocess.Popen):
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000137 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000138
maruel@chromium.org421982f2011-04-01 17:38:06 +0000139 - Forces English output since it's easier to parse the stdout if it is always
140 in English.
141 - Sets shell=True on windows by default. You can override this by forcing
142 shell parameter to a value.
143 - Adds support for VOID to not buffer when not needed.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000144 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000145
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000146 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
147 exceptions generated by cygwin when it fails trying to emulate fork().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000148 """
maruel@google.comef77f9e2011-11-24 15:24:02 +0000149 def __init__(self, args, **kwargs):
150 # Make sure we hack subprocess if necessary.
151 hack_subprocess()
152 add_kill()
maruel@chromium.org4860f052011-03-25 20:34:38 +0000153
maruel@google.comef77f9e2011-11-24 15:24:02 +0000154 env = get_english_env(kwargs.get('env'))
155 if env:
156 kwargs['env'] = env
157 if kwargs.get('shell') is None:
158 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
159 # the executable, but shell=True makes subprocess on Linux fail when it's
160 # called with a list because it only tries to execute the first item in
161 # the list.
162 kwargs['shell'] = bool(sys.platform=='win32')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000163
maruel@google.comef77f9e2011-11-24 15:24:02 +0000164 if isinstance(args, basestring):
165 tmp_str = args
166 elif isinstance(args, (list, tuple)):
167 tmp_str = ' '.join(args)
168 else:
169 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
170 if kwargs.get('cwd', None):
171 tmp_str += '; cwd=%s' % kwargs['cwd']
172 logging.debug(tmp_str)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000173
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000174 self.stdout_cb = None
175 self.stderr_cb = None
176 self.stdout_void = False
177 self.stderr_void = False
maruel@google.comef77f9e2011-11-24 15:24:02 +0000178 def fix(stream):
179 if kwargs.get(stream) in (VOID, os.devnull):
180 # Replaces VOID with handle to /dev/null.
181 # Create a temporary file to workaround python's deadlock.
182 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
183 # When the pipe fills up, it will deadlock this process. Using a real
184 # file works around that issue.
185 kwargs[stream] = open(os.devnull, 'w')
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000186 setattr(self, stream + '_void', True)
187 if callable(kwargs.get(stream)):
188 # Callable stdout/stderr should be used only with call() wrappers.
189 setattr(self, stream + '_cb', kwargs[stream])
190 kwargs[stream] = PIPE
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000191
maruel@google.comef77f9e2011-11-24 15:24:02 +0000192 fix('stdout')
193 fix('stderr')
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000194
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000195 self.start = time.time()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000196 self.timeout = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000197 self.shell = kwargs.get('shell', None)
maruel@chromium.org14e37ad2011-11-30 20:26:16 +0000198 # Silence pylint on MacOSX
199 self.returncode = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000200
maruel@google.comef77f9e2011-11-24 15:24:02 +0000201 try:
202 super(Popen, self).__init__(args, **kwargs)
203 except OSError, e:
204 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
205 # Convert fork() emulation failure into a CygwinRebaseError().
206 raise CygwinRebaseError(
207 e.errno,
208 args,
209 kwargs.get('cwd'),
210 None,
211 'Visit '
212 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
213 'to learn how to fix this error; you need to rebase your cygwin '
214 'dlls')
215 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
216 # through
217 raise
maruel@chromium.org4860f052011-03-25 20:34:38 +0000218
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000219 def _tee_threads(self, input): # pylint: disable=W0622
220 """Does I/O for a process's pipes using threads.
221
222 It's the simplest and slowest implementation. Expect very slow behavior.
223
224 If there is a callback and it doesn't keep up with the calls, the timeout
225 effectiveness will be delayed accordingly.
226 """
227 # Queue of either of <threadname> when done or (<threadname>, data). In
228 # theory we would like to limit to ~64kb items to not cause large memory
229 # usage when the callback blocks. It is not done because it slows down
230 # processing on OSX10.6 by a factor of 2x, making it even slower than
231 # Windows! Revisit this decision if it becomes a problem, e.g. crash
232 # because of memory exhaustion.
233 queue = Queue.Queue()
234 done = threading.Event()
235
236 def write_stdin():
237 try:
238 stdin_io = cStringIO.StringIO(input)
239 while True:
240 data = stdin_io.read(1024)
241 if data:
242 self.stdin.write(data)
243 else:
244 self.stdin.close()
245 break
246 finally:
247 queue.put('stdin')
248
249 def _queue_pipe_read(pipe, name):
250 """Queues characters read from a pipe into a queue."""
251 try:
252 while True:
253 data = pipe.read(1)
254 if not data:
255 break
256 queue.put((name, data))
257 finally:
258 queue.put(name)
259
260 def timeout_fn():
261 try:
262 done.wait(self.timeout)
263 finally:
264 queue.put('timeout')
265
266 def wait_fn():
267 try:
268 self.wait()
269 finally:
270 queue.put('wait')
271
272 # Starts up to 5 threads:
273 # Wait for the process to quit
274 # Read stdout
275 # Read stderr
276 # Write stdin
277 # Timeout
278 threads = {
279 'wait': threading.Thread(target=wait_fn),
280 }
281 if self.timeout is not None:
282 threads['timeout'] = threading.Thread(target=timeout_fn)
283 if self.stdout_cb:
284 threads['stdout'] = threading.Thread(
285 target=_queue_pipe_read, args=(self.stdout, 'stdout'))
286 if self.stderr_cb:
287 threads['stderr'] = threading.Thread(
288 target=_queue_pipe_read, args=(self.stderr, 'stderr'))
289 if input:
290 threads['stdin'] = threading.Thread(target=write_stdin)
291 for t in threads.itervalues():
292 t.start()
293
294 timed_out = False
295 try:
296 # This thread needs to be optimized for speed.
297 while threads:
298 item = queue.get()
299 if item[0] is 'stdout':
300 self.stdout_cb(item[1])
301 elif item[0] is 'stderr':
302 self.stderr_cb(item[1])
303 else:
304 # A thread terminated.
305 threads[item].join()
306 del threads[item]
307 if item == 'wait':
308 # Terminate the timeout thread if necessary.
309 done.set()
310 elif item == 'timeout' and not timed_out and self.poll() is None:
311 logging.debug('Timed out after %fs: killing' % self.timeout)
312 self.kill()
313 timed_out = True
314 finally:
315 # Stop the threads.
316 done.set()
317 if 'wait' in threads:
318 # Accelerate things, otherwise it would hang until the child process is
319 # done.
320 logging.debug('Killing child because of an exception')
321 self.kill()
322 # Join threads.
323 for thread in threads.itervalues():
324 thread.join()
325 if timed_out:
326 self.returncode = TIMED_OUT
327
328 def communicate(self, input=None, timeout=None): # pylint: disable=W0221,W0622
329 """Adds timeout and callbacks support.
330
331 Returns (stdout, stderr) like subprocess.Popen().communicate().
332
333 - The process will be killed after |timeout| seconds and returncode set to
334 TIMED_OUT.
335 """
336 self.timeout = timeout
337 if not self.timeout and not self.stdout_cb and not self.stderr_cb:
338 return super(Popen, self).communicate(input)
339
340 if self.timeout and self.shell:
341 raise TypeError(
342 'Using timeout and shell simultaneously will cause a process leak '
343 'since the shell will be killed instead of the child process.')
344
345 stdout = None
346 stderr = None
347 # Convert to a lambda to workaround python's deadlock.
348 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
349 # When the pipe fills up, it will deadlock this process. Using a thread
350 # works around that issue. No need for thread safe function since the call
351 # backs are guaranteed to be called from the main thread.
352 if self.stdout and not self.stdout_cb and not self.stdout_void:
353 stdout = cStringIO.StringIO()
354 self.stdout_cb = stdout.write
355 if self.stderr and not self.stderr_cb and not self.stderr_void:
356 stderr = cStringIO.StringIO()
357 self.stderr_cb = stderr.write
358 self._tee_threads(input)
359 if stdout:
360 stdout = stdout.getvalue()
361 if stderr:
362 stderr = stderr.getvalue()
363 return (stdout, stderr)
364
maruel@chromium.org4860f052011-03-25 20:34:38 +0000365
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000366def communicate(args, timeout=None, **kwargs):
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000367 """Wraps subprocess.Popen().communicate() and add timeout support.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000368
maruel@chromium.org421982f2011-04-01 17:38:06 +0000369 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000370
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000371 - The process will be killed after |timeout| seconds and returncode set to
372 TIMED_OUT.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000373 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000374 """
375 stdin = kwargs.pop('stdin', None)
376 if stdin is not None:
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000377 if stdin is VOID:
378 kwargs['stdin'] = open(os.devnull, 'r')
379 stdin = None
380 else:
maruel@chromium.org39f645f2011-04-21 00:07:53 +0000381 assert isinstance(stdin, basestring)
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000382 # When stdin is passed as an argument, use it as the actual input data and
383 # set the Popen() parameter accordingly.
384 kwargs['stdin'] = PIPE
maruel@chromium.org4860f052011-03-25 20:34:38 +0000385
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000386 proc = Popen(args, **kwargs)
387 if stdin not in (None, VOID):
388 return proc.communicate(stdin, timeout), proc.returncode
389 else:
390 return proc.communicate(None, timeout), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000391
392
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000393def call(args, **kwargs):
394 """Emulates subprocess.call().
395
396 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000397 In no case they can be returned since no code path raises
398 subprocess2.CalledProcessError.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000399 """
400 if kwargs.get('stdout') == PIPE:
401 kwargs['stdout'] = VOID
402 if kwargs.get('stderr') == PIPE:
403 kwargs['stderr'] = VOID
404 return communicate(args, **kwargs)[1]
405
406
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000407def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000408 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000409
maruel@chromium.org421982f2011-04-01 17:38:06 +0000410 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000411 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000412 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000413 if returncode:
414 raise CalledProcessError(
415 returncode, args, kwargs.get('cwd'), out[0], out[1])
416 return out
417
418
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000419def check_call(args, **kwargs):
420 """Emulate subprocess.check_call()."""
421 check_call_out(args, **kwargs)
422 return 0
423
424
maruel@chromium.org4860f052011-03-25 20:34:38 +0000425def capture(args, **kwargs):
426 """Captures stdout of a process call and returns it.
427
maruel@chromium.org421982f2011-04-01 17:38:06 +0000428 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000429
maruel@chromium.org421982f2011-04-01 17:38:06 +0000430 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000431 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000432 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000433 kwargs.setdefault('stdin', VOID)
434
435 # Like check_output, deny the caller from using stdout arg.
436 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000437
438
439def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000440 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000441
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000442 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000443
maruel@chromium.org421982f2011-04-01 17:38:06 +0000444 - Throws if return code is not 0.
445 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000446 - Blocks stdin by default if not specified since no output will be visible.
447 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000448 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000449 kwargs.setdefault('stdin', VOID)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000450 if 'stdout' in kwargs:
451 raise ValueError('stdout argument not allowed, it will be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000452 return check_call_out(args, stdout=PIPE, **kwargs)[0]