1#!/usr/bin/env vpython 2# Copyright 2016 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""A script to recover devices in a known bad state.""" 6 7import argparse 8import glob 9import logging 10import os 11import signal 12import sys 13 14import psutil 15 16if __name__ == '__main__': 17 sys.path.append( 18 os.path.abspath( 19 os.path.join(os.path.dirname(__file__), '..', '..', '..'))) 20from devil.android import device_denylist 21from devil.android import device_errors 22from devil.android import device_utils 23from devil.android.sdk import adb_wrapper 24from devil.android.tools import device_status 25from devil.android.tools import script_common 26from devil.utils import logging_common 27from devil.utils import lsusb 28# TODO(jbudorick): Resolve this after experimenting w/ disabling the USB reset. 29from devil.utils import reset_usb # pylint: disable=unused-import 30 31logger = logging.getLogger(__name__) 32 33from py_utils import modules_util 34 35# Script depends on features from psutil version 2.0 or higher. 36modules_util.RequireVersion(psutil, '2.0') 37 38 39def KillAllAdb(): 40 def get_all_adb(): 41 for p in psutil.process_iter(): 42 try: 43 # Retrieve all required process infos at once. 44 pinfo = p.as_dict(attrs=['pid', 'name', 'cmdline']) 45 if pinfo['name'] == 'adb': 46 pinfo['cmdline'] = ' '.join(pinfo['cmdline']) 47 yield p, pinfo 48 except (psutil.NoSuchProcess, psutil.AccessDenied): 49 pass 50 51 for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]: 52 for p, pinfo in get_all_adb(): 53 try: 54 pinfo['signal'] = sig 55 logger.info('kill %(signal)s %(pid)s (%(name)s [%(cmdline)s])', pinfo) 56 p.send_signal(sig) 57 except (psutil.NoSuchProcess, psutil.AccessDenied): 58 pass 59 for _, pinfo in get_all_adb(): 60 try: 61 logger.error('Unable to kill %(pid)s (%(name)s [%(cmdline)s])', pinfo) 62 except (psutil.NoSuchProcess, psutil.AccessDenied): 63 pass 64 65 66def TryAuth(device): 67 """Uses anything in ~/.android/ that looks like a key to auth with the device. 68 69 Args: 70 device: The DeviceUtils device to attempt to auth. 71 72 Returns: 73 True if device successfully authed. 74 """ 75 possible_keys = glob.glob(os.path.join(adb_wrapper.ADB_HOST_KEYS_DIR, '*key')) 76 if len(possible_keys) <= 1: 77 logger.warning('Only %d ADB keys available. Not forcing auth.', 78 len(possible_keys)) 79 return False 80 81 KillAllAdb() 82 adb_wrapper.AdbWrapper.StartServer(keys=possible_keys) 83 new_state = device.adb.GetState() 84 if new_state != 'device': 85 logger.error('Auth failed. Device %s still stuck in %s.', str(device), 86 new_state) 87 return False 88 89 # It worked! Now register the host's default ADB key on the device so we don't 90 # have to do all that again. 91 pub_key = os.path.join(adb_wrapper.ADB_HOST_KEYS_DIR, 'adbkey.pub') 92 if not os.path.exists(pub_key): # This really shouldn't happen. 93 logger.error('Default ADB key not available at %s.', pub_key) 94 return False 95 96 with open(pub_key) as f: 97 pub_key_contents = f.read() 98 try: 99 device.WriteFile(adb_wrapper.ADB_KEYS_FILE, pub_key_contents, as_root=True) 100 except (device_errors.CommandTimeoutError, device_errors.CommandFailedError, 101 device_errors.DeviceUnreachableError): 102 logger.exception('Unable to write default ADB key to %s.', str(device)) 103 return False 104 return True 105 106 107def RecoverDevice(device, denylist, should_reboot=lambda device: True): 108 if device_status.IsDenylisted(device.adb.GetDeviceSerial(), denylist): 109 logger.debug('%s is denylisted, skipping recovery.', str(device)) 110 return 111 112 if device.adb.GetState() == 'unauthorized' and TryAuth(device): 113 logger.info('Successfully authed device %s!', str(device)) 114 return 115 116 if should_reboot(device): 117 should_restore_root = device.HasRoot() 118 try: 119 device.WaitUntilFullyBooted(retries=0) 120 except (device_errors.CommandTimeoutError, device_errors.CommandFailedError, 121 device_errors.DeviceUnreachableError): 122 logger.exception( 123 'Failure while waiting for %s. ' 124 'Attempting to recover.', str(device)) 125 try: 126 try: 127 device.Reboot(block=False, timeout=5, retries=0) 128 except device_errors.CommandTimeoutError: 129 logger.warning( 130 'Timed out while attempting to reboot %s normally.' 131 'Attempting alternative reboot.', str(device)) 132 # The device drops offline before we can grab the exit code, so 133 # we don't check for status. 134 try: 135 device.adb.Root() 136 finally: 137 # We are already in a failure mode, attempt to reboot regardless of 138 # what device.adb.Root() returns. If the sysrq reboot fails an 139 # exception willbe thrown at that level. 140 device.adb.Shell( 141 'echo b > /proc/sysrq-trigger', 142 expect_status=None, 143 timeout=5, 144 retries=0) 145 except (device_errors.CommandFailedError, 146 device_errors.DeviceUnreachableError): 147 logger.exception('Failed to reboot %s.', str(device)) 148 if denylist: 149 denylist.Extend([device.adb.GetDeviceSerial()], reason='reboot_failure') 150 except device_errors.CommandTimeoutError: 151 logger.exception('Timed out while rebooting %s.', str(device)) 152 if denylist: 153 denylist.Extend([device.adb.GetDeviceSerial()], reason='reboot_timeout') 154 155 try: 156 device.WaitUntilFullyBooted( 157 retries=0, timeout=device.REBOOT_DEFAULT_TIMEOUT) 158 if should_restore_root: 159 device.EnableRoot() 160 except (device_errors.CommandFailedError, 161 device_errors.DeviceUnreachableError): 162 logger.exception('Failure while waiting for %s.', str(device)) 163 if denylist: 164 denylist.Extend([device.adb.GetDeviceSerial()], reason='reboot_failure') 165 except device_errors.CommandTimeoutError: 166 logger.exception('Timed out while waiting for %s.', str(device)) 167 if denylist: 168 denylist.Extend([device.adb.GetDeviceSerial()], reason='reboot_timeout') 169 170 171def RecoverDevices(devices, denylist, enable_usb_reset=False): 172 """Attempts to recover any inoperable devices in the provided list. 173 174 Args: 175 devices: The list of devices to attempt to recover. 176 denylist: The current device denylist, which will be used then 177 reset. 178 """ 179 180 statuses = device_status.DeviceStatus(devices, denylist) 181 182 should_restart_usb = set( 183 status['serial'] for status in statuses 184 if (not status['usb_status'] or status['adb_status'] in ('offline', 185 'missing'))) 186 should_restart_adb = should_restart_usb.union( 187 set(status['serial'] for status in statuses 188 if status['adb_status'] == 'unauthorized')) 189 should_reboot_device = should_restart_usb.union( 190 set(status['serial'] for status in statuses if status['denylisted'])) 191 192 logger.debug('Should restart USB for:') 193 for d in should_restart_usb: 194 logger.debug(' %s', d) 195 logger.debug('Should restart ADB for:') 196 for d in should_restart_adb: 197 logger.debug(' %s', d) 198 logger.debug('Should reboot:') 199 for d in should_reboot_device: 200 logger.debug(' %s', d) 201 202 if denylist: 203 denylist.Reset() 204 205 if should_restart_adb: 206 KillAllAdb() 207 adb_wrapper.AdbWrapper.StartServer() 208 209 for serial in should_restart_usb: 210 try: 211 # TODO(crbug.com/642194): Resetting may be causing more harm 212 # (specifically, kernel panics) than it does good. 213 if enable_usb_reset: 214 reset_usb.reset_android_usb(serial) 215 else: 216 logger.warning('USB reset disabled for %s (crbug.com/642914)', serial) 217 except IOError: 218 logger.exception('Unable to reset USB for %s.', serial) 219 if denylist: 220 denylist.Extend([serial], reason='USB failure') 221 except device_errors.DeviceUnreachableError: 222 logger.exception('Unable to reset USB for %s.', serial) 223 if denylist: 224 denylist.Extend([serial], reason='offline') 225 226 device_utils.DeviceUtils.parallel(devices).pMap( 227 RecoverDevice, 228 denylist, 229 should_reboot=lambda device: device.serial in should_reboot_device) 230 231 232def main(): 233 parser = argparse.ArgumentParser() 234 logging_common.AddLoggingArguments(parser) 235 script_common.AddEnvironmentArguments(parser) 236 parser.add_argument('--denylist-file', help='Device denylist JSON file.') 237 parser.add_argument( 238 '--known-devices-file', 239 action='append', 240 default=[], 241 dest='known_devices_files', 242 help='Path to known device lists.') 243 parser.add_argument( 244 '--enable-usb-reset', action='store_true', help='Reset USB if necessary.') 245 246 args = parser.parse_args() 247 logging_common.InitializeLogging(args) 248 script_common.InitializeEnvironment(args) 249 250 denylist = (device_denylist.Denylist(args.denylist_file) 251 if args.denylist_file else None) 252 253 expected_devices = device_status.GetExpectedDevices(args.known_devices_files) 254 usb_devices = set(lsusb.get_android_devices()) 255 devices = [ 256 device_utils.DeviceUtils(s) for s in expected_devices.union(usb_devices) 257 ] 258 259 RecoverDevices(devices, denylist, enable_usb_reset=args.enable_usb_reset) 260 261 262if __name__ == '__main__': 263 sys.exit(main()) 264