1*5a923131SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*5a923131SAndroid Build Coastguard Worker# 3*5a923131SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*5a923131SAndroid Build Coastguard Worker# 5*5a923131SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*5a923131SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*5a923131SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*5a923131SAndroid Build Coastguard Worker# 9*5a923131SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*5a923131SAndroid Build Coastguard Worker# 11*5a923131SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*5a923131SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*5a923131SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*5a923131SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*5a923131SAndroid Build Coastguard Worker# limitations under the License. 16*5a923131SAndroid Build Coastguard Worker# 17*5a923131SAndroid Build Coastguard Worker 18*5a923131SAndroid Build Coastguard Worker"""Tools for running host side simulation of an OTA update.""" 19*5a923131SAndroid Build Coastguard Worker 20*5a923131SAndroid Build Coastguard Worker 21*5a923131SAndroid Build Coastguard Workerimport argparse 22*5a923131SAndroid Build Coastguard Workerimport filecmp 23*5a923131SAndroid Build Coastguard Workerimport os 24*5a923131SAndroid Build Coastguard Workerimport shutil 25*5a923131SAndroid Build Coastguard Workerimport subprocess 26*5a923131SAndroid Build Coastguard Workerimport sys 27*5a923131SAndroid Build Coastguard Workerimport tempfile 28*5a923131SAndroid Build Coastguard Workerimport zipfile 29*5a923131SAndroid Build Coastguard Worker 30*5a923131SAndroid Build Coastguard Workerimport update_payload 31*5a923131SAndroid Build Coastguard Worker 32*5a923131SAndroid Build Coastguard Worker 33*5a923131SAndroid Build Coastguard Workerdef extract_file(zip_file_path, entry_name, target_file_path): 34*5a923131SAndroid Build Coastguard Worker """Extract a file from zip archive into |target_file_path|""" 35*5a923131SAndroid Build Coastguard Worker with open(target_file_path, 'wb') as out_fp: 36*5a923131SAndroid Build Coastguard Worker if isinstance(zip_file_path, zipfile.ZipFile): 37*5a923131SAndroid Build Coastguard Worker with zip_file_path.open(entry_name) as fp: 38*5a923131SAndroid Build Coastguard Worker shutil.copyfileobj(fp, out_fp) 39*5a923131SAndroid Build Coastguard Worker elif os.path.isdir(zip_file_path): 40*5a923131SAndroid Build Coastguard Worker with open(os.path.join(zip_file_path, entry_name), "rb") as fp: 41*5a923131SAndroid Build Coastguard Worker shutil.copyfileobj(fp, out_fp) 42*5a923131SAndroid Build Coastguard Worker 43*5a923131SAndroid Build Coastguard Worker 44*5a923131SAndroid Build Coastguard Workerdef is_sparse_image(filepath): 45*5a923131SAndroid Build Coastguard Worker with open(filepath, 'rb') as fp: 46*5a923131SAndroid Build Coastguard Worker # Magic for android sparse image format 47*5a923131SAndroid Build Coastguard Worker # https://source.android.com/devices/bootloader/images 48*5a923131SAndroid Build Coastguard Worker return fp.read(4) == b'\x3A\xFF\x26\xED' 49*5a923131SAndroid Build Coastguard Worker 50*5a923131SAndroid Build Coastguard Worker 51*5a923131SAndroid Build Coastguard Workerdef extract_img(zip_archive: zipfile.ZipFile, img_name, output_path, is_source): 52*5a923131SAndroid Build Coastguard Worker """ Extract and unsparse partition image from zip archive """ 53*5a923131SAndroid Build Coastguard Worker entry_name = "IMAGES/" + img_name + ".img" 54*5a923131SAndroid Build Coastguard Worker try: 55*5a923131SAndroid Build Coastguard Worker extract_file(zip_archive, entry_name, output_path) 56*5a923131SAndroid Build Coastguard Worker except (KeyError, FileNotFoundError) as e: 57*5a923131SAndroid Build Coastguard Worker print("Faild to extract", img_name, "from IMAGES/ dir, trying RADIO/", e) 58*5a923131SAndroid Build Coastguard Worker extract_file(zip_archive, "RADIO/" + img_name + ".img", output_path) 59*5a923131SAndroid Build Coastguard Worker if is_sparse_image(output_path): 60*5a923131SAndroid Build Coastguard Worker raw_img_path = output_path + ".raw" 61*5a923131SAndroid Build Coastguard Worker subprocess.check_output(["simg2img", output_path, raw_img_path]) 62*5a923131SAndroid Build Coastguard Worker os.rename(raw_img_path, output_path) 63*5a923131SAndroid Build Coastguard Worker 64*5a923131SAndroid Build Coastguard Worker # delta_generator only supports images multiple of 4 KiB. For target images 65*5a923131SAndroid Build Coastguard Worker # we pad the data with zeros if needed, but for source images we truncate 66*5a923131SAndroid Build Coastguard Worker # down the data since the last block of the old image could be padded on 67*5a923131SAndroid Build Coastguard Worker # disk with unknown data. 68*5a923131SAndroid Build Coastguard Worker file_size = os.path.getsize(output_path) 69*5a923131SAndroid Build Coastguard Worker if file_size % 4096 != 0: 70*5a923131SAndroid Build Coastguard Worker if is_source: 71*5a923131SAndroid Build Coastguard Worker print("Rounding DOWN partition {} to a multiple of 4 KiB." 72*5a923131SAndroid Build Coastguard Worker .format(output_path)) 73*5a923131SAndroid Build Coastguard Worker file_size = file_size & -4096 74*5a923131SAndroid Build Coastguard Worker else: 75*5a923131SAndroid Build Coastguard Worker print("Rounding UP partition {} to a multiple of 4 KiB." 76*5a923131SAndroid Build Coastguard Worker .format(output_path)) 77*5a923131SAndroid Build Coastguard Worker file_size = (file_size + 4095) & -4096 78*5a923131SAndroid Build Coastguard Worker with open(output_path, 'a') as f: 79*5a923131SAndroid Build Coastguard Worker f.truncate(file_size) 80*5a923131SAndroid Build Coastguard Worker 81*5a923131SAndroid Build Coastguard Workerdef run_ota(source, target, payload_path, tempdir, output_dir): 82*5a923131SAndroid Build Coastguard Worker """Run an OTA on host side""" 83*5a923131SAndroid Build Coastguard Worker payload = update_payload.Payload(payload_path) 84*5a923131SAndroid Build Coastguard Worker payload.Init() 85*5a923131SAndroid Build Coastguard Worker if source and zipfile.is_zipfile(source): 86*5a923131SAndroid Build Coastguard Worker source = zipfile.ZipFile(source) 87*5a923131SAndroid Build Coastguard Worker if target and zipfile.is_zipfile(target): 88*5a923131SAndroid Build Coastguard Worker target = zipfile.ZipFile(target) 89*5a923131SAndroid Build Coastguard Worker source_exist = source and (isinstance( 90*5a923131SAndroid Build Coastguard Worker source, zipfile.ZipFile) or os.path.exists(source)) 91*5a923131SAndroid Build Coastguard Worker target_exist = target and (isinstance( 92*5a923131SAndroid Build Coastguard Worker target, zipfile.ZipFile) or os.path.exists(target)) 93*5a923131SAndroid Build Coastguard Worker 94*5a923131SAndroid Build Coastguard Worker old_partitions = [] 95*5a923131SAndroid Build Coastguard Worker new_partitions = [] 96*5a923131SAndroid Build Coastguard Worker expected_new_partitions = [] 97*5a923131SAndroid Build Coastguard Worker for part in payload.manifest.partitions: 98*5a923131SAndroid Build Coastguard Worker name = part.partition_name 99*5a923131SAndroid Build Coastguard Worker old_image = os.path.join(tempdir, "source_" + name + ".img") 100*5a923131SAndroid Build Coastguard Worker new_image = os.path.join(tempdir, "target_" + name + ".img") 101*5a923131SAndroid Build Coastguard Worker if part.HasField("old_partition_info"): 102*5a923131SAndroid Build Coastguard Worker assert source_exist, \ 103*5a923131SAndroid Build Coastguard Worker "source target file must point to a valid zipfile or directory " + \ 104*5a923131SAndroid Build Coastguard Worker source 105*5a923131SAndroid Build Coastguard Worker print("Extracting source image for", name) 106*5a923131SAndroid Build Coastguard Worker extract_img(source, name, old_image, True) 107*5a923131SAndroid Build Coastguard Worker if target_exist: 108*5a923131SAndroid Build Coastguard Worker print("Extracting target image for", name) 109*5a923131SAndroid Build Coastguard Worker extract_img(target, name, new_image, False) 110*5a923131SAndroid Build Coastguard Worker 111*5a923131SAndroid Build Coastguard Worker old_partitions.append(old_image) 112*5a923131SAndroid Build Coastguard Worker scratch_image_name = new_image + ".actual" 113*5a923131SAndroid Build Coastguard Worker new_partitions.append(scratch_image_name) 114*5a923131SAndroid Build Coastguard Worker with open(scratch_image_name, "wb") as fp: 115*5a923131SAndroid Build Coastguard Worker fp.truncate(part.new_partition_info.size) 116*5a923131SAndroid Build Coastguard Worker expected_new_partitions.append(new_image) 117*5a923131SAndroid Build Coastguard Worker 118*5a923131SAndroid Build Coastguard Worker delta_generator_args = ["delta_generator", "--in_file=" + payload_path] 119*5a923131SAndroid Build Coastguard Worker partition_names = [ 120*5a923131SAndroid Build Coastguard Worker part.partition_name for part in payload.manifest.partitions 121*5a923131SAndroid Build Coastguard Worker ] 122*5a923131SAndroid Build Coastguard Worker if payload.manifest.partial_update: 123*5a923131SAndroid Build Coastguard Worker delta_generator_args.append("--is_partial_update") 124*5a923131SAndroid Build Coastguard Worker if payload.is_incremental: 125*5a923131SAndroid Build Coastguard Worker delta_generator_args.append("--old_partitions=" + ":".join(old_partitions)) 126*5a923131SAndroid Build Coastguard Worker delta_generator_args.append("--partition_names=" + ":".join(partition_names)) 127*5a923131SAndroid Build Coastguard Worker delta_generator_args.append("--new_partitions=" + ":".join(new_partitions)) 128*5a923131SAndroid Build Coastguard Worker 129*5a923131SAndroid Build Coastguard Worker print("Running ", " ".join(delta_generator_args)) 130*5a923131SAndroid Build Coastguard Worker subprocess.check_output(delta_generator_args) 131*5a923131SAndroid Build Coastguard Worker 132*5a923131SAndroid Build Coastguard Worker valid = True 133*5a923131SAndroid Build Coastguard Worker if not target_exist: 134*5a923131SAndroid Build Coastguard Worker for part in new_partitions: 135*5a923131SAndroid Build Coastguard Worker print("Output written to", part) 136*5a923131SAndroid Build Coastguard Worker shutil.copy(part, output_dir) 137*5a923131SAndroid Build Coastguard Worker return 138*5a923131SAndroid Build Coastguard Worker for (expected_part, actual_part, part_name) in \ 139*5a923131SAndroid Build Coastguard Worker zip(expected_new_partitions, new_partitions, partition_names): 140*5a923131SAndroid Build Coastguard Worker if filecmp.cmp(expected_part, actual_part): 141*5a923131SAndroid Build Coastguard Worker print("Partition `{}` is valid".format(part_name)) 142*5a923131SAndroid Build Coastguard Worker else: 143*5a923131SAndroid Build Coastguard Worker valid = False 144*5a923131SAndroid Build Coastguard Worker print( 145*5a923131SAndroid Build Coastguard Worker "Partition `{}` is INVALID expected image: {} actual image: {}" 146*5a923131SAndroid Build Coastguard Worker .format(part_name, expected_part, actual_part)) 147*5a923131SAndroid Build Coastguard Worker 148*5a923131SAndroid Build Coastguard Worker if not valid and sys.stdout.isatty(): 149*5a923131SAndroid Build Coastguard Worker input("Paused to investigate invalid partitions, press any key to exit.") 150*5a923131SAndroid Build Coastguard Worker 151*5a923131SAndroid Build Coastguard Worker 152*5a923131SAndroid Build Coastguard Workerdef main(): 153*5a923131SAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 154*5a923131SAndroid Build Coastguard Worker description="Run host side simulation of OTA package") 155*5a923131SAndroid Build Coastguard Worker parser.add_argument( 156*5a923131SAndroid Build Coastguard Worker "--source", 157*5a923131SAndroid Build Coastguard Worker help="Target file zip for the source build", 158*5a923131SAndroid Build Coastguard Worker required=False) 159*5a923131SAndroid Build Coastguard Worker parser.add_argument( 160*5a923131SAndroid Build Coastguard Worker "--target", 161*5a923131SAndroid Build Coastguard Worker help="Target file zip for the target build", 162*5a923131SAndroid Build Coastguard Worker required=False) 163*5a923131SAndroid Build Coastguard Worker parser.add_argument( 164*5a923131SAndroid Build Coastguard Worker "-o", 165*5a923131SAndroid Build Coastguard Worker dest="output_dir", 166*5a923131SAndroid Build Coastguard Worker help="Output directory to put all images, current directory by default" 167*5a923131SAndroid Build Coastguard Worker ) 168*5a923131SAndroid Build Coastguard Worker parser.add_argument( 169*5a923131SAndroid Build Coastguard Worker "payload", 170*5a923131SAndroid Build Coastguard Worker help="payload.bin for the OTA package, or a zip of OTA package itself", 171*5a923131SAndroid Build Coastguard Worker nargs=1) 172*5a923131SAndroid Build Coastguard Worker args = parser.parse_args() 173*5a923131SAndroid Build Coastguard Worker print(args) 174*5a923131SAndroid Build Coastguard Worker 175*5a923131SAndroid Build Coastguard Worker # pylint: disable=no-member 176*5a923131SAndroid Build Coastguard Worker with tempfile.TemporaryDirectory() as tempdir: 177*5a923131SAndroid Build Coastguard Worker payload_path = args.payload[0] 178*5a923131SAndroid Build Coastguard Worker if zipfile.is_zipfile(payload_path): 179*5a923131SAndroid Build Coastguard Worker with zipfile.ZipFile(payload_path, "r") as zfp: 180*5a923131SAndroid Build Coastguard Worker payload_entry_name = 'payload.bin' 181*5a923131SAndroid Build Coastguard Worker zfp.extract(payload_entry_name, tempdir) 182*5a923131SAndroid Build Coastguard Worker payload_path = os.path.join(tempdir, payload_entry_name) 183*5a923131SAndroid Build Coastguard Worker if args.output_dir is None: 184*5a923131SAndroid Build Coastguard Worker args.output_dir = "." 185*5a923131SAndroid Build Coastguard Worker if not os.path.exists(args.output_dir): 186*5a923131SAndroid Build Coastguard Worker os.makedirs(args.output_dir, exist_ok=True) 187*5a923131SAndroid Build Coastguard Worker assert os.path.isdir(args.output_dir) 188*5a923131SAndroid Build Coastguard Worker run_ota(args.source, args.target, payload_path, tempdir, args.output_dir) 189*5a923131SAndroid Build Coastguard Worker 190*5a923131SAndroid Build Coastguard Worker 191*5a923131SAndroid Build Coastguard Workerif __name__ == '__main__': 192*5a923131SAndroid Build Coastguard Worker main() 193