blob: 4047b87e9741bc67f4ced682c6187265c34c0aed [file] [log] [blame]
# 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])