blob: ef95d6f8701c4c7634782e8729e89fab1c07dd44 [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
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000176 self.stdin_is_void = False
177 self.stdout_is_void = False
178 self.stderr_is_void = False
179
180 if kwargs.get('stdin') is VOID:
181 kwargs['stdin'] = open(os.devnull, 'r')
182 self.stdin_is_void = True
183
184 for stream in ('stdout', 'stderr'):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000185 if kwargs.get(stream) in (VOID, os.devnull):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000186 kwargs[stream] = open(os.devnull, 'w')
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000187 setattr(self, stream + '_is_void', True)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000188 if callable(kwargs.get(stream)):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000189 setattr(self, stream + '_cb', kwargs[stream])
190 kwargs[stream] = PIPE
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000191
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000192 self.start = time.time()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000193 self.timeout = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000194 self.shell = kwargs.get('shell', None)
maruel@chromium.org14e37ad2011-11-30 20:26:16 +0000195 # Silence pylint on MacOSX
196 self.returncode = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000197
maruel@google.comef77f9e2011-11-24 15:24:02 +0000198 try:
199 super(Popen, self).__init__(args, **kwargs)
200 except OSError, e:
201 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
202 # Convert fork() emulation failure into a CygwinRebaseError().
203 raise CygwinRebaseError(
204 e.errno,
205 args,
206 kwargs.get('cwd'),
207 None,
208 'Visit '
209 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
210 'to learn how to fix this error; you need to rebase your cygwin '
211 'dlls')
212 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
213 # through
214 raise
maruel@chromium.org4860f052011-03-25 20:34:38 +0000215
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000216 def _tee_threads(self, input): # pylint: disable=W0622
217 """Does I/O for a process's pipes using threads.
218
219 It's the simplest and slowest implementation. Expect very slow behavior.
220
221 If there is a callback and it doesn't keep up with the calls, the timeout
222 effectiveness will be delayed accordingly.
223 """
224 # Queue of either of <threadname> when done or (<threadname>, data). In
225 # theory we would like to limit to ~64kb items to not cause large memory
226 # usage when the callback blocks. It is not done because it slows down
227 # processing on OSX10.6 by a factor of 2x, making it even slower than
228 # Windows! Revisit this decision if it becomes a problem, e.g. crash
229 # because of memory exhaustion.
230 queue = Queue.Queue()
231 done = threading.Event()
232
233 def write_stdin():
234 try:
235 stdin_io = cStringIO.StringIO(input)
236 while True:
237 data = stdin_io.read(1024)
238 if data:
239 self.stdin.write(data)
240 else:
241 self.stdin.close()
242 break
243 finally:
244 queue.put('stdin')
245
246 def _queue_pipe_read(pipe, name):
247 """Queues characters read from a pipe into a queue."""
248 try:
249 while True:
250 data = pipe.read(1)
251 if not data:
252 break
253 queue.put((name, data))
254 finally:
255 queue.put(name)
256
257 def timeout_fn():
258 try:
259 done.wait(self.timeout)
260 finally:
261 queue.put('timeout')
262
263 def wait_fn():
264 try:
265 self.wait()
266 finally:
267 queue.put('wait')
268
269 # Starts up to 5 threads:
270 # Wait for the process to quit
271 # Read stdout
272 # Read stderr
273 # Write stdin
274 # Timeout
275 threads = {
276 'wait': threading.Thread(target=wait_fn),
277 }
278 if self.timeout is not None:
279 threads['timeout'] = threading.Thread(target=timeout_fn)
280 if self.stdout_cb:
281 threads['stdout'] = threading.Thread(
282 target=_queue_pipe_read, args=(self.stdout, 'stdout'))
283 if self.stderr_cb:
284 threads['stderr'] = threading.Thread(
285 target=_queue_pipe_read, args=(self.stderr, 'stderr'))
286 if input:
287 threads['stdin'] = threading.Thread(target=write_stdin)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000288 elif self.stdin:
289 # Pipe but no input, make sure it's closed.
290 self.stdin.close()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000291 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
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000349 # When the pipe fills up, it would deadlock this process.
350 if self.stdout and not self.stdout_cb and not self.stdout_is_void:
351 stdout = []
352 self.stdout_cb = stdout.append
353 if self.stderr and not self.stderr_cb and not self.stderr_is_void:
354 stderr = []
355 self.stderr_cb = stderr.append
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000356 self._tee_threads(input)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000357 if stdout is not None:
358 stdout = ''.join(stdout)
359 stderr = None
360 if stderr is not None:
361 stderr = ''.join(stderr)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000362 return (stdout, stderr)
363
maruel@chromium.org4860f052011-03-25 20:34:38 +0000364
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000365def communicate(args, timeout=None, **kwargs):
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000366 """Wraps subprocess.Popen().communicate() and add timeout support.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000367
maruel@chromium.org421982f2011-04-01 17:38:06 +0000368 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000369
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000370 - The process will be killed after |timeout| seconds and returncode set to
371 TIMED_OUT.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000372 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000373 """
374 stdin = kwargs.pop('stdin', None)
375 if stdin is not None:
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000376 if isinstance(stdin, basestring):
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000377 # When stdin is passed as an argument, use it as the actual input data and
378 # set the Popen() parameter accordingly.
379 kwargs['stdin'] = PIPE
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000380 else:
381 kwargs['stdin'] = stdin
382 stdin = None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000383
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000384 proc = Popen(args, **kwargs)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000385 if stdin:
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000386 return proc.communicate(stdin, timeout), proc.returncode
387 else:
388 return proc.communicate(None, timeout), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000389
390
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000391def call(args, **kwargs):
392 """Emulates subprocess.call().
393
394 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000395 In no case they can be returned since no code path raises
396 subprocess2.CalledProcessError.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000397 """
398 if kwargs.get('stdout') == PIPE:
399 kwargs['stdout'] = VOID
400 if kwargs.get('stderr') == PIPE:
401 kwargs['stderr'] = VOID
402 return communicate(args, **kwargs)[1]
403
404
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000405def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000406 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000407
maruel@chromium.org421982f2011-04-01 17:38:06 +0000408 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000409 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000410 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000411 if returncode:
412 raise CalledProcessError(
413 returncode, args, kwargs.get('cwd'), out[0], out[1])
414 return out
415
416
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000417def check_call(args, **kwargs):
418 """Emulate subprocess.check_call()."""
419 check_call_out(args, **kwargs)
420 return 0
421
422
maruel@chromium.org4860f052011-03-25 20:34:38 +0000423def capture(args, **kwargs):
424 """Captures stdout of a process call and returns it.
425
maruel@chromium.org421982f2011-04-01 17:38:06 +0000426 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000427
maruel@chromium.org421982f2011-04-01 17:38:06 +0000428 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000429 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000430 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000431 kwargs.setdefault('stdin', VOID)
432
433 # Like check_output, deny the caller from using stdout arg.
434 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000435
436
437def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000438 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000439
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000440 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000441
maruel@chromium.org421982f2011-04-01 17:38:06 +0000442 - Throws if return code is not 0.
443 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000444 - Blocks stdin by default if not specified since no output will be visible.
445 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000446 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000447 kwargs.setdefault('stdin', VOID)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000448 if 'stdout' in kwargs:
449 raise ValueError('stdout argument not allowed, it will be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000450 return check_call_out(args, stdout=PIPE, **kwargs)[0]