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