cmd_helper.py revision f2477e01787aa58f445919b809d89e252beef54f
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""A wrapper for subprocess to make calling shell commands easier."""
6
7import logging
8import pipes
9import signal
10import subprocess
11import tempfile
12
13from utils import timeout_retry
14
15
16def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
17  return subprocess.Popen(
18      args=args, cwd=cwd, stdout=stdout, stderr=stderr,
19      shell=shell, close_fds=True, env=env,
20      preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
21
22
23def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
24  pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd,
25               env=env)
26  pipe.communicate()
27  return pipe.wait()
28
29
30def RunCmd(args, cwd=None):
31  """Opens a subprocess to execute a program and returns its return value.
32
33  Args:
34    args: A string or a sequence of program arguments. The program to execute is
35      the string or the first item in the args sequence.
36    cwd: If not None, the subprocess's current directory will be changed to
37      |cwd| before it's executed.
38
39  Returns:
40    Return code from the command execution.
41  """
42  logging.info(str(args) + ' ' + (cwd or ''))
43  return Call(args, cwd=cwd)
44
45
46def GetCmdOutput(args, cwd=None, shell=False):
47  """Open a subprocess to execute a program and returns its output.
48
49  Args:
50    args: A string or a sequence of program arguments. The program to execute is
51      the string or the first item in the args sequence.
52    cwd: If not None, the subprocess's current directory will be changed to
53      |cwd| before it's executed.
54    shell: Whether to execute args as a shell command.
55
56  Returns:
57    Captures and returns the command's stdout.
58    Prints the command's stderr to logger (which defaults to stdout).
59  """
60  (_, output) = GetCmdStatusAndOutput(args, cwd, shell)
61  return output
62
63
64def GetCmdStatusAndOutput(args, cwd=None, shell=False):
65  """Executes a subprocess and returns its exit code and output.
66
67  Args:
68    args: A string or a sequence of program arguments. The program to execute is
69      the string or the first item in the args sequence.
70    cwd: If not None, the subprocess's current directory will be changed to
71      |cwd| before it's executed.
72    shell: Whether to execute args as a shell command.
73
74  Returns:
75    The 2-tuple (exit code, output).
76  """
77  if isinstance(args, basestring):
78    args_repr = args
79    if not shell:
80      raise Exception('string args must be run with shell=True')
81  elif shell:
82    raise Exception('array args must be run with shell=False')
83  else:
84    args_repr = ' '.join(map(pipes.quote, args))
85
86  s = '[host]'
87  if cwd:
88    s += ':' + cwd
89  s += '> ' + args_repr
90  logging.info(s)
91  tmpout = tempfile.TemporaryFile(bufsize=0)
92  tmperr = tempfile.TemporaryFile(bufsize=0)
93  exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell)
94  tmperr.seek(0)
95  stderr = tmperr.read()
96  tmperr.close()
97  if stderr:
98    logging.critical(stderr)
99  tmpout.seek(0)
100  stdout = tmpout.read()
101  tmpout.close()
102  if len(stdout) > 4096:
103    logging.debug('Truncated output:')
104  logging.debug(stdout[:4096])
105  return (exit_code, stdout)
106
107
108def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries):
109  """Executes a subprocess with a timeout and retries.
110
111  Args:
112    args: List of arguments to the program, the program to execute is the first
113      element.
114    timeout: the timeout in seconds.
115    retries: the number of retries.
116
117  Returns:
118    The 2-tuple (exit code, output).
119  """
120  return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args])
121