blob: e81798613cf6946789841df1dd4fce44417d9897 [file] [log] [blame]
maruel@chromium.org4860f052011-03-25 20:34:38 +00001# coding=utf8
maruel@chromium.org4f6852c2012-04-20 20:39:20 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org4860f052011-03-25 20:34:38 +00003# 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.org94c712f2011-12-01 15:04:57 +000010import cStringIO
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000011import errno
maruel@chromium.org4860f052011-03-25 20:34:38 +000012import logging
13import os
maruel@chromium.org94c712f2011-12-01 15:04:57 +000014import Queue
maruel@chromium.org4860f052011-03-25 20:34:38 +000015import subprocess
16import sys
maruel@chromium.org4860f052011-03-25 20:34:38 +000017import time
18import threading
19
maruel@chromium.orga8e81632011-12-01 00:35:24 +000020
maruel@chromium.org4860f052011-03-25 20:34:38 +000021# Constants forwarded from subprocess.
22PIPE = subprocess.PIPE
23STDOUT = subprocess.STDOUT
maruel@chromium.org421982f2011-04-01 17:38:06 +000024# Sends stdout or stderr to os.devnull.
maruel@chromium.org0d5ef242011-04-18 13:52:58 +000025VOID = object()
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000026# Error code when a process was killed because it timed out.
27TIMED_OUT = -2001
maruel@chromium.org4860f052011-03-25 20:34:38 +000028
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
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000050class CygwinRebaseError(CalledProcessError):
51 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
52
53
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000054## Utility functions
55
56
57def kill_pid(pid):
58 """Kills a process by its process id."""
59 try:
60 # Unable to import 'module'
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000061 # pylint: disable=E1101,F0401
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000062 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
maruel@chromium.org4860f052011-03-25 20:34:38 +000095def 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 """
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +0000115 if sys.platform == 'win32':
116 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000117 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
maruel@google.comef77f9e2011-11-24 15:24:02 +0000135class Popen(subprocess.Popen):
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000136 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000137
maruel@chromium.org421982f2011-04-01 17:38:06 +0000138 - 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.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000143 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000144
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000145 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().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000147 """
maruel@google.comef77f9e2011-11-24 15:24:02 +0000148 def __init__(self, args, **kwargs):
149 # Make sure we hack subprocess if necessary.
150 hack_subprocess()
151 add_kill()
maruel@chromium.org4860f052011-03-25 20:34:38 +0000152
maruel@google.comef77f9e2011-11-24 15:24:02 +0000153 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')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000162
maruel@google.comef77f9e2011-11-24 15:24:02 +0000163 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)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000172
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000173 self.stdout_cb = None
174 self.stderr_cb = None
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000175 self.stdin_is_void = False
176 self.stdout_is_void = False
177 self.stderr_is_void = False
szager@chromium.orge0558e62013-05-02 02:48:51 +0000178 self.cmd_str = tmp_str
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000179
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
szager@chromium.orge0558e62013-05-02 02:48:51 +0000194 self.nag_timer = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000195 self.shell = kwargs.get('shell', None)
maruel@chromium.org14e37ad2011-11-30 20:26:16 +0000196 # Silence pylint on MacOSX
197 self.returncode = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000198
maruel@google.comef77f9e2011-11-24 15:24:02 +0000199 try:
200 super(Popen, self).__init__(args, **kwargs)
201 except OSError, e:
202 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
203 # Convert fork() emulation failure into a CygwinRebaseError().
204 raise CygwinRebaseError(
205 e.errno,
206 args,
207 kwargs.get('cwd'),
208 None,
209 'Visit '
210 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
211 'to learn how to fix this error; you need to rebase your cygwin '
212 'dlls')
213 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
214 # through
215 raise
maruel@chromium.org4860f052011-03-25 20:34:38 +0000216
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000217 def _tee_threads(self, input): # pylint: disable=W0622
218 """Does I/O for a process's pipes using threads.
219
220 It's the simplest and slowest implementation. Expect very slow behavior.
221
222 If there is a callback and it doesn't keep up with the calls, the timeout
223 effectiveness will be delayed accordingly.
224 """
225 # Queue of either of <threadname> when done or (<threadname>, data). In
226 # theory we would like to limit to ~64kb items to not cause large memory
227 # usage when the callback blocks. It is not done because it slows down
228 # processing on OSX10.6 by a factor of 2x, making it even slower than
229 # Windows! Revisit this decision if it becomes a problem, e.g. crash
230 # because of memory exhaustion.
231 queue = Queue.Queue()
232 done = threading.Event()
szager@chromium.orge0558e62013-05-02 02:48:51 +0000233 timer = []
234 last_output = [time.time()] * 2
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000235
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
szager@chromium.orge0558e62013-05-02 02:48:51 +0000256 last_output[0] = time.time()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000257 queue.put((name, data))
258 finally:
259 queue.put(name)
260
szager@chromium.orge0558e62013-05-02 02:48:51 +0000261 def nag_fn():
262 now = time.time()
263 if done.is_set():
264 return
265 if last_output[0] == last_output[1]:
266 logging.warn(' No output for %.0f seconds from command:' % (
267 now - last_output[1]))
268 logging.warn(' %s' % self.cmd_str)
269 # Use 0.1 fudge factor in case:
270 # now ~= last_output[0] + self.nag_timer
271 sleep_time = self.nag_timer + last_output[0] - now - 0.1
272 while sleep_time < 0:
273 sleep_time += self.nag_timer
274 last_output[1] = last_output[0]
275 timer[0] = threading.Timer(sleep_time, nag_fn)
276 timer[0].start()
277
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000278 def timeout_fn():
279 try:
280 done.wait(self.timeout)
281 finally:
282 queue.put('timeout')
283
284 def wait_fn():
285 try:
286 self.wait()
287 finally:
288 queue.put('wait')
289
290 # Starts up to 5 threads:
291 # Wait for the process to quit
292 # Read stdout
293 # Read stderr
294 # Write stdin
295 # Timeout
296 threads = {
297 'wait': threading.Thread(target=wait_fn),
298 }
299 if self.timeout is not None:
300 threads['timeout'] = threading.Thread(target=timeout_fn)
301 if self.stdout_cb:
302 threads['stdout'] = threading.Thread(
303 target=_queue_pipe_read, args=(self.stdout, 'stdout'))
304 if self.stderr_cb:
305 threads['stderr'] = threading.Thread(
306 target=_queue_pipe_read, args=(self.stderr, 'stderr'))
307 if input:
308 threads['stdin'] = threading.Thread(target=write_stdin)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000309 elif self.stdin:
310 # Pipe but no input, make sure it's closed.
311 self.stdin.close()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000312 for t in threads.itervalues():
313 t.start()
314
szager@chromium.orge0558e62013-05-02 02:48:51 +0000315 if self.nag_timer:
316 timer.append(threading.Timer(self.nag_timer, nag_fn))
317 timer[0].start()
318
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000319 timed_out = False
320 try:
321 # This thread needs to be optimized for speed.
322 while threads:
323 item = queue.get()
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000324 if item[0] == 'stdout':
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000325 self.stdout_cb(item[1])
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000326 elif item[0] == 'stderr':
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000327 self.stderr_cb(item[1])
328 else:
329 # A thread terminated.
330 threads[item].join()
331 del threads[item]
332 if item == 'wait':
333 # Terminate the timeout thread if necessary.
334 done.set()
335 elif item == 'timeout' and not timed_out and self.poll() is None:
336 logging.debug('Timed out after %fs: killing' % self.timeout)
337 self.kill()
338 timed_out = True
339 finally:
340 # Stop the threads.
341 done.set()
szager@chromium.orge0558e62013-05-02 02:48:51 +0000342 if timer:
343 timer[0].cancel()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000344 if 'wait' in threads:
345 # Accelerate things, otherwise it would hang until the child process is
346 # done.
347 logging.debug('Killing child because of an exception')
348 self.kill()
349 # Join threads.
350 for thread in threads.itervalues():
351 thread.join()
352 if timed_out:
353 self.returncode = TIMED_OUT
354
szager@chromium.orge0558e62013-05-02 02:48:51 +0000355 # pylint: disable=W0221,W0622
356 def communicate(self, input=None, timeout=None, nag_timer=None):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000357 """Adds timeout and callbacks support.
358
359 Returns (stdout, stderr) like subprocess.Popen().communicate().
360
361 - The process will be killed after |timeout| seconds and returncode set to
362 TIMED_OUT.
szager@chromium.orge0558e62013-05-02 02:48:51 +0000363 - If the subprocess runs for |nag_timer| seconds without producing terminal
364 output, print a warning to stderr.
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000365 """
366 self.timeout = timeout
szager@chromium.orge0558e62013-05-02 02:48:51 +0000367 self.nag_timer = nag_timer
368 if (not self.timeout and not self.nag_timer and
369 not self.stdout_cb and not self.stderr_cb):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000370 return super(Popen, self).communicate(input)
371
372 if self.timeout and self.shell:
373 raise TypeError(
374 'Using timeout and shell simultaneously will cause a process leak '
375 'since the shell will be killed instead of the child process.')
376
377 stdout = None
378 stderr = None
379 # Convert to a lambda to workaround python's deadlock.
380 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000381 # When the pipe fills up, it would deadlock this process.
382 if self.stdout and not self.stdout_cb and not self.stdout_is_void:
383 stdout = []
384 self.stdout_cb = stdout.append
385 if self.stderr and not self.stderr_cb and not self.stderr_is_void:
386 stderr = []
387 self.stderr_cb = stderr.append
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000388 self._tee_threads(input)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000389 if stdout is not None:
390 stdout = ''.join(stdout)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000391 if stderr is not None:
392 stderr = ''.join(stderr)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000393 return (stdout, stderr)
394
maruel@chromium.org4860f052011-03-25 20:34:38 +0000395
szager@chromium.orge0558e62013-05-02 02:48:51 +0000396def communicate(args, timeout=None, nag_timer=None, **kwargs):
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000397 """Wraps subprocess.Popen().communicate() and add timeout support.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000398
maruel@chromium.org421982f2011-04-01 17:38:06 +0000399 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000400
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000401 - The process will be killed after |timeout| seconds and returncode set to
402 TIMED_OUT.
szager@chromium.orge0558e62013-05-02 02:48:51 +0000403 - If the subprocess runs for |nag_timer| seconds without producing terminal
404 output, print a warning to stderr.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000405 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000406 """
407 stdin = kwargs.pop('stdin', None)
408 if stdin is not None:
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000409 if isinstance(stdin, basestring):
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000410 # When stdin is passed as an argument, use it as the actual input data and
411 # set the Popen() parameter accordingly.
412 kwargs['stdin'] = PIPE
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000413 else:
414 kwargs['stdin'] = stdin
415 stdin = None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000416
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000417 proc = Popen(args, **kwargs)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000418 if stdin:
szager@chromium.orge0558e62013-05-02 02:48:51 +0000419 return proc.communicate(stdin, timeout, nag_timer), proc.returncode
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000420 else:
szager@chromium.orge0558e62013-05-02 02:48:51 +0000421 return proc.communicate(None, timeout, nag_timer), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000422
423
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000424def call(args, **kwargs):
425 """Emulates subprocess.call().
426
427 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000428 In no case they can be returned since no code path raises
429 subprocess2.CalledProcessError.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000430 """
431 if kwargs.get('stdout') == PIPE:
432 kwargs['stdout'] = VOID
433 if kwargs.get('stderr') == PIPE:
434 kwargs['stderr'] = VOID
435 return communicate(args, **kwargs)[1]
436
437
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000438def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000439 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000440
maruel@chromium.org421982f2011-04-01 17:38:06 +0000441 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000442 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000443 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000444 if returncode:
445 raise CalledProcessError(
446 returncode, args, kwargs.get('cwd'), out[0], out[1])
447 return out
448
449
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000450def check_call(args, **kwargs):
451 """Emulate subprocess.check_call()."""
452 check_call_out(args, **kwargs)
453 return 0
454
455
maruel@chromium.org4860f052011-03-25 20:34:38 +0000456def capture(args, **kwargs):
457 """Captures stdout of a process call and returns it.
458
maruel@chromium.org421982f2011-04-01 17:38:06 +0000459 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000460
maruel@chromium.org421982f2011-04-01 17:38:06 +0000461 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000462 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000463 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000464 kwargs.setdefault('stdin', VOID)
465
466 # Like check_output, deny the caller from using stdout arg.
467 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000468
469
470def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000471 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000472
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000473 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000474
maruel@chromium.org421982f2011-04-01 17:38:06 +0000475 - Throws if return code is not 0.
476 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000477 - Blocks stdin by default if not specified since no output will be visible.
478 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000479 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000480 kwargs.setdefault('stdin', VOID)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000481 if 'stdout' in kwargs:
482 raise ValueError('stdout argument not allowed, it will be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000483 return check_call_out(args, stdout=PIPE, **kwargs)[0]