1#!/usr/bin/env python 2# 3# Copyright (C) 2008 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Given a target-files zipfile, produces an OTA package that installs that build. 19An incremental OTA is produced if -i is given, otherwise a full OTA is produced. 20 21Usage: ota_from_target_files [options] input_target_files output_ota_package 22 23Common options that apply to both of non-A/B and A/B OTAs 24 25 --downgrade 26 Intentionally generate an incremental OTA that updates from a newer build 27 to an older one (e.g. downgrading from P preview back to O MR1). 28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe 29 will always be enforced when using this flag, so "ota-wipe=yes" will also 30 be included in the metadata file. The update-binary in the source build 31 will be used in the OTA package, unless --binary flag is specified. Please 32 also check the comment for --override_timestamp below. 33 34 -i (--incremental_from) <file> 35 Generate an incremental OTA using the given target-files zip as the 36 starting build. 37 38 -k (--package_key) <key> 39 Key to use to sign the package (default is the value of 40 default_system_dev_certificate from the input target-files's 41 META/misc_info.txt, or "build/make/target/product/security/testkey" if 42 that value is not specified). 43 44 For incremental OTAs, the default value is based on the source 45 target-file, not the target build. 46 47 --override_timestamp 48 Intentionally generate an incremental OTA that updates from a newer build 49 to an older one (based on timestamp comparison), by setting the downgrade 50 flag in the package metadata. This differs from --downgrade flag, as we 51 don't enforce a data wipe with this flag. Because we know for sure this is 52 NOT an actual downgrade case, but two builds happen to be cut in a reverse 53 order (e.g. from two branches). A legit use case is that we cut a new 54 build C (after having A and B), but want to enfore an update path of A -> 55 C -> B. Specifying --downgrade may not help since that would enforce a 56 data wipe for C -> B update. 57 58 We used to set a fake timestamp in the package metadata for this flow. But 59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade 60 based on timestamp) with the same "ota-downgrade=yes" flag, with the 61 difference being whether "ota-wipe=yes" is set. 62 63 --wipe_user_data 64 Generate an OTA package that will wipe the user data partition when 65 installed. 66 67 --retrofit_dynamic_partitions 68 Generates an OTA package that updates a device to support dynamic 69 partitions (default False). This flag is implied when generating 70 an incremental OTA where the base build does not support dynamic 71 partitions but the target build does. For A/B, when this flag is set, 72 --skip_postinstall is implied. 73 74 --skip_compatibility_check 75 Skip checking compatibility of the input target files package. 76 77 --output_metadata_path 78 Write a copy of the metadata to a separate file. Therefore, users can 79 read the post build fingerprint without extracting the OTA package. 80 81 --force_non_ab 82 This flag can only be set on an A/B device that also supports non-A/B 83 updates. Implies --two_step. 84 If set, generate that non-A/B update package. 85 If not set, generates A/B package for A/B device and non-A/B package for 86 non-A/B device. 87 88 -o (--oem_settings) <main_file[,additional_files...]> 89 Comma separated list of files used to specify the expected OEM-specific 90 properties on the OEM partition of the intended device. Multiple expected 91 values can be used by providing multiple files. Only the first dict will 92 be used to compute fingerprint, while the rest will be used to assert 93 OEM-specific properties. 94 95Non-A/B OTA specific options 96 97 -b (--binary) <file> 98 Use the given binary as the update-binary in the output package, instead 99 of the binary in the build's target_files. Use for development only. 100 101 --block 102 Generate a block-based OTA for non-A/B device. We have deprecated the 103 support for file-based OTA since O. Block-based OTA will be used by 104 default for all non-A/B devices. Keeping this flag here to not break 105 existing callers. 106 107 -e (--extra_script) <file> 108 Insert the contents of file at the end of the update script. 109 110 --full_bootloader 111 Similar to --full_radio. When generating an incremental OTA, always 112 include a full copy of bootloader image. 113 114 --full_radio 115 When generating an incremental OTA, always include a full copy of radio 116 image. This option is only meaningful when -i is specified, because a full 117 radio is always included in a full OTA if applicable. 118 119 --log_diff <file> 120 Generate a log file that shows the differences in the source and target 121 builds for an incremental package. This option is only meaningful when -i 122 is specified. 123 124 --oem_no_mount 125 For devices with OEM-specific properties but without an OEM partition, do 126 not mount the OEM partition in the updater-script. This should be very 127 rarely used, since it's expected to have a dedicated OEM partition for 128 OEM-specific properties. Only meaningful when -o is specified. 129 130 --stash_threshold <float> 131 Specify the threshold that will be used to compute the maximum allowed 132 stash size (defaults to 0.8). 133 134 -t (--worker_threads) <int> 135 Specify the number of worker-threads that will be used when generating 136 patches for incremental updates (defaults to 3). 137 138 --verify 139 Verify the checksums of the updated system and vendor (if any) partitions. 140 Non-A/B incremental OTAs only. 141 142 -2 (--two_step) 143 Generate a 'two-step' OTA package, where recovery is updated first, so 144 that any changes made to the system partition are done using the new 145 recovery (new kernel, etc.). 146 147A/B OTA specific options 148 149 --disable_fec_computation 150 Disable the on device FEC data computation for incremental updates. 151 152 --include_secondary 153 Additionally include the payload for secondary slot images (default: 154 False). Only meaningful when generating A/B OTAs. 155 156 By default, an A/B OTA package doesn't contain the images for the 157 secondary slot (e.g. system_other.img). Specifying this flag allows 158 generating a separate payload that will install secondary slot images. 159 160 Such a package needs to be applied in a two-stage manner, with a reboot 161 in-between. During the first stage, the updater applies the primary 162 payload only. Upon finishing, it reboots the device into the newly updated 163 slot. It then continues to install the secondary payload to the inactive 164 slot, but without switching the active slot at the end (needs the matching 165 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag). 166 167 Due to the special install procedure, the secondary payload will be always 168 generated as a full payload. 169 170 --payload_signer <signer> 171 Specify the signer when signing the payload and metadata for A/B OTAs. 172 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign 173 with the package private key. If the private key cannot be accessed 174 directly, a payload signer that knows how to do that should be specified. 175 The signer will be supplied with "-inkey <path_to_key>", 176 "-in <input_file>" and "-out <output_file>" parameters. 177 178 --payload_signer_args <args> 179 Specify the arguments needed for payload signer. 180 181 --payload_signer_maximum_signature_size <signature_size> 182 The maximum signature size (in bytes) that would be generated by the given 183 payload signer. Only meaningful when custom payload signer is specified 184 via '--payload_signer'. 185 If the signer uses a RSA key, this should be the number of bytes to 186 represent the modulus. If it uses an EC key, this is the size of a 187 DER-encoded ECDSA signature. 188 189 --payload_signer_key_size <key_size> 190 Deprecated. Use the '--payload_signer_maximum_signature_size' instead. 191 192 --boot_variable_file <path> 193 A file that contains the possible values of ro.boot.* properties. It's 194 used to calculate the possible runtime fingerprints when some 195 ro.product.* properties are overridden by the 'import' statement. 196 The file expects one property per line, and each line has the following 197 format: 'prop_name=value1,value2'. e.g. 'ro.boot.product.sku=std,pro' 198 199 --skip_postinstall 200 Skip the postinstall hooks when generating an A/B OTA package (default: 201 False). Note that this discards ALL the hooks, including non-optional 202 ones. Should only be used if caller knows it's safe to do so (e.g. all the 203 postinstall work is to dexopt apps and a data wipe will happen immediately 204 after). Only meaningful when generating A/B OTAs. 205 206 --partial "<PARTITION> [<PARTITION>[...]]" 207 Generate partial updates, overriding ab_partitions list with the given 208 list. Specify --partial= without partition list to let tooling auto detect 209 partial partition list. 210 211 --custom_image <custom_partition=custom_image> 212 Use the specified custom_image to update custom_partition when generating 213 an A/B OTA package. e.g. "--custom_image oem=oem.img --custom_image 214 cus=cus_test.img" 215 216 --disable_vabc 217 Disable Virtual A/B Compression, for builds that have compression enabled 218 by default. 219 220 --vabc_downgrade 221 Don't disable Virtual A/B Compression for downgrading OTAs. 222 For VABC downgrades, we must finish merging before doing data wipe, and 223 since data wipe is required for downgrading OTA, this might cause long 224 wait time in recovery. 225 226 --enable_vabc_xor 227 Enable the VABC xor feature. Will reduce space requirements for OTA 228 229 --force_minor_version 230 Override the update_engine minor version for delta generation. 231 232 --compressor_types 233 A colon ':' separated list of compressors. Allowed values are bz2 and brotli. 234 235 --enable_zucchini 236 Whether to enable to zucchini feature. Will generate smaller OTA but uses more memory. 237 238 --enable_lz4diff 239 Whether to enable lz4diff feature. Will generate smaller OTA for EROFS but 240 uses more memory. 241 242 --spl_downgrade 243 Force generate an SPL downgrade OTA. Only needed if target build has an 244 older SPL. 245 246 --vabc_compression_param 247 Compression algorithm to be used for VABC. Available options: gz, brotli, none 248 249 --security_patch_level 250 Override the security patch level in target files 251 252 --max_threads 253 Specify max number of threads allowed when generating A/B OTA 254""" 255 256from __future__ import print_function 257 258import logging 259import multiprocessing 260import os 261import os.path 262import re 263import shlex 264import shutil 265import subprocess 266import sys 267import zipfile 268 269import care_map_pb2 270import common 271import ota_utils 272from ota_utils import (UNZIP_PATTERN, FinalizeMetadata, GetPackageMetadata, 273 PayloadGenerator, SECURITY_PATCH_LEVEL_PROP_NAME, CopyTargetFilesDir, VABC_COMPRESSION_PARAM_SUPPORT) 274from common import DoesInputFileContain, IsSparseImage 275import target_files_diff 276from check_target_files_vintf import CheckVintfIfTrebleEnabled 277from non_ab_ota import GenerateNonAbOtaPackage 278from payload_signer import PayloadSigner 279 280if sys.hexversion < 0x02070000: 281 print("Python 2.7 or newer is required.", file=sys.stderr) 282 sys.exit(1) 283 284logger = logging.getLogger(__name__) 285 286OPTIONS = ota_utils.OPTIONS 287OPTIONS.verify = False 288OPTIONS.patch_threshold = 0.95 289OPTIONS.wipe_user_data = False 290OPTIONS.extra_script = None 291OPTIONS.worker_threads = multiprocessing.cpu_count() // 2 292if OPTIONS.worker_threads == 0: 293 OPTIONS.worker_threads = 1 294OPTIONS.two_step = False 295OPTIONS.include_secondary = False 296OPTIONS.block_based = True 297OPTIONS.updater_binary = None 298OPTIONS.oem_dicts = None 299OPTIONS.oem_source = None 300OPTIONS.oem_no_mount = False 301OPTIONS.full_radio = False 302OPTIONS.full_bootloader = False 303# Stash size cannot exceed cache_size * threshold. 304OPTIONS.cache_size = None 305OPTIONS.stash_threshold = 0.8 306OPTIONS.log_diff = None 307OPTIONS.payload_signer = None 308OPTIONS.payload_signer_args = [] 309OPTIONS.payload_signer_maximum_signature_size = None 310OPTIONS.extracted_input = None 311OPTIONS.skip_postinstall = False 312OPTIONS.skip_compatibility_check = False 313OPTIONS.disable_fec_computation = False 314OPTIONS.disable_verity_computation = False 315OPTIONS.partial = None 316OPTIONS.custom_images = {} 317OPTIONS.disable_vabc = False 318OPTIONS.spl_downgrade = False 319OPTIONS.vabc_downgrade = False 320OPTIONS.enable_vabc_xor = True 321OPTIONS.force_minor_version = None 322OPTIONS.compressor_types = None 323OPTIONS.enable_zucchini = True 324OPTIONS.enable_lz4diff = False 325OPTIONS.vabc_compression_param = None 326OPTIONS.security_patch_level = None 327OPTIONS.max_threads = None 328 329 330POSTINSTALL_CONFIG = 'META/postinstall_config.txt' 331DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt' 332AB_PARTITIONS = 'META/ab_partitions.txt' 333 334# Files to be unzipped for target diffing purpose. 335TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*', 336 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*', 337 'VENDOR_DLKM/*', 'ODM_DLKM/*', 'SYSTEM_DLKM/*'] 338RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS] 339 340# Images to be excluded from secondary payload. We essentially only keep 341# 'system_other' and bootloader partitions. 342SECONDARY_PAYLOAD_SKIPPED_IMAGES = [ 343 'boot', 'dtbo', 'modem', 'odm', 'odm_dlkm', 'product', 'radio', 'recovery', 344 'system_dlkm', 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 345 'vendor', 'vendor_boot'] 346 347 348def _LoadOemDicts(oem_source): 349 """Returns the list of loaded OEM properties dict.""" 350 if not oem_source: 351 return None 352 353 oem_dicts = [] 354 for oem_file in oem_source: 355 oem_dicts.append(common.LoadDictionaryFromFile(oem_file)) 356 return oem_dicts 357 358 359def ModifyVABCCompressionParam(content, algo): 360 """ Update update VABC Compression Param in dynamic_partitions_info.txt 361 Args: 362 content: The string content of dynamic_partitions_info.txt 363 algo: The compression algorithm should be used for VABC. See 364 https://cs.android.com/android/platform/superproject/+/master:system/core/fs_mgr/libsnapshot/cow_writer.cpp;l=127;bpv=1;bpt=1?q=CowWriter::ParseOptions&sq= 365 Returns: 366 Updated content of dynamic_partitions_info.txt , with custom compression algo 367 """ 368 output_list = [] 369 for line in content.splitlines(): 370 if line.startswith("virtual_ab_compression_method="): 371 continue 372 output_list.append(line) 373 output_list.append("virtual_ab_compression_method="+algo) 374 return "\n".join(output_list) 375 376 377def UpdatesInfoForSpecialUpdates(content, partitions_filter, 378 delete_keys=None): 379 """ Updates info file for secondary payload generation, partial update, etc. 380 381 Scan each line in the info file, and remove the unwanted partitions from 382 the dynamic partition list in the related properties. e.g. 383 "super_google_dynamic_partitions_partition_list=system vendor product" 384 will become "super_google_dynamic_partitions_partition_list=system". 385 386 Args: 387 content: The content of the input info file. e.g. misc_info.txt. 388 partitions_filter: A function to filter the desired partitions from a given 389 list 390 delete_keys: A list of keys to delete in the info file 391 392 Returns: 393 A string of the updated info content. 394 """ 395 396 output_list = [] 397 # The suffix in partition_list variables that follows the name of the 398 # partition group. 399 list_suffix = 'partition_list' 400 for line in content.splitlines(): 401 if line.startswith('#') or '=' not in line: 402 output_list.append(line) 403 continue 404 key, value = line.strip().split('=', 1) 405 406 if delete_keys and key in delete_keys: 407 pass 408 elif key.endswith(list_suffix): 409 partitions = value.split() 410 # TODO for partial update, partitions in the same group must be all 411 # updated or all omitted 412 partitions = filter(partitions_filter, partitions) 413 output_list.append('{}={}'.format(key, ' '.join(partitions))) 414 else: 415 output_list.append(line) 416 return '\n'.join(output_list) 417 418 419def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False): 420 """Returns a target-files.zip file for generating secondary payload. 421 422 Although the original target-files.zip already contains secondary slot 423 images (i.e. IMAGES/system_other.img), we need to rename the files to the 424 ones without _other suffix. Note that we cannot instead modify the names in 425 META/ab_partitions.txt, because there are no matching partitions on device. 426 427 For the partitions that don't have secondary images, the ones for primary 428 slot will be used. This is to ensure that we always have valid boot, vbmeta, 429 bootloader images in the inactive slot. 430 431 After writing system_other to inactive slot's system partiiton, 432 PackageManagerService will read `ro.cp_system_other_odex`, and set 433 `sys.cppreopt` to "requested". Then, according to 434 system/extras/cppreopts/cppreopts.rc , init will mount system_other at 435 /postinstall, and execute `cppreopts` to copy optimized APKs from 436 /postinstall to /data . 437 438 Args: 439 input_file: The input target-files.zip file. 440 skip_postinstall: Whether to skip copying the postinstall config file. 441 442 Returns: 443 The filename of the target-files.zip for generating secondary payload. 444 """ 445 446 def GetInfoForSecondaryImages(info_file): 447 """Updates info file for secondary payload generation.""" 448 with open(info_file) as f: 449 content = f.read() 450 # Remove virtual_ab flag from secondary payload so that OTA client 451 # don't use snapshots for secondary update 452 delete_keys = ['virtual_ab', "virtual_ab_retrofit"] 453 return UpdatesInfoForSpecialUpdates( 454 content, lambda p: p not in SECONDARY_PAYLOAD_SKIPPED_IMAGES, 455 delete_keys) 456 457 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 458 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True) 459 460 with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip: 461 infolist = input_zip.infolist() 462 463 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN) 464 for info in infolist: 465 unzipped_file = os.path.join(input_tmp, *info.filename.split('/')) 466 if info.filename == 'IMAGES/system_other.img': 467 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img') 468 469 # Primary images and friends need to be skipped explicitly. 470 elif info.filename in ('IMAGES/system.img', 471 'IMAGES/system.map'): 472 pass 473 474 # Copy images that are not in SECONDARY_PAYLOAD_SKIPPED_IMAGES. 475 elif info.filename.startswith(('IMAGES/', 'RADIO/')): 476 image_name = os.path.basename(info.filename) 477 if image_name not in ['{}.img'.format(partition) for partition in 478 SECONDARY_PAYLOAD_SKIPPED_IMAGES]: 479 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename) 480 481 # Skip copying the postinstall config if requested. 482 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG: 483 pass 484 485 elif info.filename.startswith('META/'): 486 # Remove the unnecessary partitions for secondary images from the 487 # ab_partitions file. 488 if info.filename == AB_PARTITIONS: 489 with open(unzipped_file) as f: 490 partition_list = f.read().splitlines() 491 partition_list = [partition for partition in partition_list if partition 492 and partition not in SECONDARY_PAYLOAD_SKIPPED_IMAGES] 493 common.ZipWriteStr(target_zip, info.filename, 494 '\n'.join(partition_list)) 495 # Remove the unnecessary partitions from the dynamic partitions list. 496 elif (info.filename == 'META/misc_info.txt' or 497 info.filename == DYNAMIC_PARTITION_INFO): 498 modified_info = GetInfoForSecondaryImages(unzipped_file) 499 common.ZipWriteStr(target_zip, info.filename, modified_info) 500 else: 501 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename) 502 503 common.ZipClose(target_zip) 504 505 return target_file 506 507 508def GetTargetFilesZipWithoutPostinstallConfig(input_file): 509 """Returns a target-files.zip that's not containing postinstall_config.txt. 510 511 This allows brillo_update_payload script to skip writing all the postinstall 512 hooks in the generated payload. The input target-files.zip file will be 513 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't 514 contain the postinstall_config.txt entry, the input file will be returned. 515 516 Args: 517 input_file: The input target-files.zip filename. 518 519 Returns: 520 The filename of target-files.zip that doesn't contain postinstall config. 521 """ 522 # We should only make a copy if postinstall_config entry exists. 523 with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip: 524 if POSTINSTALL_CONFIG not in input_zip.namelist(): 525 return input_file 526 527 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 528 shutil.copyfile(input_file, target_file) 529 common.ZipDelete(target_file, POSTINSTALL_CONFIG) 530 return target_file 531 532 533def ParseInfoDict(target_file_path): 534 with zipfile.ZipFile(target_file_path, 'r', allowZip64=True) as zfp: 535 return common.LoadInfoDict(zfp) 536 537 538def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param): 539 """Returns a target-files.zip with a custom VABC compression param. 540 Args: 541 input_file: The input target-files.zip path 542 vabc_compression_param: Custom Virtual AB Compression algorithm 543 544 Returns: 545 The path to modified target-files.zip 546 """ 547 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 548 shutil.copyfile(input_file, target_file) 549 common.ZipDelete(target_file, DYNAMIC_PARTITION_INFO) 550 with zipfile.ZipFile(input_file, 'r', allowZip64=True) as zfp: 551 dynamic_partition_info = zfp.read(DYNAMIC_PARTITION_INFO).decode() 552 dynamic_partition_info = ModifyVABCCompressionParam( 553 dynamic_partition_info, vabc_compression_param) 554 with zipfile.ZipFile(target_file, "a", allowZip64=True) as output_zip: 555 output_zip.writestr(DYNAMIC_PARTITION_INFO, dynamic_partition_info) 556 return target_file 557 558 559def GetTargetFilesZipForPartialUpdates(input_file, ab_partitions): 560 """Returns a target-files.zip for partial ota update package generation. 561 562 This function modifies ab_partitions list with the desired partitions before 563 calling the brillo_update_payload script. It also cleans up the reference to 564 the excluded partitions in the info file, e.g misc_info.txt. 565 566 Args: 567 input_file: The input target-files.zip filename. 568 ab_partitions: A list of partitions to include in the partial update 569 570 Returns: 571 The filename of target-files.zip used for partial ota update. 572 """ 573 574 def AddImageForPartition(partition_name): 575 """Add the archive name for a given partition to the copy list.""" 576 for prefix in ['IMAGES', 'RADIO']: 577 image_path = '{}/{}.img'.format(prefix, partition_name) 578 if image_path in namelist: 579 copy_entries.append(image_path) 580 map_path = '{}/{}.map'.format(prefix, partition_name) 581 if map_path in namelist: 582 copy_entries.append(map_path) 583 return 584 585 raise ValueError("Cannot find {} in input zipfile".format(partition_name)) 586 587 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip: 588 original_ab_partitions = input_zip.read( 589 AB_PARTITIONS).decode().splitlines() 590 namelist = input_zip.namelist() 591 592 unrecognized_partitions = [partition for partition in ab_partitions if 593 partition not in original_ab_partitions] 594 if unrecognized_partitions: 595 raise ValueError("Unrecognized partitions when generating partial updates", 596 unrecognized_partitions) 597 598 logger.info("Generating partial updates for %s", ab_partitions) 599 600 copy_entries = ['META/update_engine_config.txt'] 601 for partition_name in ab_partitions: 602 AddImageForPartition(partition_name) 603 604 # Use zip2zip to avoid extracting the zipfile. 605 partial_target_file = common.MakeTempFile(suffix='.zip') 606 cmd = ['zip2zip', '-i', input_file, '-o', partial_target_file] 607 cmd.extend(['{}:{}'.format(name, name) for name in copy_entries]) 608 common.RunAndCheckOutput(cmd) 609 610 partial_target_zip = zipfile.ZipFile(partial_target_file, 'a', 611 allowZip64=True) 612 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip: 613 common.ZipWriteStr(partial_target_zip, 'META/ab_partitions.txt', 614 '\n'.join(ab_partitions)) 615 CARE_MAP_ENTRY = "META/care_map.pb" 616 if CARE_MAP_ENTRY in input_zip.namelist(): 617 caremap = care_map_pb2.CareMap() 618 caremap.ParseFromString(input_zip.read(CARE_MAP_ENTRY)) 619 filtered = [ 620 part for part in caremap.partitions if part.name in ab_partitions] 621 del caremap.partitions[:] 622 caremap.partitions.extend(filtered) 623 common.ZipWriteStr(partial_target_zip, CARE_MAP_ENTRY, 624 caremap.SerializeToString()) 625 626 for info_file in ['META/misc_info.txt', DYNAMIC_PARTITION_INFO]: 627 if info_file not in input_zip.namelist(): 628 logger.warning('Cannot find %s in input zipfile', info_file) 629 continue 630 content = input_zip.read(info_file).decode() 631 modified_info = UpdatesInfoForSpecialUpdates( 632 content, lambda p: p in ab_partitions) 633 if OPTIONS.vabc_compression_param and info_file == DYNAMIC_PARTITION_INFO: 634 modified_info = ModifyVABCCompressionParam( 635 modified_info, OPTIONS.vabc_compression_param) 636 common.ZipWriteStr(partial_target_zip, info_file, modified_info) 637 638 # TODO(xunchang) handle META/postinstall_config.txt' 639 640 common.ZipClose(partial_target_zip) 641 642 return partial_target_file 643 644 645def GetTargetFilesZipForRetrofitDynamicPartitions(input_file, 646 super_block_devices, 647 dynamic_partition_list): 648 """Returns a target-files.zip for retrofitting dynamic partitions. 649 650 This allows brillo_update_payload to generate an OTA based on the exact 651 bits on the block devices. Postinstall is disabled. 652 653 Args: 654 input_file: The input target-files.zip filename. 655 super_block_devices: The list of super block devices 656 dynamic_partition_list: The list of dynamic partitions 657 658 Returns: 659 The filename of target-files.zip with *.img replaced with super_*.img for 660 each block device in super_block_devices. 661 """ 662 assert super_block_devices, "No super_block_devices are specified." 663 664 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev) 665 for dev in super_block_devices} 666 667 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 668 shutil.copyfile(input_file, target_file) 669 670 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip: 671 namelist = input_zip.namelist() 672 673 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN) 674 675 # Remove partitions from META/ab_partitions.txt that is in 676 # dynamic_partition_list but not in super_block_devices so that 677 # brillo_update_payload won't generate update for those logical partitions. 678 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/')) 679 with open(ab_partitions_file) as f: 680 ab_partitions_lines = f.readlines() 681 ab_partitions = [line.strip() for line in ab_partitions_lines] 682 # Assert that all super_block_devices are in ab_partitions 683 super_device_not_updated = [partition for partition in super_block_devices 684 if partition not in ab_partitions] 685 assert not super_device_not_updated, \ 686 "{} is in super_block_devices but not in {}".format( 687 super_device_not_updated, AB_PARTITIONS) 688 # ab_partitions -= (dynamic_partition_list - super_block_devices) 689 new_ab_partitions = common.MakeTempFile( 690 prefix="ab_partitions", suffix=".txt") 691 with open(new_ab_partitions, 'w') as f: 692 for partition in ab_partitions: 693 if (partition in dynamic_partition_list and 694 partition not in super_block_devices): 695 logger.info("Dropping %s from ab_partitions.txt", partition) 696 continue 697 f.write(partition + "\n") 698 to_delete = [AB_PARTITIONS] 699 700 # Always skip postinstall for a retrofit update. 701 to_delete += [POSTINSTALL_CONFIG] 702 703 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this 704 # is a regular update on devices without dynamic partitions support. 705 to_delete += [DYNAMIC_PARTITION_INFO] 706 707 # Remove the existing partition images as well as the map files. 708 to_delete += list(replace.values()) 709 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices] 710 711 common.ZipDelete(target_file, to_delete) 712 713 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True) 714 715 # Write super_{foo}.img as {foo}.img. 716 for src, dst in replace.items(): 717 assert src in namelist, \ 718 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst) 719 unzipped_file = os.path.join(input_tmp, *src.split('/')) 720 common.ZipWrite(target_zip, unzipped_file, arcname=dst) 721 722 # Write new ab_partitions.txt file 723 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS) 724 725 common.ZipClose(target_zip) 726 727 return target_file 728 729 730def GetTargetFilesZipForCustomImagesUpdates(input_file, custom_images): 731 """Returns a target-files.zip for custom partitions update. 732 733 This function modifies ab_partitions list with the desired custom partitions 734 and puts the custom images into the target target-files.zip. 735 736 Args: 737 input_file: The input target-files.zip filename. 738 custom_images: A map of custom partitions and custom images. 739 740 Returns: 741 The filename of a target-files.zip which has renamed the custom images in 742 the IMAGES/ to their partition names. 743 """ 744 745 # First pass: use zip2zip to copy the target files contents, excluding 746 # the "custom" images that will be replaced. 747 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 748 cmd = ['zip2zip', '-i', input_file, '-o', target_file] 749 750 images = {} 751 for custom_partition, custom_image in custom_images.items(): 752 default_custom_image = '{}.img'.format(custom_partition) 753 if default_custom_image != custom_image: 754 src = 'IMAGES/' + custom_image 755 dst = 'IMAGES/' + default_custom_image 756 cmd.extend(['-x', dst]) 757 images[dst] = src 758 759 common.RunAndCheckOutput(cmd) 760 761 # Second pass: write {custom_image}.img as {custom_partition}.img. 762 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip: 763 with zipfile.ZipFile(target_file, 'a', allowZip64=True) as output_zip: 764 for dst, src in images.items(): 765 data = input_zip.read(src) 766 logger.info("Update custom partition '%s'", dst) 767 common.ZipWriteStr(output_zip, dst, data) 768 output_zip.close() 769 770 return target_file 771 772 773def GeneratePartitionTimestampFlags(partition_state): 774 partition_timestamps = [ 775 part.partition_name + ":" + part.version 776 for part in partition_state] 777 return ["--partition_timestamps", ",".join(partition_timestamps)] 778 779 780def GeneratePartitionTimestampFlagsDowngrade( 781 pre_partition_state, post_partition_state): 782 assert pre_partition_state is not None 783 partition_timestamps = {} 784 for part in post_partition_state: 785 partition_timestamps[part.partition_name] = part.version 786 for part in pre_partition_state: 787 if part.partition_name in partition_timestamps: 788 partition_timestamps[part.partition_name] = \ 789 max(part.version, partition_timestamps[part.partition_name]) 790 return [ 791 "--partition_timestamps", 792 ",".join([key + ":" + val for (key, val) 793 in partition_timestamps.items()]) 794 ] 795 796 797def SupportsMainlineGkiUpdates(target_file): 798 """Return True if the build supports MainlineGKIUpdates. 799 800 This function scans the product.img file in IMAGES/ directory for 801 pattern |*/apex/com.android.gki.*.apex|. If there are files 802 matching this pattern, conclude that build supports mainline 803 GKI and return True 804 805 Args: 806 target_file: Path to a target_file.zip, or an extracted directory 807 Return: 808 True if thisb uild supports Mainline GKI Updates. 809 """ 810 if target_file is None: 811 return False 812 if os.path.isfile(target_file): 813 target_file = common.UnzipTemp(target_file, ["IMAGES/product.img"]) 814 if not os.path.isdir(target_file): 815 assert os.path.isdir(target_file), \ 816 "{} must be a path to zip archive or dir containing extracted"\ 817 " target_files".format(target_file) 818 image_file = os.path.join(target_file, "IMAGES", "product.img") 819 820 if not os.path.isfile(image_file): 821 return False 822 823 if IsSparseImage(image_file): 824 # Unsparse the image 825 tmp_img = common.MakeTempFile(suffix=".img") 826 subprocess.check_output(["simg2img", image_file, tmp_img]) 827 image_file = tmp_img 828 829 cmd = ["debugfs_static", "-R", "ls -p /apex", image_file] 830 output = subprocess.check_output(cmd).decode() 831 832 pattern = re.compile(r"com\.android\.gki\..*\.apex") 833 return pattern.search(output) is not None 834 835 836def GenerateAbOtaPackage(target_file, output_file, source_file=None): 837 """Generates an Android OTA package that has A/B update payload.""" 838 # If input target_files are directories, create a copy so that we can modify 839 # them directly 840 if os.path.isdir(target_file): 841 target_file = CopyTargetFilesDir(target_file) 842 if source_file is not None and os.path.isdir(source_file): 843 source_file = CopyTargetFilesDir(source_file) 844 # Stage the output zip package for package signing. 845 if not OPTIONS.no_signing: 846 staging_file = common.MakeTempFile(suffix='.zip') 847 else: 848 staging_file = output_file 849 output_zip = zipfile.ZipFile(staging_file, "w", 850 compression=zipfile.ZIP_DEFLATED, 851 allowZip64=True) 852 853 if source_file is not None: 854 source_file = ota_utils.ExtractTargetFiles(source_file) 855 assert "ab_partitions" in OPTIONS.source_info_dict, \ 856 "META/ab_partitions.txt is required for ab_update." 857 assert "ab_partitions" in OPTIONS.target_info_dict, \ 858 "META/ab_partitions.txt is required for ab_update." 859 target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) 860 source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) 861 # If source supports VABC, delta_generator/update_engine will attempt to 862 # use VABC. This dangerous, as the target build won't have snapuserd to 863 # serve I/O request when device boots. Therefore, disable VABC if source 864 # build doesn't supports it. 865 if not source_info.is_vabc or not target_info.is_vabc: 866 logger.info("Either source or target does not support VABC, disabling.") 867 OPTIONS.disable_vabc = True 868 if source_info.vabc_compression_param != target_info.vabc_compression_param: 869 logger.info("Source build and target build use different compression methods {} vs {}, default to source builds parameter {}".format( 870 source_info.vabc_compression_param, target_info.vabc_compression_param, source_info.vabc_compression_param)) 871 OPTIONS.vabc_compression_param = source_info.vabc_compression_param 872 873 # Virtual AB Compression was introduced in Androd S. 874 # Later, we backported VABC to Android R. But verity support was not 875 # backported, so if VABC is used and we are on Android R, disable 876 # verity computation. 877 if not OPTIONS.disable_vabc and source_info.is_android_r: 878 OPTIONS.disable_verity_computation = True 879 OPTIONS.disable_fec_computation = True 880 881 else: 882 assert "ab_partitions" in OPTIONS.info_dict, \ 883 "META/ab_partitions.txt is required for ab_update." 884 target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) 885 source_info = None 886 if target_info.vabc_compression_param: 887 minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[ 888 target_info.vabc_compression_param] 889 if target_info.vendor_api_level < minimum_api_level_required: 890 logger.warning( 891 "This full OTA is configured to use VABC compression algorithm" 892 " {}, which is supported since" 893 " Android API level {}, but device is " 894 "launched with {} . If this full OTA is" 895 " served to a device running old build, OTA might fail due to " 896 "unsupported compression parameter. For safety, gz is used because " 897 "it's supported since day 1.".format( 898 target_info.vabc_compression_param, 899 minimum_api_level_required, 900 target_info.vendor_api_level)) 901 OPTIONS.vabc_compression_param = "gz" 902 903 if OPTIONS.partial == []: 904 logger.info( 905 "Automatically detecting partial partition list from input target files.") 906 OPTIONS.partial = target_info.get( 907 "partial_ota_update_partitions_list").split() 908 assert OPTIONS.partial, "Input target_file does not have" 909 " partial_ota_update_partitions_list defined, failed to auto detect partial" 910 " partition list. Please specify list of partitions to update manually via" 911 " --partial=a,b,c , or generate a complete OTA by removing the --partial" 912 " option" 913 OPTIONS.partial.sort() 914 if source_info: 915 source_partial_list = source_info.get( 916 "partial_ota_update_partitions_list").split() 917 if source_partial_list: 918 source_partial_list.sort() 919 if source_partial_list != OPTIONS.partial: 920 logger.warning("Source build and target build have different partial partition lists. Source: %s, target: %s, taking the intersection.", 921 source_partial_list, OPTIONS.partial) 922 OPTIONS.partial = list( 923 set(OPTIONS.partial) and set(source_partial_list)) 924 OPTIONS.partial.sort() 925 logger.info("Automatically deduced partial partition list: %s", 926 OPTIONS.partial) 927 928 if target_info.vendor_suppressed_vabc: 929 logger.info("Vendor suppressed VABC. Disabling") 930 OPTIONS.disable_vabc = True 931 932 # Both source and target build need to support VABC XOR for us to use it. 933 # Source build's update_engine must be able to write XOR ops, and target 934 # build's snapuserd must be able to interpret XOR ops. 935 if not target_info.is_vabc_xor or OPTIONS.disable_vabc or \ 936 (source_info is not None and not source_info.is_vabc_xor): 937 logger.info("VABC XOR Not supported, disabling") 938 OPTIONS.enable_vabc_xor = False 939 940 if OPTIONS.vabc_compression_param == "none": 941 logger.info( 942 "VABC Compression algorithm is set to 'none', disabling VABC xor") 943 OPTIONS.enable_vabc_xor = False 944 945 if OPTIONS.enable_vabc_xor: 946 api_level = -1 947 if source_info is not None: 948 api_level = source_info.vendor_api_level 949 if api_level == -1: 950 api_level = target_info.vendor_api_level 951 952 # XOR is only supported on T and higher. 953 if api_level < 33: 954 logger.error("VABC XOR not supported on this vendor, disabling") 955 OPTIONS.enable_vabc_xor = False 956 957 additional_args = [] 958 959 # Prepare custom images. 960 if OPTIONS.custom_images: 961 target_file = GetTargetFilesZipForCustomImagesUpdates( 962 target_file, OPTIONS.custom_images) 963 964 if OPTIONS.retrofit_dynamic_partitions: 965 target_file = GetTargetFilesZipForRetrofitDynamicPartitions( 966 target_file, target_info.get("super_block_devices").strip().split(), 967 target_info.get("dynamic_partition_list").strip().split()) 968 elif OPTIONS.partial: 969 target_file = GetTargetFilesZipForPartialUpdates(target_file, 970 OPTIONS.partial) 971 elif OPTIONS.vabc_compression_param: 972 target_file = GetTargetFilesZipForCustomVABCCompression( 973 target_file, OPTIONS.vabc_compression_param) 974 elif OPTIONS.skip_postinstall: 975 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file) 976 # Target_file may have been modified, reparse ab_partitions 977 target_info.info_dict['ab_partitions'] = common.ReadFromInputFile(target_file, 978 AB_PARTITIONS).strip().split("\n") 979 980 CheckVintfIfTrebleEnabled(target_file, target_info) 981 982 # Metadata to comply with Android OTA package format. 983 metadata = GetPackageMetadata(target_info, source_info) 984 # Generate payload. 985 payload = PayloadGenerator( 986 wipe_user_data=OPTIONS.wipe_user_data, minor_version=OPTIONS.force_minor_version, is_partial_update=OPTIONS.partial) 987 988 partition_timestamps_flags = [] 989 # Enforce a max timestamp this payload can be applied on top of. 990 if OPTIONS.downgrade: 991 max_timestamp = source_info.GetBuildProp("ro.build.date.utc") 992 partition_timestamps_flags = GeneratePartitionTimestampFlagsDowngrade( 993 metadata.precondition.partition_state, 994 metadata.postcondition.partition_state 995 ) 996 else: 997 max_timestamp = str(metadata.postcondition.timestamp) 998 partition_timestamps_flags = GeneratePartitionTimestampFlags( 999 metadata.postcondition.partition_state) 1000 1001 if not ota_utils.IsZucchiniCompatible(source_file, target_file): 1002 logger.warning( 1003 "Builds doesn't support zucchini, or source/target don't have compatible zucchini versions. Disabling zucchini.") 1004 OPTIONS.enable_zucchini = False 1005 1006 security_patch_level = target_info.GetBuildProp( 1007 "ro.build.version.security_patch") 1008 if OPTIONS.security_patch_level is not None: 1009 security_patch_level = OPTIONS.security_patch_level 1010 1011 additional_args += ["--security_patch_level", security_patch_level] 1012 1013 if OPTIONS.max_threads: 1014 additional_args += ["--max_threads", OPTIONS.max_threads] 1015 1016 additional_args += ["--enable_zucchini=" + 1017 str(OPTIONS.enable_zucchini).lower()] 1018 1019 if not ota_utils.IsLz4diffCompatible(source_file, target_file): 1020 logger.warning( 1021 "Source build doesn't support lz4diff, or source/target don't have compatible lz4diff versions. Disabling lz4diff.") 1022 OPTIONS.enable_lz4diff = False 1023 1024 additional_args += ["--enable_lz4diff=" + 1025 str(OPTIONS.enable_lz4diff).lower()] 1026 1027 if source_file and OPTIONS.enable_lz4diff: 1028 input_tmp = common.UnzipTemp(source_file, ["META/liblz4.so"]) 1029 liblz4_path = os.path.join(input_tmp, "META", "liblz4.so") 1030 assert os.path.exists( 1031 liblz4_path), "liblz4.so not found in META/ dir of target file {}".format(liblz4_path) 1032 logger.info("Enabling lz4diff %s", liblz4_path) 1033 additional_args += ["--liblz4_path", liblz4_path] 1034 erofs_compression_param = OPTIONS.target_info_dict.get( 1035 "erofs_default_compressor") 1036 assert erofs_compression_param is not None, "'erofs_default_compressor' not found in META/misc_info.txt of target build. This is required to enable lz4diff." 1037 additional_args += ["--erofs_compression_param", erofs_compression_param] 1038 1039 if OPTIONS.disable_vabc: 1040 additional_args += ["--disable_vabc=true"] 1041 if OPTIONS.enable_vabc_xor: 1042 additional_args += ["--enable_vabc_xor=true"] 1043 if OPTIONS.compressor_types: 1044 additional_args += ["--compressor_types", OPTIONS.compressor_types] 1045 additional_args += ["--max_timestamp", max_timestamp] 1046 1047 payload.Generate( 1048 target_file, 1049 source_file, 1050 additional_args + partition_timestamps_flags 1051 ) 1052 1053 # Sign the payload. 1054 pw = OPTIONS.key_passwords[OPTIONS.package_key] 1055 payload_signer = PayloadSigner( 1056 OPTIONS.package_key, OPTIONS.private_key_suffix, 1057 pw, OPTIONS.payload_signer) 1058 payload.Sign(payload_signer) 1059 1060 # Write the payload into output zip. 1061 payload.WriteToZip(output_zip) 1062 1063 # Generate and include the secondary payload that installs secondary images 1064 # (e.g. system_other.img). 1065 if OPTIONS.include_secondary: 1066 # We always include a full payload for the secondary slot, even when 1067 # building an incremental OTA. See the comments for "--include_secondary". 1068 secondary_target_file = GetTargetFilesZipForSecondaryImages( 1069 target_file, OPTIONS.skip_postinstall) 1070 secondary_payload = PayloadGenerator(secondary=True) 1071 secondary_payload.Generate(secondary_target_file, 1072 additional_args=["--max_timestamp", 1073 max_timestamp]) 1074 secondary_payload.Sign(payload_signer) 1075 secondary_payload.WriteToZip(output_zip) 1076 1077 # If dm-verity is supported for the device, copy contents of care_map 1078 # into A/B OTA package. 1079 if target_info.get("avb_enable") == "true": 1080 # Adds care_map if either the protobuf format or the plain text one exists. 1081 for care_map_name in ["care_map.pb", "care_map.txt"]: 1082 if not DoesInputFileContain(target_file, "META/" + care_map_name): 1083 continue 1084 care_map_data = common.ReadBytesFromInputFile( 1085 target_file, "META/" + care_map_name) 1086 # In order to support streaming, care_map needs to be packed as 1087 # ZIP_STORED. 1088 common.ZipWriteStr(output_zip, care_map_name, care_map_data, 1089 compress_type=zipfile.ZIP_STORED) 1090 else: 1091 logger.warning("Cannot find care map file in target_file package") 1092 1093 # Add the source apex version for incremental ota updates, and write the 1094 # result apex info to the ota package. 1095 ota_apex_info = ota_utils.ConstructOtaApexInfo(target_file, source_file) 1096 if ota_apex_info is not None: 1097 common.ZipWriteStr(output_zip, "apex_info.pb", ota_apex_info, 1098 compress_type=zipfile.ZIP_STORED) 1099 1100 # We haven't written the metadata entry yet, which will be handled in 1101 # FinalizeMetadata(). 1102 common.ZipClose(output_zip) 1103 1104 FinalizeMetadata(metadata, staging_file, output_file, 1105 package_key=OPTIONS.package_key) 1106 1107 1108def main(argv): 1109 1110 def option_handler(o, a): 1111 if o in ("-k", "--package_key"): 1112 OPTIONS.package_key = a 1113 elif o in ("-i", "--incremental_from"): 1114 OPTIONS.incremental_source = a 1115 elif o == "--full_radio": 1116 OPTIONS.full_radio = True 1117 elif o == "--full_bootloader": 1118 OPTIONS.full_bootloader = True 1119 elif o == "--wipe_user_data": 1120 OPTIONS.wipe_user_data = True 1121 elif o == "--downgrade": 1122 OPTIONS.downgrade = True 1123 OPTIONS.wipe_user_data = True 1124 elif o == "--override_timestamp": 1125 OPTIONS.downgrade = True 1126 elif o in ("-o", "--oem_settings"): 1127 OPTIONS.oem_source = a.split(',') 1128 elif o == "--oem_no_mount": 1129 OPTIONS.oem_no_mount = True 1130 elif o in ("-e", "--extra_script"): 1131 OPTIONS.extra_script = a 1132 elif o in ("-t", "--worker_threads"): 1133 if a.isdigit(): 1134 OPTIONS.worker_threads = int(a) 1135 else: 1136 raise ValueError("Cannot parse value %r for option %r - only " 1137 "integers are allowed." % (a, o)) 1138 elif o in ("-2", "--two_step"): 1139 OPTIONS.two_step = True 1140 elif o == "--include_secondary": 1141 OPTIONS.include_secondary = True 1142 elif o == "--no_signing": 1143 OPTIONS.no_signing = True 1144 elif o == "--verify": 1145 OPTIONS.verify = True 1146 elif o == "--block": 1147 OPTIONS.block_based = True 1148 elif o in ("-b", "--binary"): 1149 OPTIONS.updater_binary = a 1150 elif o == "--stash_threshold": 1151 try: 1152 OPTIONS.stash_threshold = float(a) 1153 except ValueError: 1154 raise ValueError("Cannot parse value %r for option %r - expecting " 1155 "a float" % (a, o)) 1156 elif o == "--log_diff": 1157 OPTIONS.log_diff = a 1158 elif o == "--payload_signer": 1159 OPTIONS.payload_signer = a 1160 elif o == "--payload_signer_args": 1161 OPTIONS.payload_signer_args = shlex.split(a) 1162 elif o == "--payload_signer_maximum_signature_size": 1163 OPTIONS.payload_signer_maximum_signature_size = a 1164 elif o == "--payload_signer_key_size": 1165 # TODO(Xunchang) remove this option after cleaning up the callers. 1166 logger.warning("The option '--payload_signer_key_size' is deprecated." 1167 " Use '--payload_signer_maximum_signature_size' instead.") 1168 OPTIONS.payload_signer_maximum_signature_size = a 1169 elif o == "--extracted_input_target_files": 1170 OPTIONS.extracted_input = a 1171 elif o == "--skip_postinstall": 1172 OPTIONS.skip_postinstall = True 1173 elif o == "--retrofit_dynamic_partitions": 1174 OPTIONS.retrofit_dynamic_partitions = True 1175 elif o == "--skip_compatibility_check": 1176 OPTIONS.skip_compatibility_check = True 1177 elif o == "--output_metadata_path": 1178 OPTIONS.output_metadata_path = a 1179 elif o == "--disable_fec_computation": 1180 OPTIONS.disable_fec_computation = True 1181 elif o == "--disable_verity_computation": 1182 OPTIONS.disable_verity_computation = True 1183 elif o == "--force_non_ab": 1184 OPTIONS.force_non_ab = True 1185 elif o == "--boot_variable_file": 1186 OPTIONS.boot_variable_file = a 1187 elif o == "--partial": 1188 if a: 1189 partitions = a.split() 1190 if not partitions: 1191 raise ValueError("Cannot parse partitions in {}".format(a)) 1192 else: 1193 partitions = [] 1194 OPTIONS.partial = partitions 1195 elif o == "--custom_image": 1196 custom_partition, custom_image = a.split("=") 1197 OPTIONS.custom_images[custom_partition] = custom_image 1198 elif o == "--disable_vabc": 1199 OPTIONS.disable_vabc = True 1200 elif o == "--spl_downgrade": 1201 OPTIONS.spl_downgrade = True 1202 OPTIONS.wipe_user_data = True 1203 elif o == "--vabc_downgrade": 1204 OPTIONS.vabc_downgrade = True 1205 elif o == "--enable_vabc_xor": 1206 assert a.lower() in ["true", "false"] 1207 OPTIONS.enable_vabc_xor = a.lower() != "false" 1208 elif o == "--force_minor_version": 1209 OPTIONS.force_minor_version = a 1210 elif o == "--compressor_types": 1211 OPTIONS.compressor_types = a 1212 elif o == "--enable_zucchini": 1213 assert a.lower() in ["true", "false"] 1214 OPTIONS.enable_zucchini = a.lower() != "false" 1215 elif o == "--enable_lz4diff": 1216 assert a.lower() in ["true", "false"] 1217 OPTIONS.enable_lz4diff = a.lower() != "false" 1218 elif o == "--vabc_compression_param": 1219 OPTIONS.vabc_compression_param = a.lower() 1220 elif o == "--security_patch_level": 1221 OPTIONS.security_patch_level = a 1222 elif o in ("--max_threads"): 1223 if a.isdigit(): 1224 OPTIONS.max_threads = a 1225 else: 1226 raise ValueError("Cannot parse value %r for option %r - only " 1227 "integers are allowed." % (a, o)) 1228 else: 1229 return False 1230 return True 1231 1232 args = common.ParseOptions(argv, __doc__, 1233 extra_opts="b:k:i:d:e:t:2o:", 1234 extra_long_opts=[ 1235 "package_key=", 1236 "incremental_from=", 1237 "full_radio", 1238 "full_bootloader", 1239 "wipe_user_data", 1240 "downgrade", 1241 "override_timestamp", 1242 "extra_script=", 1243 "worker_threads=", 1244 "two_step", 1245 "include_secondary", 1246 "no_signing", 1247 "block", 1248 "binary=", 1249 "oem_settings=", 1250 "oem_no_mount", 1251 "verify", 1252 "stash_threshold=", 1253 "log_diff=", 1254 "payload_signer=", 1255 "payload_signer_args=", 1256 "payload_signer_maximum_signature_size=", 1257 "payload_signer_key_size=", 1258 "extracted_input_target_files=", 1259 "skip_postinstall", 1260 "retrofit_dynamic_partitions", 1261 "skip_compatibility_check", 1262 "output_metadata_path=", 1263 "disable_fec_computation", 1264 "disable_verity_computation", 1265 "force_non_ab", 1266 "boot_variable_file=", 1267 "partial=", 1268 "custom_image=", 1269 "disable_vabc", 1270 "spl_downgrade", 1271 "vabc_downgrade", 1272 "enable_vabc_xor=", 1273 "force_minor_version=", 1274 "compressor_types=", 1275 "enable_zucchini=", 1276 "enable_lz4diff=", 1277 "vabc_compression_param=", 1278 "security_patch_level=", 1279 "max_threads=", 1280 ], extra_option_handler=option_handler) 1281 common.InitLogging() 1282 1283 if len(args) != 2: 1284 common.Usage(__doc__) 1285 sys.exit(1) 1286 1287 # Load the build info dicts from the zip directly or the extracted input 1288 # directory. We don't need to unzip the entire target-files zips, because they 1289 # won't be needed for A/B OTAs (brillo_update_payload does that on its own). 1290 # When loading the info dicts, we don't need to provide the second parameter 1291 # to common.LoadInfoDict(). Specifying the second parameter allows replacing 1292 # some properties with their actual paths, such as 'selinux_fc', 1293 # 'ramdisk_dir', which won't be used during OTA generation. 1294 if OPTIONS.extracted_input is not None: 1295 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input) 1296 else: 1297 OPTIONS.info_dict = common.LoadInfoDict(args[0]) 1298 1299 if OPTIONS.wipe_user_data: 1300 if not OPTIONS.vabc_downgrade: 1301 logger.info("Detected downgrade/datawipe OTA." 1302 "When wiping userdata, VABC OTA makes the user " 1303 "wait in recovery mode for merge to finish. Disable VABC by " 1304 "default. If you really want to do VABC downgrade, pass " 1305 "--vabc_downgrade") 1306 OPTIONS.disable_vabc = True 1307 # We should only allow downgrading incrementals (as opposed to full). 1308 # Otherwise the device may go back from arbitrary build with this full 1309 # OTA package. 1310 if OPTIONS.incremental_source is None and OPTIONS.downgrade: 1311 raise ValueError("Cannot generate downgradable full OTAs") 1312 1313 # TODO(xunchang) for retrofit and partial updates, maybe we should rebuild the 1314 # target-file and reload the info_dict. So the info will be consistent with 1315 # the modified target-file. 1316 1317 logger.info("--- target info ---") 1318 common.DumpInfoDict(OPTIONS.info_dict) 1319 1320 # Load the source build dict if applicable. 1321 if OPTIONS.incremental_source is not None: 1322 OPTIONS.target_info_dict = OPTIONS.info_dict 1323 OPTIONS.source_info_dict = ParseInfoDict(OPTIONS.incremental_source) 1324 1325 logger.info("--- source info ---") 1326 common.DumpInfoDict(OPTIONS.source_info_dict) 1327 1328 if OPTIONS.partial: 1329 OPTIONS.info_dict['ab_partitions'] = \ 1330 list( 1331 set(OPTIONS.info_dict['ab_partitions']) & set(OPTIONS.partial) 1332 ) 1333 if OPTIONS.source_info_dict: 1334 OPTIONS.source_info_dict['ab_partitions'] = \ 1335 list( 1336 set(OPTIONS.source_info_dict['ab_partitions']) & 1337 set(OPTIONS.partial) 1338 ) 1339 1340 # Load OEM dicts if provided. 1341 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source) 1342 1343 # Assume retrofitting dynamic partitions when base build does not set 1344 # use_dynamic_partitions but target build does. 1345 if (OPTIONS.source_info_dict and 1346 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and 1347 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"): 1348 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true": 1349 raise common.ExternalError( 1350 "Expect to generate incremental OTA for retrofitting dynamic " 1351 "partitions, but dynamic_partition_retrofit is not set in target " 1352 "build.") 1353 logger.info("Implicitly generating retrofit incremental OTA.") 1354 OPTIONS.retrofit_dynamic_partitions = True 1355 1356 # Skip postinstall for retrofitting dynamic partitions. 1357 if OPTIONS.retrofit_dynamic_partitions: 1358 OPTIONS.skip_postinstall = True 1359 1360 ab_update = OPTIONS.info_dict.get("ab_update") == "true" 1361 allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true" 1362 if OPTIONS.force_non_ab: 1363 assert allow_non_ab,\ 1364 "--force_non_ab only allowed on devices that supports non-A/B" 1365 assert ab_update, "--force_non_ab only allowed on A/B devices" 1366 1367 generate_ab = not OPTIONS.force_non_ab and ab_update 1368 1369 # Use the default key to sign the package if not specified with package_key. 1370 # package_keys are needed on ab_updates, so always define them if an 1371 # A/B update is getting created. 1372 if not OPTIONS.no_signing or generate_ab: 1373 if OPTIONS.package_key is None: 1374 OPTIONS.package_key = OPTIONS.info_dict.get( 1375 "default_system_dev_certificate", 1376 "build/make/target/product/security/testkey") 1377 # Get signing keys 1378 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 1379 1380 # Only check for existence of key file if using the default signer. 1381 # Because the custom signer might not need the key file AT all. 1382 # b/191704641 1383 if not OPTIONS.payload_signer: 1384 private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix 1385 if not os.path.exists(private_key_path): 1386 raise common.ExternalError( 1387 "Private key {} doesn't exist. Make sure you passed the" 1388 " correct key path through -k option".format( 1389 private_key_path) 1390 ) 1391 signapk_abs_path = os.path.join( 1392 OPTIONS.search_path, OPTIONS.signapk_path) 1393 if not os.path.exists(signapk_abs_path): 1394 raise common.ExternalError( 1395 "Failed to find sign apk binary {} in search path {}. Make sure the correct search path is passed via -p".format(OPTIONS.signapk_path, OPTIONS.search_path)) 1396 1397 if OPTIONS.source_info_dict: 1398 source_build_prop = OPTIONS.source_info_dict["build.prop"] 1399 target_build_prop = OPTIONS.target_info_dict["build.prop"] 1400 source_spl = source_build_prop.GetProp(SECURITY_PATCH_LEVEL_PROP_NAME) 1401 target_spl = target_build_prop.GetProp(SECURITY_PATCH_LEVEL_PROP_NAME) 1402 is_spl_downgrade = target_spl < source_spl 1403 if is_spl_downgrade and target_build_prop.GetProp("ro.build.tags") == "release-keys": 1404 raise common.ExternalError( 1405 "Target security patch level {} is older than source SPL {} " 1406 "A locked bootloader will reject SPL downgrade no matter " 1407 "what(even if data wipe is done), so SPL downgrade on any " 1408 "release-keys build is not allowed.".format(target_spl, source_spl)) 1409 1410 logger.info("SPL downgrade on %s", 1411 target_build_prop.GetProp("ro.build.tags")) 1412 if is_spl_downgrade and not OPTIONS.spl_downgrade and not OPTIONS.downgrade: 1413 raise common.ExternalError( 1414 "Target security patch level {} is older than source SPL {} applying " 1415 "such OTA will likely cause device fail to boot. Pass --spl_downgrade " 1416 "to override this check. This script expects security patch level to " 1417 "be in format yyyy-mm-dd (e.x. 2021-02-05). It's possible to use " 1418 "separators other than -, so as long as it's used consistenly across " 1419 "all SPL dates".format(target_spl, source_spl)) 1420 elif not is_spl_downgrade and OPTIONS.spl_downgrade: 1421 raise ValueError("--spl_downgrade specified but no actual SPL downgrade" 1422 " detected. Please only pass in this flag if you want a" 1423 " SPL downgrade. Target SPL: {} Source SPL: {}" 1424 .format(target_spl, source_spl)) 1425 if generate_ab: 1426 GenerateAbOtaPackage( 1427 target_file=args[0], 1428 output_file=args[1], 1429 source_file=OPTIONS.incremental_source) 1430 1431 else: 1432 GenerateNonAbOtaPackage( 1433 target_file=args[0], 1434 output_file=args[1], 1435 source_file=OPTIONS.incremental_source) 1436 1437 # Post OTA generation works. 1438 if OPTIONS.incremental_source is not None and OPTIONS.log_diff: 1439 logger.info("Generating diff logs...") 1440 logger.info("Unzipping target-files for diffing...") 1441 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN) 1442 source_dir = common.UnzipTemp( 1443 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN) 1444 1445 with open(OPTIONS.log_diff, 'w') as out_file: 1446 target_files_diff.recursiveDiff( 1447 '', source_dir, target_dir, out_file) 1448 1449 logger.info("done.") 1450 1451 1452if __name__ == '__main__': 1453 try: 1454 common.CloseInheritedPipes() 1455 main(sys.argv[1:]) 1456 finally: 1457 common.Cleanup() 1458