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 threading 23 import time 24 25 # local imports 26 import errors 27 import logger 28 29 _abort_on_error = False 30 31 def 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 37 def 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 65 def 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 150 def 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 187 def 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