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 threading
23import time
24
25# local imports
26import errors
27import logger
28
29_abort_on_error = False
30
31def SetAbortOnError(abort=True):
32  """Sets behavior of RunCommand to throw AbortError if command process returns
33  a negative error code"""
34  global _abort_on_error
35  _abort_on_error = abort
36
37def RunCommand(cmd, timeout_time=None, retry_count=3, return_output=True,
38               stdin_input=None):
39  """Spawn and retry a subprocess to run the given shell command.
40
41  Args:
42    cmd: shell command to run
43    timeout_time: time in seconds to wait for command to run before aborting.
44    retry_count: number of times to retry command
45    return_output: if True return output of command as string. Otherwise,
46      direct output of command to stdout.
47    stdin_input: data to feed to stdin
48  Returns:
49    output of command
50  """
51  result = None
52  while True:
53    try:
54      result = RunOnce(cmd, timeout_time=timeout_time,
55                       return_output=return_output, stdin_input=stdin_input)
56    except errors.WaitForResponseTimedOutError:
57      if retry_count == 0:
58        raise
59      retry_count -= 1
60      logger.Log("No response for %s, retrying" % cmd)
61    else:
62      # Success
63      return result
64
65def RunOnce(cmd, timeout_time=None, return_output=True, stdin_input=None):
66  """Spawns a subprocess to run the given shell command.
67
68  Args:
69    cmd: shell command to run
70    timeout_time: time in seconds to wait for command to run before aborting.
71    return_output: if True return output of command as string. Otherwise,
72      direct output of command to stdout.
73    stdin_input: data to feed to stdin
74  Returns:
75    output of command
76  Raises:
77    errors.WaitForResponseTimedOutError if command did not complete within
78      timeout_time seconds.
79    errors.AbortError is command returned error code and SetAbortOnError is on.
80  """
81  start_time = time.time()
82  so = []
83  pid = []
84  global _abort_on_error, error_occurred
85  error_occurred = False
86
87  def Run():
88    global error_occurred
89    if return_output:
90      output_dest = subprocess.PIPE
91    else:
92      # None means direct to stdout
93      output_dest = None
94    if stdin_input:
95      stdin_dest = subprocess.PIPE
96    else:
97      stdin_dest = None
98    pipe = subprocess.Popen(
99        cmd,
100        executable='/bin/bash',
101        stdin=stdin_dest,
102        stdout=output_dest,
103        stderr=subprocess.STDOUT,
104        shell=True)
105    pid.append(pipe.pid)
106    try:
107      output = pipe.communicate(input=stdin_input)[0]
108      if output is not None and len(output) > 0:
109        so.append(output)
110    except OSError, e:
111      logger.SilentLog("failed to retrieve stdout from: %s" % cmd)
112      logger.Log(e)
113      so.append("ERROR")
114      error_occurred = True
115    if pipe.returncode:
116      logger.SilentLog("Error: %s returned %d error code" %(cmd,
117          pipe.returncode))
118      error_occurred = True
119
120  t = threading.Thread(target=Run)
121  t.start()
122
123  break_loop = False
124  while not break_loop:
125    if not t.isAlive():
126      break_loop = True
127
128    # Check the timeout
129    if (not break_loop and timeout_time is not None
130        and time.time() > start_time + timeout_time):
131      try:
132        os.kill(pid[0], signal.SIGKILL)
133      except OSError:
134        # process already dead. No action required.
135        pass
136
137      logger.SilentLog("about to raise a timeout for: %s" % cmd)
138      raise errors.WaitForResponseTimedOutError
139    if not break_loop:
140      time.sleep(0.1)
141
142  t.join()
143  output = "".join(so)
144  if _abort_on_error and error_occurred:
145    raise errors.AbortError(msg=output)
146
147  return "".join(so)
148
149
150def RunHostCommand(binary, valgrind=False):
151  """Run a command on the host (opt using valgrind).
152
153  Runs the host binary and returns the exit code.
154  If successfull, the output (stdout and stderr) are discarded,
155  but printed in case of error.
156  The command can be run under valgrind in which case all the
157  output are always discarded.
158
159  Args:
160    binary: full path of the file to be run.
161    valgrind: If True the command will be run under valgrind.
162
163  Returns:
164    The command exit code (int)
165  """
166  if not valgrind:
167    subproc = subprocess.Popen(binary, stdout=subprocess.PIPE,
168                               stderr=subprocess.STDOUT)
169    subproc.wait()
170    if subproc.returncode != 0:         # In case of error print the output
171      print subproc.communicate()[0]
172    return subproc.returncode
173  else:
174    # Need the full path to valgrind to avoid other versions on the system.
175    subproc = subprocess.Popen(["/usr/bin/valgrind", "--tool=memcheck",
176                                "--leak-check=yes", "-q", binary],
177                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
178    # Cannot rely on the retcode of valgrind. Instead look for an empty output.
179    valgrind_out = subproc.communicate()[0].strip()
180    if valgrind_out:
181      print valgrind_out
182      return 1
183    else:
184      return 0
185
186
187def HasValgrind():
188  """Check that /usr/bin/valgrind exists.
189
190  We look for the fullpath to avoid picking up 'alternative' valgrind
191  on the system.
192
193  Returns:
194    True if a system valgrind was found.
195  """
196  return os.path.exists("/usr/bin/valgrind")
197