1#!/usr/bin/python2.4
2#
3#
4# Copyright 2007, The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18# System imports
19import os
20import signal
21import subprocess
22import tempfile
23import threading
24import time
25
26# local imports
27import errors
28import logger
29
30_abort_on_error = False
31
32def SetAbortOnError(abort=True):
33  """Sets behavior of RunCommand to throw AbortError if command process returns
34  a negative error code"""
35  global _abort_on_error
36  _abort_on_error = abort
37
38def RunCommand(cmd, timeout_time=None, retry_count=3, return_output=True,
39               stdin_input=None):
40  """Spawn and retry a subprocess to run the given shell command.
41
42  Args:
43    cmd: shell command to run
44    timeout_time: time in seconds to wait for command to run before aborting.
45    retry_count: number of times to retry command
46    return_output: if True return output of command as string. Otherwise,
47      direct output of command to stdout.
48    stdin_input: data to feed to stdin
49  Returns:
50    output of command
51  """
52  result = None
53  while True:
54    try:
55      result = RunOnce(cmd, timeout_time=timeout_time,
56                       return_output=return_output, stdin_input=stdin_input)
57    except errors.WaitForResponseTimedOutError:
58      if retry_count == 0:
59        raise
60      retry_count -= 1
61      logger.Log("No response for %s, retrying" % cmd)
62    else:
63      # Success
64      return result
65
66def RunOnce(cmd, timeout_time=None, return_output=True, stdin_input=None):
67  """Spawns a subprocess to run the given shell command.
68
69  Args:
70    cmd: shell command to run
71    timeout_time: time in seconds to wait for command to run before aborting.
72    return_output: if True return output of command as string. Otherwise,
73      direct output of command to stdout.
74    stdin_input: data to feed to stdin
75  Returns:
76    output of command
77  Raises:
78    errors.WaitForResponseTimedOutError if command did not complete within
79      timeout_time seconds.
80    errors.AbortError is command returned error code and SetAbortOnError is on.
81  """
82  start_time = time.time()
83  so = []
84  global _abort_on_error, error_occurred
85  error_occurred = False
86
87  if return_output:
88    output_dest = tempfile.TemporaryFile(bufsize=0)
89  else:
90    # None means direct to stdout
91    output_dest = None
92  if stdin_input:
93    stdin_dest = subprocess.PIPE
94  else:
95    stdin_dest = None
96  pipe = subprocess.Popen(
97      cmd,
98      executable='/bin/bash',
99      stdin=stdin_dest,
100      stdout=output_dest,
101      stderr=subprocess.STDOUT,
102      shell=True, close_fds=True,
103      preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
104
105  def Run():
106    global error_occurred
107    try:
108      pipe.communicate(input=stdin_input)
109      output = None
110      if return_output:
111        output_dest.seek(0)
112        output = output_dest.read()
113        output_dest.close()
114      if output is not None and len(output) > 0:
115        so.append(output)
116    except OSError, e:
117      logger.SilentLog("failed to retrieve stdout from: %s" % cmd)
118      logger.Log(e)
119      so.append("ERROR")
120      error_occurred = True
121    if pipe.returncode:
122      logger.SilentLog("Error: %s returned %d error code" %(cmd,
123          pipe.returncode))
124      error_occurred = True
125
126  t = threading.Thread(target=Run)
127  t.start()
128  t.join(timeout_time)
129  if t.isAlive():
130    try:
131      pipe.kill()
132    except OSError:
133      # Can't kill a dead process.
134      pass
135    finally:
136      logger.SilentLog("about to raise a timeout for: %s" % cmd)
137      raise errors.WaitForResponseTimedOutError
138
139  output = "".join(so)
140  if _abort_on_error and error_occurred:
141    raise errors.AbortError(msg=output)
142
143  return "".join(so)
144
145
146def RunHostCommand(binary, valgrind=False):
147  """Run a command on the host (opt using valgrind).
148
149  Runs the host binary and returns the exit code.
150  If successfull, the output (stdout and stderr) are discarded,
151  but printed in case of error.
152  The command can be run under valgrind in which case all the
153  output are always discarded.
154
155  Args:
156    binary: full path of the file to be run.
157    valgrind: If True the command will be run under valgrind.
158
159  Returns:
160    The command exit code (int)
161  """
162  if not valgrind:
163    subproc = subprocess.Popen(binary, stdout=subprocess.PIPE,
164                               stderr=subprocess.STDOUT)
165    subproc.wait()
166    if subproc.returncode != 0:         # In case of error print the output
167      print subproc.communicate()[0]
168    return subproc.returncode
169  else:
170    # Need the full path to valgrind to avoid other versions on the system.
171    subproc = subprocess.Popen(["/usr/bin/valgrind", "--tool=memcheck",
172                                "--leak-check=yes", "-q", binary],
173                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
174    # Cannot rely on the retcode of valgrind. Instead look for an empty output.
175    valgrind_out = subproc.communicate()[0].strip()
176    if valgrind_out:
177      print valgrind_out
178      return 1
179    else:
180      return 0
181
182
183def HasValgrind():
184  """Check that /usr/bin/valgrind exists.
185
186  We look for the fullpath to avoid picking up 'alternative' valgrind
187  on the system.
188
189  Returns:
190    True if a system valgrind was found.
191  """
192  return os.path.exists("/usr/bin/valgrind")
193