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 19 import os 20 import signal 21 import subprocess 22 import tempfile 23 import threading 24 import time 25 26 # local imports 27 import errors 28 import logger 29 30 _abort_on_error = False 31 32 def 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 38 def 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 66 def 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 146 def 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 183 def 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