1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2022 The Chromium Authors 3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker"""Implements commands for flashing a Fuchsia device.""" 6*8975f5c5SAndroid Build Coastguard Worker 7*8975f5c5SAndroid Build Coastguard Workerimport argparse 8*8975f5c5SAndroid Build Coastguard Workerimport logging 9*8975f5c5SAndroid Build Coastguard Workerimport os 10*8975f5c5SAndroid Build Coastguard Workerimport subprocess 11*8975f5c5SAndroid Build Coastguard Workerimport sys 12*8975f5c5SAndroid Build Coastguard Worker 13*8975f5c5SAndroid Build Coastguard Workerfrom typing import Optional, Tuple 14*8975f5c5SAndroid Build Coastguard Worker 15*8975f5c5SAndroid Build Coastguard Workerimport common 16*8975f5c5SAndroid Build Coastguard Workerfrom boot_device import BootMode, StateTransitionError, boot_device 17*8975f5c5SAndroid Build Coastguard Workerfrom common import get_system_info, find_image_in_sdk, \ 18*8975f5c5SAndroid Build Coastguard Worker register_device_args 19*8975f5c5SAndroid Build Coastguard Workerfrom compatible_utils import get_sdk_hash, running_unattended 20*8975f5c5SAndroid Build Coastguard Workerfrom lockfile import lock 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Worker# Flash-file lock. Used to restrict number of flash operations per host. 23*8975f5c5SAndroid Build Coastguard Worker# File lock should be marked as stale after 15 mins. 24*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK = os.path.join('/tmp', 'flash.lock') 25*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK_STALE_SECS = 60 * 15 26*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK_ACQ_TIMEOUT = _FF_LOCK_STALE_SECS 27*8975f5c5SAndroid Build Coastguard Worker 28*8975f5c5SAndroid Build Coastguard Worker 29*8975f5c5SAndroid Build Coastguard Workerdef _get_system_info(target: Optional[str], 30*8975f5c5SAndroid Build Coastguard Worker serial_num: Optional[str]) -> Tuple[str, str]: 31*8975f5c5SAndroid Build Coastguard Worker """Retrieves installed OS version from device. 32*8975f5c5SAndroid Build Coastguard Worker 33*8975f5c5SAndroid Build Coastguard Worker Args: 34*8975f5c5SAndroid Build Coastguard Worker target: Target to get system info of. 35*8975f5c5SAndroid Build Coastguard Worker serial_num: Serial number of device to get system info of. 36*8975f5c5SAndroid Build Coastguard Worker Returns: 37*8975f5c5SAndroid Build Coastguard Worker Tuple of strings, containing (product, version number). 38*8975f5c5SAndroid Build Coastguard Worker """ 39*8975f5c5SAndroid Build Coastguard Worker 40*8975f5c5SAndroid Build Coastguard Worker if running_unattended(): 41*8975f5c5SAndroid Build Coastguard Worker try: 42*8975f5c5SAndroid Build Coastguard Worker boot_device(target, BootMode.REGULAR, serial_num) 43*8975f5c5SAndroid Build Coastguard Worker except (subprocess.CalledProcessError, StateTransitionError): 44*8975f5c5SAndroid Build Coastguard Worker logging.warning('Could not boot device. Assuming in fastboot') 45*8975f5c5SAndroid Build Coastguard Worker return ('', '') 46*8975f5c5SAndroid Build Coastguard Worker 47*8975f5c5SAndroid Build Coastguard Worker return get_system_info(target) 48*8975f5c5SAndroid Build Coastguard Worker 49*8975f5c5SAndroid Build Coastguard Worker 50*8975f5c5SAndroid Build Coastguard Workerdef _update_required( 51*8975f5c5SAndroid Build Coastguard Worker os_check, 52*8975f5c5SAndroid Build Coastguard Worker system_image_dir: Optional[str], 53*8975f5c5SAndroid Build Coastguard Worker target: Optional[str], 54*8975f5c5SAndroid Build Coastguard Worker serial_num: Optional[str] = None) -> Tuple[bool, Optional[str]]: 55*8975f5c5SAndroid Build Coastguard Worker """Returns True if a system update is required and path to image dir.""" 56*8975f5c5SAndroid Build Coastguard Worker 57*8975f5c5SAndroid Build Coastguard Worker if os_check == 'ignore': 58*8975f5c5SAndroid Build Coastguard Worker return False, system_image_dir 59*8975f5c5SAndroid Build Coastguard Worker if not system_image_dir: 60*8975f5c5SAndroid Build Coastguard Worker raise ValueError('System image directory must be specified.') 61*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(system_image_dir): 62*8975f5c5SAndroid Build Coastguard Worker logging.warning( 63*8975f5c5SAndroid Build Coastguard Worker 'System image directory does not exist. Assuming it\'s ' 64*8975f5c5SAndroid Build Coastguard Worker 'a product-bundle name and dynamically searching for ' 65*8975f5c5SAndroid Build Coastguard Worker 'image directory') 66*8975f5c5SAndroid Build Coastguard Worker path = find_image_in_sdk(system_image_dir) 67*8975f5c5SAndroid Build Coastguard Worker if not path: 68*8975f5c5SAndroid Build Coastguard Worker raise FileNotFoundError( 69*8975f5c5SAndroid Build Coastguard Worker f'System image directory {system_image_dir} could not' 70*8975f5c5SAndroid Build Coastguard Worker 'be found') 71*8975f5c5SAndroid Build Coastguard Worker system_image_dir = path 72*8975f5c5SAndroid Build Coastguard Worker if (os_check == 'check' 73*8975f5c5SAndroid Build Coastguard Worker and get_sdk_hash(system_image_dir) == _get_system_info( 74*8975f5c5SAndroid Build Coastguard Worker target, serial_num)): 75*8975f5c5SAndroid Build Coastguard Worker return False, system_image_dir 76*8975f5c5SAndroid Build Coastguard Worker return True, system_image_dir 77*8975f5c5SAndroid Build Coastguard Worker 78*8975f5c5SAndroid Build Coastguard Worker 79*8975f5c5SAndroid Build Coastguard Workerdef _run_flash_command(system_image_dir: str, target_id: Optional[str]): 80*8975f5c5SAndroid Build Coastguard Worker """Helper function for running `ffx target flash`.""" 81*8975f5c5SAndroid Build Coastguard Worker logging.info('Flashing %s to %s', system_image_dir, target_id) 82*8975f5c5SAndroid Build Coastguard Worker # Flash only with a file lock acquired. 83*8975f5c5SAndroid Build Coastguard Worker # This prevents multiple fastboot binaries from flashing concurrently, 84*8975f5c5SAndroid Build Coastguard Worker # which should increase the odds of flashing success. 85*8975f5c5SAndroid Build Coastguard Worker with lock(_FF_LOCK, timeout=_FF_LOCK_ACQ_TIMEOUT): 86*8975f5c5SAndroid Build Coastguard Worker # The ffx.fastboot.inline_target has negative impact when ffx 87*8975f5c5SAndroid Build Coastguard Worker # discovering devices in fastboot, so it's inlined here to limit its 88*8975f5c5SAndroid Build Coastguard Worker # scope. See the discussion in https://fxbug.dev/issues/317228141. 89*8975f5c5SAndroid Build Coastguard Worker logging.info( 90*8975f5c5SAndroid Build Coastguard Worker 'Flash result %s', 91*8975f5c5SAndroid Build Coastguard Worker common.run_ffx_command(cmd=('target', 'flash', '-b', 92*8975f5c5SAndroid Build Coastguard Worker system_image_dir, 93*8975f5c5SAndroid Build Coastguard Worker '--no-bootloader-reboot'), 94*8975f5c5SAndroid Build Coastguard Worker target_id=target_id, 95*8975f5c5SAndroid Build Coastguard Worker configs=['ffx.fastboot.inline_target=true'], 96*8975f5c5SAndroid Build Coastguard Worker capture_output=True).stdout) 97*8975f5c5SAndroid Build Coastguard Worker 98*8975f5c5SAndroid Build Coastguard Worker 99*8975f5c5SAndroid Build Coastguard Workerdef update(system_image_dir: str, 100*8975f5c5SAndroid Build Coastguard Worker os_check: str, 101*8975f5c5SAndroid Build Coastguard Worker target: Optional[str], 102*8975f5c5SAndroid Build Coastguard Worker serial_num: Optional[str] = None) -> None: 103*8975f5c5SAndroid Build Coastguard Worker """Conditionally updates target given. 104*8975f5c5SAndroid Build Coastguard Worker 105*8975f5c5SAndroid Build Coastguard Worker Args: 106*8975f5c5SAndroid Build Coastguard Worker system_image_dir: string, path to image directory. 107*8975f5c5SAndroid Build Coastguard Worker os_check: <check|ignore|update>, which decides how to update the device. 108*8975f5c5SAndroid Build Coastguard Worker target: Node-name string indicating device that should be updated. 109*8975f5c5SAndroid Build Coastguard Worker serial_num: String of serial number of device that should be updated. 110*8975f5c5SAndroid Build Coastguard Worker """ 111*8975f5c5SAndroid Build Coastguard Worker needs_update, actual_image_dir = _update_required(os_check, 112*8975f5c5SAndroid Build Coastguard Worker system_image_dir, target, 113*8975f5c5SAndroid Build Coastguard Worker serial_num) 114*8975f5c5SAndroid Build Coastguard Worker logging.info('update_required %s, actual_image_dir %s', needs_update, 115*8975f5c5SAndroid Build Coastguard Worker actual_image_dir) 116*8975f5c5SAndroid Build Coastguard Worker if not needs_update: 117*8975f5c5SAndroid Build Coastguard Worker return 118*8975f5c5SAndroid Build Coastguard Worker if serial_num: 119*8975f5c5SAndroid Build Coastguard Worker boot_device(target, BootMode.BOOTLOADER, serial_num) 120*8975f5c5SAndroid Build Coastguard Worker _run_flash_command(system_image_dir, serial_num) 121*8975f5c5SAndroid Build Coastguard Worker else: 122*8975f5c5SAndroid Build Coastguard Worker _run_flash_command(system_image_dir, target) 123*8975f5c5SAndroid Build Coastguard Worker 124*8975f5c5SAndroid Build Coastguard Worker 125*8975f5c5SAndroid Build Coastguard Workerdef register_update_args(arg_parser: argparse.ArgumentParser, 126*8975f5c5SAndroid Build Coastguard Worker default_os_check: Optional[str] = 'check') -> None: 127*8975f5c5SAndroid Build Coastguard Worker """Register common arguments for device updating.""" 128*8975f5c5SAndroid Build Coastguard Worker serve_args = arg_parser.add_argument_group('update', 129*8975f5c5SAndroid Build Coastguard Worker 'device updating arguments') 130*8975f5c5SAndroid Build Coastguard Worker serve_args.add_argument('--system-image-dir', 131*8975f5c5SAndroid Build Coastguard Worker help='Specify the directory that contains the ' 132*8975f5c5SAndroid Build Coastguard Worker 'Fuchsia image used to flash the device. Only ' 133*8975f5c5SAndroid Build Coastguard Worker 'needs to be specified if "os_check" is not ' 134*8975f5c5SAndroid Build Coastguard Worker '"ignore".') 135*8975f5c5SAndroid Build Coastguard Worker serve_args.add_argument('--serial-num', 136*8975f5c5SAndroid Build Coastguard Worker default=os.environ.get('FUCHSIA_FASTBOOT_SERNUM'), 137*8975f5c5SAndroid Build Coastguard Worker help='Serial number of the device. Should be ' 138*8975f5c5SAndroid Build Coastguard Worker 'specified for devices that do not have an image ' 139*8975f5c5SAndroid Build Coastguard Worker 'flashed.') 140*8975f5c5SAndroid Build Coastguard Worker serve_args.add_argument('--os-check', 141*8975f5c5SAndroid Build Coastguard Worker choices=['check', 'update', 'ignore'], 142*8975f5c5SAndroid Build Coastguard Worker default=default_os_check, 143*8975f5c5SAndroid Build Coastguard Worker help='Sets the OS version enforcement policy. If ' 144*8975f5c5SAndroid Build Coastguard Worker '"check", then the deployment process will halt ' 145*8975f5c5SAndroid Build Coastguard Worker 'if the target\'s version does not match. If ' 146*8975f5c5SAndroid Build Coastguard Worker '"update", then the target device will ' 147*8975f5c5SAndroid Build Coastguard Worker 'be reflashed. If "ignore", then the OS version ' 148*8975f5c5SAndroid Build Coastguard Worker 'will not be checked.') 149*8975f5c5SAndroid Build Coastguard Worker 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard Workerdef main(): 152*8975f5c5SAndroid Build Coastguard Worker """Stand-alone function for flashing a device.""" 153*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 154*8975f5c5SAndroid Build Coastguard Worker register_device_args(parser) 155*8975f5c5SAndroid Build Coastguard Worker register_update_args(parser, default_os_check='update') 156*8975f5c5SAndroid Build Coastguard Worker args = parser.parse_args() 157*8975f5c5SAndroid Build Coastguard Worker update(args.system_image_dir, args.os_check, args.target_id, 158*8975f5c5SAndroid Build Coastguard Worker args.serial_num) 159*8975f5c5SAndroid Build Coastguard Worker 160*8975f5c5SAndroid Build Coastguard Worker 161*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 162*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 163