| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A wrapper for subprocess to make calling shell commands easier.""" |
| |
| import logging |
| import pipes |
| import signal |
| import subprocess |
| import tempfile |
| |
| from utils import timeout_retry |
| |
| |
| def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
| return subprocess.Popen( |
| args=args, cwd=cwd, stdout=stdout, stderr=stderr, |
| shell=shell, close_fds=True, env=env, |
| preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) |
| |
| |
| def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
| pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, |
| env=env) |
| pipe.communicate() |
| return pipe.wait() |
| |
| |
| def RunCmd(args, cwd=None): |
| """Opens a subprocess to execute a program and returns its return value. |
| |
| Args: |
| args: A string or a sequence of program arguments. The program to execute is |
| the string or the first item in the args sequence. |
| cwd: If not None, the subprocess's current directory will be changed to |
| |cwd| before it's executed. |
| |
| Returns: |
| Return code from the command execution. |
| """ |
| logging.info(str(args) + ' ' + (cwd or '')) |
| return Call(args, cwd=cwd) |
| |
| |
| def GetCmdOutput(args, cwd=None, shell=False): |
| """Open a subprocess to execute a program and returns its output. |
| |
| Args: |
| args: A string or a sequence of program arguments. The program to execute is |
| the string or the first item in the args sequence. |
| cwd: If not None, the subprocess's current directory will be changed to |
| |cwd| before it's executed. |
| shell: Whether to execute args as a shell command. |
| |
| Returns: |
| Captures and returns the command's stdout. |
| Prints the command's stderr to logger (which defaults to stdout). |
| """ |
| (_, output) = GetCmdStatusAndOutput(args, cwd, shell) |
| return output |
| |
| |
| def GetCmdStatusAndOutput(args, cwd=None, shell=False): |
| """Executes a subprocess and returns its exit code and output. |
| |
| Args: |
| args: A string or a sequence of program arguments. The program to execute is |
| the string or the first item in the args sequence. |
| cwd: If not None, the subprocess's current directory will be changed to |
| |cwd| before it's executed. |
| shell: Whether to execute args as a shell command. |
| |
| Returns: |
| The 2-tuple (exit code, output). |
| """ |
| if isinstance(args, basestring): |
| args_repr = args |
| if not shell: |
| raise Exception('string args must be run with shell=True') |
| elif shell: |
| raise Exception('array args must be run with shell=False') |
| else: |
| args_repr = ' '.join(map(pipes.quote, args)) |
| |
| s = '[host]' |
| if cwd: |
| s += ':' + cwd |
| s += '> ' + args_repr |
| logging.info(s) |
| tmpout = tempfile.TemporaryFile(bufsize=0) |
| tmperr = tempfile.TemporaryFile(bufsize=0) |
| exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell) |
| tmperr.seek(0) |
| stderr = tmperr.read() |
| tmperr.close() |
| if stderr: |
| logging.critical(stderr) |
| tmpout.seek(0) |
| stdout = tmpout.read() |
| tmpout.close() |
| if len(stdout) > 4096: |
| logging.debug('Truncated output:') |
| logging.debug(stdout[:4096]) |
| return (exit_code, stdout) |
| |
| |
| def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries): |
| """Executes a subprocess with a timeout and retries. |
| |
| Args: |
| args: List of arguments to the program, the program to execute is the first |
| element. |
| timeout: the timeout in seconds. |
| retries: the number of retries. |
| |
| Returns: |
| The 2-tuple (exit code, output). |
| """ |
| return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args]) |