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/target/product/security/testkey" if that 42 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 adding the compatibility package to the generated OTA 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 81Non-A/B OTA specific options 82 83 -b (--binary) <file> 84 Use the given binary as the update-binary in the output package, instead 85 of the binary in the build's target_files. Use for development only. 86 87 --block 88 Generate a block-based OTA for non-A/B device. We have deprecated the 89 support for file-based OTA since O. Block-based OTA will be used by 90 default for all non-A/B devices. Keeping this flag here to not break 91 existing callers. 92 93 -e (--extra_script) <file> 94 Insert the contents of file at the end of the update script. 95 96 --full_bootloader 97 Similar to --full_radio. When generating an incremental OTA, always 98 include a full copy of bootloader image. 99 100 --full_radio 101 When generating an incremental OTA, always include a full copy of radio 102 image. This option is only meaningful when -i is specified, because a full 103 radio is always included in a full OTA if applicable. 104 105 --log_diff <file> 106 Generate a log file that shows the differences in the source and target 107 builds for an incremental package. This option is only meaningful when -i 108 is specified. 109 110 -o (--oem_settings) <main_file[,additional_files...]> 111 Comma seperated list of files used to specify the expected OEM-specific 112 properties on the OEM partition of the intended device. Multiple expected 113 values can be used by providing multiple files. Only the first dict will 114 be used to compute fingerprint, while the rest will be used to assert 115 OEM-specific properties. 116 117 --oem_no_mount 118 For devices with OEM-specific properties but without an OEM partition, do 119 not mount the OEM partition in the updater-script. This should be very 120 rarely used, since it's expected to have a dedicated OEM partition for 121 OEM-specific properties. Only meaningful when -o is specified. 122 123 --stash_threshold <float> 124 Specify the threshold that will be used to compute the maximum allowed 125 stash size (defaults to 0.8). 126 127 -t (--worker_threads) <int> 128 Specify the number of worker-threads that will be used when generating 129 patches for incremental updates (defaults to 3). 130 131 --verify 132 Verify the checksums of the updated system and vendor (if any) partitions. 133 Non-A/B incremental OTAs only. 134 135 -2 (--two_step) 136 Generate a 'two-step' OTA package, where recovery is updated first, so 137 that any changes made to the system partition are done using the new 138 recovery (new kernel, etc.). 139 140A/B OTA specific options 141 142 --include_secondary 143 Additionally include the payload for secondary slot images (default: 144 False). Only meaningful when generating A/B OTAs. 145 146 By default, an A/B OTA package doesn't contain the images for the 147 secondary slot (e.g. system_other.img). Specifying this flag allows 148 generating a separate payload that will install secondary slot images. 149 150 Such a package needs to be applied in a two-stage manner, with a reboot 151 in-between. During the first stage, the updater applies the primary 152 payload only. Upon finishing, it reboots the device into the newly updated 153 slot. It then continues to install the secondary payload to the inactive 154 slot, but without switching the active slot at the end (needs the matching 155 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag). 156 157 Due to the special install procedure, the secondary payload will be always 158 generated as a full payload. 159 160 --payload_signer <signer> 161 Specify the signer when signing the payload and metadata for A/B OTAs. 162 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign 163 with the package private key. If the private key cannot be accessed 164 directly, a payload signer that knows how to do that should be specified. 165 The signer will be supplied with "-inkey <path_to_key>", 166 "-in <input_file>" and "-out <output_file>" parameters. 167 168 --payload_signer_args <args> 169 Specify the arguments needed for payload signer. 170 171 --payload_signer_key_size <key_size> 172 Specify the key size in bytes of the payload signer. 173 174 --skip_postinstall 175 Skip the postinstall hooks when generating an A/B OTA package (default: 176 False). Note that this discards ALL the hooks, including non-optional 177 ones. Should only be used if caller knows it's safe to do so (e.g. all the 178 postinstall work is to dexopt apps and a data wipe will happen immediately 179 after). Only meaningful when generating A/B OTAs. 180""" 181 182from __future__ import print_function 183 184import logging 185import multiprocessing 186import os.path 187import shlex 188import shutil 189import struct 190import sys 191import tempfile 192import zipfile 193 194import common 195import edify_generator 196import verity_utils 197 198if sys.hexversion < 0x02070000: 199 print("Python 2.7 or newer is required.", file=sys.stderr) 200 sys.exit(1) 201 202logger = logging.getLogger(__name__) 203 204OPTIONS = common.OPTIONS 205OPTIONS.package_key = None 206OPTIONS.incremental_source = None 207OPTIONS.verify = False 208OPTIONS.patch_threshold = 0.95 209OPTIONS.wipe_user_data = False 210OPTIONS.downgrade = False 211OPTIONS.extra_script = None 212OPTIONS.worker_threads = multiprocessing.cpu_count() // 2 213if OPTIONS.worker_threads == 0: 214 OPTIONS.worker_threads = 1 215OPTIONS.two_step = False 216OPTIONS.include_secondary = False 217OPTIONS.no_signing = False 218OPTIONS.block_based = True 219OPTIONS.updater_binary = None 220OPTIONS.oem_source = None 221OPTIONS.oem_no_mount = False 222OPTIONS.full_radio = False 223OPTIONS.full_bootloader = False 224# Stash size cannot exceed cache_size * threshold. 225OPTIONS.cache_size = None 226OPTIONS.stash_threshold = 0.8 227OPTIONS.log_diff = None 228OPTIONS.payload_signer = None 229OPTIONS.payload_signer_args = [] 230OPTIONS.payload_signer_key_size = None 231OPTIONS.extracted_input = None 232OPTIONS.key_passwords = [] 233OPTIONS.skip_postinstall = False 234OPTIONS.retrofit_dynamic_partitions = False 235OPTIONS.skip_compatibility_check = False 236OPTIONS.output_metadata_path = None 237 238 239METADATA_NAME = 'META-INF/com/android/metadata' 240POSTINSTALL_CONFIG = 'META/postinstall_config.txt' 241DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt' 242AB_PARTITIONS = 'META/ab_partitions.txt' 243UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'RADIO/*'] 244RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS] 245 246 247class BuildInfo(object): 248 """A class that holds the information for a given build. 249 250 This class wraps up the property querying for a given source or target build. 251 It abstracts away the logic of handling OEM-specific properties, and caches 252 the commonly used properties such as fingerprint. 253 254 There are two types of info dicts: a) build-time info dict, which is generated 255 at build time (i.e. included in a target_files zip); b) OEM info dict that is 256 specified at package generation time (via command line argument 257 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not 258 having "oem_fingerprint_properties" in build-time info dict), all the queries 259 would be answered based on build-time info dict only. Otherwise if using 260 OEM-specific properties, some of them will be calculated from two info dicts. 261 262 Users can query properties similarly as using a dict() (e.g. info['fstab']), 263 or to query build properties via GetBuildProp() or GetVendorBuildProp(). 264 265 Attributes: 266 info_dict: The build-time info dict. 267 is_ab: Whether it's a build that uses A/B OTA. 268 oem_dicts: A list of OEM dicts. 269 oem_props: A list of OEM properties that should be read from OEM dicts; None 270 if the build doesn't use any OEM-specific property. 271 fingerprint: The fingerprint of the build, which would be calculated based 272 on OEM properties if applicable. 273 device: The device name, which could come from OEM dicts if applicable. 274 """ 275 276 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device", 277 "ro.product.manufacturer", "ro.product.model", 278 "ro.product.name"] 279 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "product_services", 280 "odm", "vendor", "system"] 281 282 def __init__(self, info_dict, oem_dicts): 283 """Initializes a BuildInfo instance with the given dicts. 284 285 Note that it only wraps up the given dicts, without making copies. 286 287 Arguments: 288 info_dict: The build-time info dict. 289 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note 290 that it always uses the first dict to calculate the fingerprint or the 291 device name. The rest would be used for asserting OEM properties only 292 (e.g. one package can be installed on one of these devices). 293 """ 294 self.info_dict = info_dict 295 self.oem_dicts = oem_dicts 296 297 self._is_ab = info_dict.get("ab_update") == "true" 298 self._oem_props = info_dict.get("oem_fingerprint_properties") 299 300 if self._oem_props: 301 assert oem_dicts, "OEM source required for this build" 302 303 # These two should be computed only after setting self._oem_props. 304 self._device = self.GetOemProperty("ro.product.device") 305 self._fingerprint = self.CalculateFingerprint() 306 307 @property 308 def is_ab(self): 309 return self._is_ab 310 311 @property 312 def device(self): 313 return self._device 314 315 @property 316 def fingerprint(self): 317 return self._fingerprint 318 319 @property 320 def vendor_fingerprint(self): 321 return self._fingerprint_of("vendor") 322 323 @property 324 def product_fingerprint(self): 325 return self._fingerprint_of("product") 326 327 @property 328 def odm_fingerprint(self): 329 return self._fingerprint_of("odm") 330 331 def _fingerprint_of(self, partition): 332 if partition + ".build.prop" not in self.info_dict: 333 return None 334 build_prop = self.info_dict[partition + ".build.prop"] 335 if "ro." + partition + ".build.fingerprint" in build_prop: 336 return build_prop["ro." + partition + ".build.fingerprint"] 337 if "ro." + partition + ".build.thumbprint" in build_prop: 338 return build_prop["ro." + partition + ".build.thumbprint"] 339 return None 340 341 @property 342 def oem_props(self): 343 return self._oem_props 344 345 def __getitem__(self, key): 346 return self.info_dict[key] 347 348 def __setitem__(self, key, value): 349 self.info_dict[key] = value 350 351 def get(self, key, default=None): 352 return self.info_dict.get(key, default) 353 354 def items(self): 355 return self.info_dict.items() 356 357 def GetBuildProp(self, prop): 358 """Returns the inquired build property.""" 359 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS: 360 return self._ResolveRoProductBuildProp(prop) 361 362 try: 363 return self.info_dict.get("build.prop", {})[prop] 364 except KeyError: 365 raise common.ExternalError("couldn't find %s in build.prop" % (prop,)) 366 367 def _ResolveRoProductBuildProp(self, prop): 368 """Resolves the inquired ro.product.* build property""" 369 prop_val = self.info_dict.get("build.prop", {}).get(prop) 370 if prop_val: 371 return prop_val 372 373 source_order_val = self.info_dict.get("build.prop", {}).get( 374 "ro.product.property_source_order") 375 if source_order_val: 376 source_order = source_order_val.split(",") 377 else: 378 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER 379 380 # Check that all sources in ro.product.property_source_order are valid 381 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER 382 for x in source_order]): 383 raise common.ExternalError( 384 "Invalid ro.product.property_source_order '{}'".format(source_order)) 385 386 for source in source_order: 387 source_prop = prop.replace("ro.product", "ro.product.{}".format(source), 388 1) 389 prop_val = self.info_dict.get("{}.build.prop".format(source), {}).get( 390 source_prop) 391 if prop_val: 392 return prop_val 393 394 raise common.ExternalError("couldn't resolve {}".format(prop)) 395 396 def GetVendorBuildProp(self, prop): 397 """Returns the inquired vendor build property.""" 398 try: 399 return self.info_dict.get("vendor.build.prop", {})[prop] 400 except KeyError: 401 raise common.ExternalError( 402 "couldn't find %s in vendor.build.prop" % (prop,)) 403 404 def GetOemProperty(self, key): 405 if self.oem_props is not None and key in self.oem_props: 406 return self.oem_dicts[0][key] 407 return self.GetBuildProp(key) 408 409 def CalculateFingerprint(self): 410 if self.oem_props is None: 411 try: 412 return self.GetBuildProp("ro.build.fingerprint") 413 except common.ExternalError: 414 return "{}/{}/{}:{}/{}/{}:{}/{}".format( 415 self.GetBuildProp("ro.product.brand"), 416 self.GetBuildProp("ro.product.name"), 417 self.GetBuildProp("ro.product.device"), 418 self.GetBuildProp("ro.build.version.release"), 419 self.GetBuildProp("ro.build.id"), 420 self.GetBuildProp("ro.build.version.incremental"), 421 self.GetBuildProp("ro.build.type"), 422 self.GetBuildProp("ro.build.tags")) 423 return "%s/%s/%s:%s" % ( 424 self.GetOemProperty("ro.product.brand"), 425 self.GetOemProperty("ro.product.name"), 426 self.GetOemProperty("ro.product.device"), 427 self.GetBuildProp("ro.build.thumbprint")) 428 429 def WriteMountOemScript(self, script): 430 assert self.oem_props is not None 431 recovery_mount_options = self.info_dict.get("recovery_mount_options") 432 script.Mount("/oem", recovery_mount_options) 433 434 def WriteDeviceAssertions(self, script, oem_no_mount): 435 # Read the property directly if not using OEM properties. 436 if not self.oem_props: 437 script.AssertDevice(self.device) 438 return 439 440 # Otherwise assert OEM properties. 441 if not self.oem_dicts: 442 raise common.ExternalError( 443 "No OEM file provided to answer expected assertions") 444 445 for prop in self.oem_props.split(): 446 values = [] 447 for oem_dict in self.oem_dicts: 448 if prop in oem_dict: 449 values.append(oem_dict[prop]) 450 if not values: 451 raise common.ExternalError( 452 "The OEM file is missing the property %s" % (prop,)) 453 script.AssertOemProperty(prop, values, oem_no_mount) 454 455 456class PayloadSigner(object): 457 """A class that wraps the payload signing works. 458 459 When generating a Payload, hashes of the payload and metadata files will be 460 signed with the device key, either by calling an external payload signer or 461 by calling openssl with the package key. This class provides a unified 462 interface, so that callers can just call PayloadSigner.Sign(). 463 464 If an external payload signer has been specified (OPTIONS.payload_signer), it 465 calls the signer with the provided args (OPTIONS.payload_signer_args). Note 466 that the signing key should be provided as part of the payload_signer_args. 467 Otherwise without an external signer, it uses the package key 468 (OPTIONS.package_key) and calls openssl for the signing works. 469 """ 470 471 def __init__(self): 472 if OPTIONS.payload_signer is None: 473 # Prepare the payload signing key. 474 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix 475 pw = OPTIONS.key_passwords[OPTIONS.package_key] 476 477 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"] 478 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) 479 signing_key = common.MakeTempFile(prefix="key-", suffix=".key") 480 cmd.extend(["-out", signing_key]) 481 common.RunAndCheckOutput(cmd, verbose=False) 482 483 self.signer = "openssl" 484 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key, 485 "-pkeyopt", "digest:sha256"] 486 self.key_size = self._GetKeySizeInBytes(signing_key) 487 else: 488 self.signer = OPTIONS.payload_signer 489 self.signer_args = OPTIONS.payload_signer_args 490 if OPTIONS.payload_signer_key_size: 491 self.key_size = int(OPTIONS.payload_signer_key_size) 492 assert self.key_size == 256 or self.key_size == 512, \ 493 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size) 494 else: 495 self.key_size = 256 496 497 @staticmethod 498 def _GetKeySizeInBytes(signing_key): 499 modulus_file = common.MakeTempFile(prefix="modulus-") 500 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus", 501 "-noout", "-out", modulus_file] 502 common.RunAndCheckOutput(cmd, verbose=False) 503 504 with open(modulus_file) as f: 505 modulus_string = f.read() 506 # The modulus string has the format "Modulus=$data", where $data is the 507 # concatenation of hex dump of the modulus. 508 MODULUS_PREFIX = "Modulus=" 509 assert modulus_string.startswith(MODULUS_PREFIX) 510 modulus_string = modulus_string[len(MODULUS_PREFIX):] 511 key_size = len(modulus_string) / 2 512 assert key_size == 256 or key_size == 512, \ 513 "Unsupported key size {}".format(key_size) 514 return key_size 515 516 def Sign(self, in_file): 517 """Signs the given input file. Returns the output filename.""" 518 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin") 519 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] 520 common.RunAndCheckOutput(cmd) 521 return out_file 522 523 524class Payload(object): 525 """Manages the creation and the signing of an A/B OTA Payload.""" 526 527 PAYLOAD_BIN = 'payload.bin' 528 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt' 529 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin' 530 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt' 531 532 def __init__(self, secondary=False): 533 """Initializes a Payload instance. 534 535 Args: 536 secondary: Whether it's generating a secondary payload (default: False). 537 """ 538 self.payload_file = None 539 self.payload_properties = None 540 self.secondary = secondary 541 542 def Generate(self, target_file, source_file=None, additional_args=None): 543 """Generates a payload from the given target-files zip(s). 544 545 Args: 546 target_file: The filename of the target build target-files zip. 547 source_file: The filename of the source build target-files zip; or None if 548 generating a full OTA. 549 additional_args: A list of additional args that should be passed to 550 brillo_update_payload script; or None. 551 """ 552 if additional_args is None: 553 additional_args = [] 554 555 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin") 556 cmd = ["brillo_update_payload", "generate", 557 "--payload", payload_file, 558 "--target_image", target_file] 559 if source_file is not None: 560 cmd.extend(["--source_image", source_file]) 561 cmd.extend(additional_args) 562 common.RunAndCheckOutput(cmd) 563 564 self.payload_file = payload_file 565 self.payload_properties = None 566 567 def Sign(self, payload_signer): 568 """Generates and signs the hashes of the payload and metadata. 569 570 Args: 571 payload_signer: A PayloadSigner() instance that serves the signing work. 572 573 Raises: 574 AssertionError: On any failure when calling brillo_update_payload script. 575 """ 576 assert isinstance(payload_signer, PayloadSigner) 577 578 # 1. Generate hashes of the payload and metadata files. 579 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") 580 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin") 581 cmd = ["brillo_update_payload", "hash", 582 "--unsigned_payload", self.payload_file, 583 "--signature_size", str(payload_signer.key_size), 584 "--metadata_hash_file", metadata_sig_file, 585 "--payload_hash_file", payload_sig_file] 586 common.RunAndCheckOutput(cmd) 587 588 # 2. Sign the hashes. 589 signed_payload_sig_file = payload_signer.Sign(payload_sig_file) 590 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file) 591 592 # 3. Insert the signatures back into the payload file. 593 signed_payload_file = common.MakeTempFile(prefix="signed-payload-", 594 suffix=".bin") 595 cmd = ["brillo_update_payload", "sign", 596 "--unsigned_payload", self.payload_file, 597 "--payload", signed_payload_file, 598 "--signature_size", str(payload_signer.key_size), 599 "--metadata_signature_file", signed_metadata_sig_file, 600 "--payload_signature_file", signed_payload_sig_file] 601 common.RunAndCheckOutput(cmd) 602 603 # 4. Dump the signed payload properties. 604 properties_file = common.MakeTempFile(prefix="payload-properties-", 605 suffix=".txt") 606 cmd = ["brillo_update_payload", "properties", 607 "--payload", signed_payload_file, 608 "--properties_file", properties_file] 609 common.RunAndCheckOutput(cmd) 610 611 if self.secondary: 612 with open(properties_file, "a") as f: 613 f.write("SWITCH_SLOT_ON_REBOOT=0\n") 614 615 if OPTIONS.wipe_user_data: 616 with open(properties_file, "a") as f: 617 f.write("POWERWASH=1\n") 618 619 self.payload_file = signed_payload_file 620 self.payload_properties = properties_file 621 622 def WriteToZip(self, output_zip): 623 """Writes the payload to the given zip. 624 625 Args: 626 output_zip: The output ZipFile instance. 627 """ 628 assert self.payload_file is not None 629 assert self.payload_properties is not None 630 631 if self.secondary: 632 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN 633 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT 634 else: 635 payload_arcname = Payload.PAYLOAD_BIN 636 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT 637 638 # Add the signed payload file and properties into the zip. In order to 639 # support streaming, we pack them as ZIP_STORED. So these entries can be 640 # read directly with the offset and length pairs. 641 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname, 642 compress_type=zipfile.ZIP_STORED) 643 common.ZipWrite(output_zip, self.payload_properties, 644 arcname=payload_properties_arcname, 645 compress_type=zipfile.ZIP_STORED) 646 647 648def SignOutput(temp_zip_name, output_zip_name): 649 pw = OPTIONS.key_passwords[OPTIONS.package_key] 650 651 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw, 652 whole_file=True) 653 654 655def _LoadOemDicts(oem_source): 656 """Returns the list of loaded OEM properties dict.""" 657 if not oem_source: 658 return None 659 660 oem_dicts = [] 661 for oem_file in oem_source: 662 with open(oem_file) as fp: 663 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines())) 664 return oem_dicts 665 666 667def _WriteRecoveryImageToBoot(script, output_zip): 668 """Find and write recovery image to /boot in two-step OTA. 669 670 In two-step OTAs, we write recovery image to /boot as the first step so that 671 we can reboot to there and install a new recovery image to /recovery. 672 A special "recovery-two-step.img" will be preferred, which encodes the correct 673 path of "/boot". Otherwise the device may show "device is corrupt" message 674 when booting into /boot. 675 676 Fall back to using the regular recovery.img if the two-step recovery image 677 doesn't exist. Note that rebuilding the special image at this point may be 678 infeasible, because we don't have the desired boot signer and keys when 679 calling ota_from_target_files.py. 680 """ 681 682 recovery_two_step_img_name = "recovery-two-step.img" 683 recovery_two_step_img_path = os.path.join( 684 OPTIONS.input_tmp, "IMAGES", recovery_two_step_img_name) 685 if os.path.exists(recovery_two_step_img_path): 686 recovery_two_step_img = common.GetBootableImage( 687 recovery_two_step_img_name, recovery_two_step_img_name, 688 OPTIONS.input_tmp, "RECOVERY") 689 common.ZipWriteStr( 690 output_zip, recovery_two_step_img_name, recovery_two_step_img.data) 691 logger.info( 692 "two-step package: using %s in stage 1/3", recovery_two_step_img_name) 693 script.WriteRawImage("/boot", recovery_two_step_img_name) 694 else: 695 logger.info("two-step package: using recovery.img in stage 1/3") 696 # The "recovery.img" entry has been written into package earlier. 697 script.WriteRawImage("/boot", "recovery.img") 698 699 700def HasRecoveryPatch(target_files_zip): 701 namelist = [name for name in target_files_zip.namelist()] 702 return ("SYSTEM/recovery-from-boot.p" in namelist or 703 "SYSTEM/etc/recovery.img" in namelist) 704 705 706def HasPartition(target_files_zip, partition): 707 try: 708 target_files_zip.getinfo(partition.upper() + "/") 709 return True 710 except KeyError: 711 return False 712 713 714def HasVendorPartition(target_files_zip): 715 return HasPartition(target_files_zip, "vendor") 716 717 718def HasProductPartition(target_files_zip): 719 return HasPartition(target_files_zip, "product") 720 721 722def HasOdmPartition(target_files_zip): 723 return HasPartition(target_files_zip, "odm") 724 725 726def HasTrebleEnabled(target_files_zip, target_info): 727 return (HasVendorPartition(target_files_zip) and 728 target_info.GetBuildProp("ro.treble.enabled") == "true") 729 730 731def WriteFingerprintAssertion(script, target_info, source_info): 732 source_oem_props = source_info.oem_props 733 target_oem_props = target_info.oem_props 734 735 if source_oem_props is None and target_oem_props is None: 736 script.AssertSomeFingerprint( 737 source_info.fingerprint, target_info.fingerprint) 738 elif source_oem_props is not None and target_oem_props is not None: 739 script.AssertSomeThumbprint( 740 target_info.GetBuildProp("ro.build.thumbprint"), 741 source_info.GetBuildProp("ro.build.thumbprint")) 742 elif source_oem_props is None and target_oem_props is not None: 743 script.AssertFingerprintOrThumbprint( 744 source_info.fingerprint, 745 target_info.GetBuildProp("ro.build.thumbprint")) 746 else: 747 script.AssertFingerprintOrThumbprint( 748 target_info.fingerprint, 749 source_info.GetBuildProp("ro.build.thumbprint")) 750 751 752def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info, 753 source_info=None): 754 """Adds compatibility info into the output zip if it's Treble-enabled target. 755 756 Metadata used for on-device compatibility verification is retrieved from 757 target_zip then added to compatibility.zip which is added to the output_zip 758 archive. 759 760 Compatibility archive should only be included for devices that have enabled 761 Treble support. 762 763 Args: 764 target_zip: Zip file containing the source files to be included for OTA. 765 output_zip: Zip file that will be sent for OTA. 766 target_info: The BuildInfo instance that holds the target build info. 767 source_info: The BuildInfo instance that holds the source build info, if 768 generating an incremental OTA; None otherwise. 769 """ 770 771 def AddCompatibilityArchive(framework_updated, device_updated): 772 """Adds compatibility info based on update status of both sides of Treble 773 boundary. 774 775 Args: 776 framework_updated: If True, the system / product image will be updated 777 and therefore their metadata should be included. 778 device_updated: If True, the vendor / odm image will be updated and 779 therefore their metadata should be included. 780 """ 781 # Determine what metadata we need. Files are names relative to META/. 782 compatibility_files = [] 783 device_metadata = ("vendor_manifest.xml", "vendor_matrix.xml") 784 framework_metadata = ("system_manifest.xml", "system_matrix.xml") 785 if device_updated: 786 compatibility_files += device_metadata 787 if framework_updated: 788 compatibility_files += framework_metadata 789 790 # Create new archive. 791 compatibility_archive = tempfile.NamedTemporaryFile() 792 compatibility_archive_zip = zipfile.ZipFile( 793 compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED) 794 795 # Add metadata. 796 for file_name in compatibility_files: 797 target_file_name = "META/" + file_name 798 799 if target_file_name in target_zip.namelist(): 800 data = target_zip.read(target_file_name) 801 common.ZipWriteStr(compatibility_archive_zip, file_name, data) 802 803 # Ensure files are written before we copy into output_zip. 804 compatibility_archive_zip.close() 805 806 # Only add the archive if we have any compatibility info. 807 if compatibility_archive_zip.namelist(): 808 common.ZipWrite(output_zip, compatibility_archive.name, 809 arcname="compatibility.zip", 810 compress_type=zipfile.ZIP_STORED) 811 812 def FingerprintChanged(source_fp, target_fp): 813 if source_fp is None or target_fp is None: 814 return True 815 return source_fp != target_fp 816 817 # Will only proceed if the target has enabled the Treble support (as well as 818 # having a /vendor partition). 819 if not HasTrebleEnabled(target_zip, target_info): 820 return 821 822 # Skip adding the compatibility package as a workaround for b/114240221. The 823 # compatibility will always fail on devices without qualified kernels. 824 if OPTIONS.skip_compatibility_check: 825 return 826 827 # Full OTA carries the info for system/vendor/product/odm 828 if source_info is None: 829 AddCompatibilityArchive(True, True) 830 return 831 832 source_fp = source_info.fingerprint 833 target_fp = target_info.fingerprint 834 system_updated = source_fp != target_fp 835 836 # other build fingerprints could be possibly blacklisted at build time. For 837 # such a case, we consider those images being changed. 838 vendor_updated = FingerprintChanged(source_info.vendor_fingerprint, 839 target_info.vendor_fingerprint) 840 product_updated = HasProductPartition(target_zip) and \ 841 FingerprintChanged(source_info.product_fingerprint, 842 target_info.product_fingerprint) 843 odm_updated = HasOdmPartition(target_zip) and \ 844 FingerprintChanged(source_info.odm_fingerprint, 845 target_info.odm_fingerprint) 846 847 AddCompatibilityArchive(system_updated or product_updated, 848 vendor_updated or odm_updated) 849 850 851def WriteFullOTAPackage(input_zip, output_file): 852 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) 853 854 # We don't know what version it will be installed on top of. We expect the API 855 # just won't change very often. Similarly for fstab, it might have changed in 856 # the target build. 857 target_api_version = target_info["recovery_api_version"] 858 script = edify_generator.EdifyGenerator(target_api_version, target_info) 859 860 if target_info.oem_props and not OPTIONS.oem_no_mount: 861 target_info.WriteMountOemScript(script) 862 863 metadata = GetPackageMetadata(target_info) 864 865 if not OPTIONS.no_signing: 866 staging_file = common.MakeTempFile(suffix='.zip') 867 else: 868 staging_file = output_file 869 870 output_zip = zipfile.ZipFile( 871 staging_file, "w", compression=zipfile.ZIP_DEFLATED) 872 873 device_specific = common.DeviceSpecificParams( 874 input_zip=input_zip, 875 input_version=target_api_version, 876 output_zip=output_zip, 877 script=script, 878 input_tmp=OPTIONS.input_tmp, 879 metadata=metadata, 880 info_dict=OPTIONS.info_dict) 881 882 assert HasRecoveryPatch(input_zip) 883 884 # Assertions (e.g. downgrade check, device properties check). 885 ts = target_info.GetBuildProp("ro.build.date.utc") 886 ts_text = target_info.GetBuildProp("ro.build.date") 887 script.AssertOlderBuild(ts, ts_text) 888 889 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount) 890 device_specific.FullOTA_Assertions() 891 892 # Two-step package strategy (in chronological order, which is *not* 893 # the order in which the generated script has things): 894 # 895 # if stage is not "2/3" or "3/3": 896 # write recovery image to boot partition 897 # set stage to "2/3" 898 # reboot to boot partition and restart recovery 899 # else if stage is "2/3": 900 # write recovery image to recovery partition 901 # set stage to "3/3" 902 # reboot to recovery partition and restart recovery 903 # else: 904 # (stage must be "3/3") 905 # set stage to "" 906 # do normal full package installation: 907 # wipe and install system, boot image, etc. 908 # set up system to update recovery partition on first boot 909 # complete script normally 910 # (allow recovery to mark itself finished and reboot) 911 912 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 913 OPTIONS.input_tmp, "RECOVERY") 914 if OPTIONS.two_step: 915 if not target_info.get("multistage_support"): 916 assert False, "two-step packages not supported by this build" 917 fs = target_info["fstab"]["/misc"] 918 assert fs.fs_type.upper() == "EMMC", \ 919 "two-step packages only supported on devices with EMMC /misc partitions" 920 bcb_dev = {"bcb_dev": fs.device} 921 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data) 922 script.AppendExtra(""" 923if get_stage("%(bcb_dev)s") == "2/3" then 924""" % bcb_dev) 925 926 # Stage 2/3: Write recovery image to /recovery (currently running /boot). 927 script.Comment("Stage 2/3") 928 script.WriteRawImage("/recovery", "recovery.img") 929 script.AppendExtra(""" 930set_stage("%(bcb_dev)s", "3/3"); 931reboot_now("%(bcb_dev)s", "recovery"); 932else if get_stage("%(bcb_dev)s") == "3/3" then 933""" % bcb_dev) 934 935 # Stage 3/3: Make changes. 936 script.Comment("Stage 3/3") 937 938 # Dump fingerprints 939 script.Print("Target: {}".format(target_info.fingerprint)) 940 941 device_specific.FullOTA_InstallBegin() 942 943 system_progress = 0.75 944 945 if OPTIONS.wipe_user_data: 946 system_progress -= 0.1 947 if HasVendorPartition(input_zip): 948 system_progress -= 0.1 949 950 script.ShowProgress(system_progress, 0) 951 952 def GetBlockDifference(partition): 953 # Full OTA is done as an "incremental" against an empty source image. This 954 # has the effect of writing new data from the package to the entire 955 # partition, but lets us reuse the updater code that writes incrementals to 956 # do it. 957 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, input_zip, 958 info_dict=target_info, 959 reset_file_map=True) 960 diff = common.BlockDifference(partition, tgt, src=None) 961 return diff 962 963 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences() 964 if device_specific_diffs: 965 assert all(isinstance(diff, common.BlockDifference) 966 for diff in device_specific_diffs), \ 967 "FullOTA_GetBlockDifferences is not returning a list of " \ 968 "BlockDifference objects" 969 970 progress_dict = dict() 971 block_diffs = [GetBlockDifference("system")] 972 if HasVendorPartition(input_zip): 973 block_diffs.append(GetBlockDifference("vendor")) 974 progress_dict["vendor"] = 0.1 975 if device_specific_diffs: 976 block_diffs += device_specific_diffs 977 978 if target_info.get('use_dynamic_partitions') == "true": 979 # Use empty source_info_dict to indicate that all partitions / groups must 980 # be re-added. 981 dynamic_partitions_diff = common.DynamicPartitionsDifference( 982 info_dict=OPTIONS.info_dict, 983 block_diffs=block_diffs, 984 progress_dict=progress_dict) 985 dynamic_partitions_diff.WriteScript(script, output_zip, 986 write_verify_script=OPTIONS.verify) 987 else: 988 for block_diff in block_diffs: 989 block_diff.WriteScript(script, output_zip, 990 progress=progress_dict.get(block_diff.partition), 991 write_verify_script=OPTIONS.verify) 992 993 AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info) 994 995 boot_img = common.GetBootableImage( 996 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 997 common.CheckSize(boot_img.data, "boot.img", target_info) 998 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 999 1000 script.ShowProgress(0.05, 5) 1001 script.WriteRawImage("/boot", "boot.img") 1002 1003 script.ShowProgress(0.2, 10) 1004 device_specific.FullOTA_InstallEnd() 1005 1006 if OPTIONS.extra_script is not None: 1007 script.AppendExtra(OPTIONS.extra_script) 1008 1009 script.UnmountAll() 1010 1011 if OPTIONS.wipe_user_data: 1012 script.ShowProgress(0.1, 10) 1013 script.FormatPartition("/data") 1014 1015 if OPTIONS.two_step: 1016 script.AppendExtra(""" 1017set_stage("%(bcb_dev)s", ""); 1018""" % bcb_dev) 1019 script.AppendExtra("else\n") 1020 1021 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot. 1022 script.Comment("Stage 1/3") 1023 _WriteRecoveryImageToBoot(script, output_zip) 1024 1025 script.AppendExtra(""" 1026set_stage("%(bcb_dev)s", "2/3"); 1027reboot_now("%(bcb_dev)s", ""); 1028endif; 1029endif; 1030""" % bcb_dev) 1031 1032 script.SetProgress(1) 1033 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary) 1034 metadata["ota-required-cache"] = str(script.required_cache) 1035 1036 # We haven't written the metadata entry, which will be done in 1037 # FinalizeMetadata. 1038 common.ZipClose(output_zip) 1039 1040 needed_property_files = ( 1041 NonAbOtaPropertyFiles(), 1042 ) 1043 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files) 1044 1045 1046def WriteMetadata(metadata, output): 1047 """Writes the metadata to the zip archive or a file. 1048 1049 Args: 1050 metadata: The metadata dict for the package. 1051 output: A ZipFile object or a string of the output file path. 1052 """ 1053 1054 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.iteritems())]) 1055 if isinstance(output, zipfile.ZipFile): 1056 common.ZipWriteStr(output, METADATA_NAME, value, 1057 compress_type=zipfile.ZIP_STORED) 1058 return 1059 1060 with open(output, 'w') as f: 1061 f.write(value) 1062 1063 1064def HandleDowngradeMetadata(metadata, target_info, source_info): 1065 # Only incremental OTAs are allowed to reach here. 1066 assert OPTIONS.incremental_source is not None 1067 1068 post_timestamp = target_info.GetBuildProp("ro.build.date.utc") 1069 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc") 1070 is_downgrade = long(post_timestamp) < long(pre_timestamp) 1071 1072 if OPTIONS.downgrade: 1073 if not is_downgrade: 1074 raise RuntimeError( 1075 "--downgrade or --override_timestamp specified but no downgrade " 1076 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp)) 1077 metadata["ota-downgrade"] = "yes" 1078 else: 1079 if is_downgrade: 1080 raise RuntimeError( 1081 "Downgrade detected based on timestamp check: pre: %s, post: %s. " 1082 "Need to specify --override_timestamp OR --downgrade to allow " 1083 "building the incremental." % (pre_timestamp, post_timestamp)) 1084 1085 1086def GetPackageMetadata(target_info, source_info=None): 1087 """Generates and returns the metadata dict. 1088 1089 It generates a dict() that contains the info to be written into an OTA 1090 package (META-INF/com/android/metadata). It also handles the detection of 1091 downgrade / data wipe based on the global options. 1092 1093 Args: 1094 target_info: The BuildInfo instance that holds the target build info. 1095 source_info: The BuildInfo instance that holds the source build info, or 1096 None if generating full OTA. 1097 1098 Returns: 1099 A dict to be written into package metadata entry. 1100 """ 1101 assert isinstance(target_info, BuildInfo) 1102 assert source_info is None or isinstance(source_info, BuildInfo) 1103 1104 metadata = { 1105 'post-build' : target_info.fingerprint, 1106 'post-build-incremental' : target_info.GetBuildProp( 1107 'ro.build.version.incremental'), 1108 'post-sdk-level' : target_info.GetBuildProp( 1109 'ro.build.version.sdk'), 1110 'post-security-patch-level' : target_info.GetBuildProp( 1111 'ro.build.version.security_patch'), 1112 } 1113 1114 if target_info.is_ab: 1115 metadata['ota-type'] = 'AB' 1116 metadata['ota-required-cache'] = '0' 1117 else: 1118 metadata['ota-type'] = 'BLOCK' 1119 1120 if OPTIONS.wipe_user_data: 1121 metadata['ota-wipe'] = 'yes' 1122 1123 if OPTIONS.retrofit_dynamic_partitions: 1124 metadata['ota-retrofit-dynamic-partitions'] = 'yes' 1125 1126 is_incremental = source_info is not None 1127 if is_incremental: 1128 metadata['pre-build'] = source_info.fingerprint 1129 metadata['pre-build-incremental'] = source_info.GetBuildProp( 1130 'ro.build.version.incremental') 1131 metadata['pre-device'] = source_info.device 1132 else: 1133 metadata['pre-device'] = target_info.device 1134 1135 # Use the actual post-timestamp, even for a downgrade case. 1136 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc') 1137 1138 # Detect downgrades and set up downgrade flags accordingly. 1139 if is_incremental: 1140 HandleDowngradeMetadata(metadata, target_info, source_info) 1141 1142 return metadata 1143 1144 1145class PropertyFiles(object): 1146 """A class that computes the property-files string for an OTA package. 1147 1148 A property-files string is a comma-separated string that contains the 1149 offset/size info for an OTA package. The entries, which must be ZIP_STORED, 1150 can be fetched directly with the package URL along with the offset/size info. 1151 These strings can be used for streaming A/B OTAs, or allowing an updater to 1152 download package metadata entry directly, without paying the cost of 1153 downloading entire package. 1154 1155 Computing the final property-files string requires two passes. Because doing 1156 the whole package signing (with signapk.jar) will possibly reorder the ZIP 1157 entries, which may in turn invalidate earlier computed ZIP entry offset/size 1158 values. 1159 1160 This class provides functions to be called for each pass. The general flow is 1161 as follows. 1162 1163 property_files = PropertyFiles() 1164 # The first pass, which writes placeholders before doing initial signing. 1165 property_files.Compute() 1166 SignOutput() 1167 1168 # The second pass, by replacing the placeholders with actual data. 1169 property_files.Finalize() 1170 SignOutput() 1171 1172 And the caller can additionally verify the final result. 1173 1174 property_files.Verify() 1175 """ 1176 1177 def __init__(self): 1178 self.name = None 1179 self.required = () 1180 self.optional = () 1181 1182 def Compute(self, input_zip): 1183 """Computes and returns a property-files string with placeholders. 1184 1185 We reserve extra space for the offset and size of the metadata entry itself, 1186 although we don't know the final values until the package gets signed. 1187 1188 Args: 1189 input_zip: The input ZIP file. 1190 1191 Returns: 1192 A string with placeholders for the metadata offset/size info, e.g. 1193 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ". 1194 """ 1195 return self.GetPropertyFilesString(input_zip, reserve_space=True) 1196 1197 class InsufficientSpaceException(Exception): 1198 pass 1199 1200 def Finalize(self, input_zip, reserved_length): 1201 """Finalizes a property-files string with actual METADATA offset/size info. 1202 1203 The input ZIP file has been signed, with the ZIP entries in the desired 1204 place (signapk.jar will possibly reorder the ZIP entries). Now we compute 1205 the ZIP entry offsets and construct the property-files string with actual 1206 data. Note that during this process, we must pad the property-files string 1207 to the reserved length, so that the METADATA entry size remains the same. 1208 Otherwise the entries' offsets and sizes may change again. 1209 1210 Args: 1211 input_zip: The input ZIP file. 1212 reserved_length: The reserved length of the property-files string during 1213 the call to Compute(). The final string must be no more than this 1214 size. 1215 1216 Returns: 1217 A property-files string including the metadata offset/size info, e.g. 1218 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ". 1219 1220 Raises: 1221 InsufficientSpaceException: If the reserved length is insufficient to hold 1222 the final string. 1223 """ 1224 result = self.GetPropertyFilesString(input_zip, reserve_space=False) 1225 if len(result) > reserved_length: 1226 raise self.InsufficientSpaceException( 1227 'Insufficient reserved space: reserved={}, actual={}'.format( 1228 reserved_length, len(result))) 1229 1230 result += ' ' * (reserved_length - len(result)) 1231 return result 1232 1233 def Verify(self, input_zip, expected): 1234 """Verifies the input ZIP file contains the expected property-files string. 1235 1236 Args: 1237 input_zip: The input ZIP file. 1238 expected: The property-files string that's computed from Finalize(). 1239 1240 Raises: 1241 AssertionError: On finding a mismatch. 1242 """ 1243 actual = self.GetPropertyFilesString(input_zip) 1244 assert actual == expected, \ 1245 "Mismatching streaming metadata: {} vs {}.".format(actual, expected) 1246 1247 def GetPropertyFilesString(self, zip_file, reserve_space=False): 1248 """ 1249 Constructs the property-files string per request. 1250 1251 Args: 1252 zip_file: The input ZIP file. 1253 reserved_length: The reserved length of the property-files string. 1254 1255 Returns: 1256 A property-files string including the metadata offset/size info, e.g. 1257 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ". 1258 """ 1259 1260 def ComputeEntryOffsetSize(name): 1261 """Computes the zip entry offset and size.""" 1262 info = zip_file.getinfo(name) 1263 offset = info.header_offset 1264 offset += zipfile.sizeFileHeader 1265 offset += len(info.extra) + len(info.filename) 1266 size = info.file_size 1267 return '%s:%d:%d' % (os.path.basename(name), offset, size) 1268 1269 tokens = [] 1270 tokens.extend(self._GetPrecomputed(zip_file)) 1271 for entry in self.required: 1272 tokens.append(ComputeEntryOffsetSize(entry)) 1273 for entry in self.optional: 1274 if entry in zip_file.namelist(): 1275 tokens.append(ComputeEntryOffsetSize(entry)) 1276 1277 # 'META-INF/com/android/metadata' is required. We don't know its actual 1278 # offset and length (as well as the values for other entries). So we reserve 1279 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover 1280 # the space for metadata entry. Because 'offset' allows a max of 10-digit 1281 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the 1282 # reserved space serves the metadata entry only. 1283 if reserve_space: 1284 tokens.append('metadata:' + ' ' * 15) 1285 else: 1286 tokens.append(ComputeEntryOffsetSize(METADATA_NAME)) 1287 1288 return ','.join(tokens) 1289 1290 def _GetPrecomputed(self, input_zip): 1291 """Computes the additional tokens to be included into the property-files. 1292 1293 This applies to tokens without actual ZIP entries, such as 1294 payload_metadadata.bin. We want to expose the offset/size to updaters, so 1295 that they can download the payload metadata directly with the info. 1296 1297 Args: 1298 input_zip: The input zip file. 1299 1300 Returns: 1301 A list of strings (tokens) to be added to the property-files string. 1302 """ 1303 # pylint: disable=no-self-use 1304 # pylint: disable=unused-argument 1305 return [] 1306 1307 1308class StreamingPropertyFiles(PropertyFiles): 1309 """A subclass for computing the property-files for streaming A/B OTAs.""" 1310 1311 def __init__(self): 1312 super(StreamingPropertyFiles, self).__init__() 1313 self.name = 'ota-streaming-property-files' 1314 self.required = ( 1315 # payload.bin and payload_properties.txt must exist. 1316 'payload.bin', 1317 'payload_properties.txt', 1318 ) 1319 self.optional = ( 1320 # care_map is available only if dm-verity is enabled. 1321 'care_map.pb', 1322 'care_map.txt', 1323 # compatibility.zip is available only if target supports Treble. 1324 'compatibility.zip', 1325 ) 1326 1327 1328class AbOtaPropertyFiles(StreamingPropertyFiles): 1329 """The property-files for A/B OTA that includes payload_metadata.bin info. 1330 1331 Since P, we expose one more token (aka property-file), in addition to the ones 1332 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'. 1333 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which 1334 doesn't exist as a separate ZIP entry, but can be used to verify if the 1335 payload can be applied on the given device. 1336 1337 For backward compatibility, we keep both of the 'ota-streaming-property-files' 1338 and the newly added 'ota-property-files' in P. The new token will only be 1339 available in 'ota-property-files'. 1340 """ 1341 1342 def __init__(self): 1343 super(AbOtaPropertyFiles, self).__init__() 1344 self.name = 'ota-property-files' 1345 1346 def _GetPrecomputed(self, input_zip): 1347 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip) 1348 return ['payload_metadata.bin:{}:{}'.format(offset, size)] 1349 1350 @staticmethod 1351 def _GetPayloadMetadataOffsetAndSize(input_zip): 1352 """Computes the offset and size of the payload metadata for a given package. 1353 1354 (From system/update_engine/update_metadata.proto) 1355 A delta update file contains all the deltas needed to update a system from 1356 one specific version to another specific version. The update format is 1357 represented by this struct pseudocode: 1358 1359 struct delta_update_file { 1360 char magic[4] = "CrAU"; 1361 uint64 file_format_version; 1362 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest 1363 1364 // Only present if format_version > 1: 1365 uint32 metadata_signature_size; 1366 1367 // The Bzip2 compressed DeltaArchiveManifest 1368 char manifest[metadata_signature_size]; 1369 1370 // The signature of the metadata (from the beginning of the payload up to 1371 // this location, not including the signature itself). This is a 1372 // serialized Signatures message. 1373 char medatada_signature_message[metadata_signature_size]; 1374 1375 // Data blobs for files, no specific format. The specific offset 1376 // and length of each data blob is recorded in the DeltaArchiveManifest. 1377 struct { 1378 char data[]; 1379 } blobs[]; 1380 1381 // These two are not signed: 1382 uint64 payload_signatures_message_size; 1383 char payload_signatures_message[]; 1384 }; 1385 1386 'payload-metadata.bin' contains all the bytes from the beginning of the 1387 payload, till the end of 'medatada_signature_message'. 1388 """ 1389 payload_info = input_zip.getinfo('payload.bin') 1390 payload_offset = payload_info.header_offset 1391 payload_offset += zipfile.sizeFileHeader 1392 payload_offset += len(payload_info.extra) + len(payload_info.filename) 1393 payload_size = payload_info.file_size 1394 1395 with input_zip.open('payload.bin', 'r') as payload_fp: 1396 header_bin = payload_fp.read(24) 1397 1398 # network byte order (big-endian) 1399 header = struct.unpack("!IQQL", header_bin) 1400 1401 # 'CrAU' 1402 magic = header[0] 1403 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic) 1404 1405 manifest_size = header[2] 1406 metadata_signature_size = header[3] 1407 metadata_total = 24 + manifest_size + metadata_signature_size 1408 assert metadata_total < payload_size 1409 1410 return (payload_offset, metadata_total) 1411 1412 1413class NonAbOtaPropertyFiles(PropertyFiles): 1414 """The property-files for non-A/B OTA. 1415 1416 For non-A/B OTA, the property-files string contains the info for METADATA 1417 entry, with which a system updater can be fetched the package metadata prior 1418 to downloading the entire package. 1419 """ 1420 1421 def __init__(self): 1422 super(NonAbOtaPropertyFiles, self).__init__() 1423 self.name = 'ota-property-files' 1424 1425 1426def FinalizeMetadata(metadata, input_file, output_file, needed_property_files): 1427 """Finalizes the metadata and signs an A/B OTA package. 1428 1429 In order to stream an A/B OTA package, we need 'ota-streaming-property-files' 1430 that contains the offsets and sizes for the ZIP entries. An example 1431 property-files string is as follows. 1432 1433 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379" 1434 1435 OTA server can pass down this string, in addition to the package URL, to the 1436 system update client. System update client can then fetch individual ZIP 1437 entries (ZIP_STORED) directly at the given offset of the URL. 1438 1439 Args: 1440 metadata: The metadata dict for the package. 1441 input_file: The input ZIP filename that doesn't contain the package METADATA 1442 entry yet. 1443 output_file: The final output ZIP filename. 1444 needed_property_files: The list of PropertyFiles' to be generated. 1445 """ 1446 1447 def ComputeAllPropertyFiles(input_file, needed_property_files): 1448 # Write the current metadata entry with placeholders. 1449 with zipfile.ZipFile(input_file) as input_zip: 1450 for property_files in needed_property_files: 1451 metadata[property_files.name] = property_files.Compute(input_zip) 1452 namelist = input_zip.namelist() 1453 1454 if METADATA_NAME in namelist: 1455 common.ZipDelete(input_file, METADATA_NAME) 1456 output_zip = zipfile.ZipFile(input_file, 'a') 1457 WriteMetadata(metadata, output_zip) 1458 common.ZipClose(output_zip) 1459 1460 if OPTIONS.no_signing: 1461 return input_file 1462 1463 prelim_signing = common.MakeTempFile(suffix='.zip') 1464 SignOutput(input_file, prelim_signing) 1465 return prelim_signing 1466 1467 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files): 1468 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip: 1469 for property_files in needed_property_files: 1470 metadata[property_files.name] = property_files.Finalize( 1471 prelim_signing_zip, len(metadata[property_files.name])) 1472 1473 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP 1474 # entries, as well as padding the entry headers. We do a preliminary signing 1475 # (with an incomplete metadata entry) to allow that to happen. Then compute 1476 # the ZIP entry offsets, write back the final metadata and do the final 1477 # signing. 1478 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files) 1479 try: 1480 FinalizeAllPropertyFiles(prelim_signing, needed_property_files) 1481 except PropertyFiles.InsufficientSpaceException: 1482 # Even with the preliminary signing, the entry orders may change 1483 # dramatically, which leads to insufficiently reserved space during the 1484 # first call to ComputeAllPropertyFiles(). In that case, we redo all the 1485 # preliminary signing works, based on the already ordered ZIP entries, to 1486 # address the issue. 1487 prelim_signing = ComputeAllPropertyFiles( 1488 prelim_signing, needed_property_files) 1489 FinalizeAllPropertyFiles(prelim_signing, needed_property_files) 1490 1491 # Replace the METADATA entry. 1492 common.ZipDelete(prelim_signing, METADATA_NAME) 1493 output_zip = zipfile.ZipFile(prelim_signing, 'a') 1494 WriteMetadata(metadata, output_zip) 1495 common.ZipClose(output_zip) 1496 1497 # Re-sign the package after updating the metadata entry. 1498 if OPTIONS.no_signing: 1499 output_file = prelim_signing 1500 else: 1501 SignOutput(prelim_signing, output_file) 1502 1503 # Reopen the final signed zip to double check the streaming metadata. 1504 with zipfile.ZipFile(output_file) as output_zip: 1505 for property_files in needed_property_files: 1506 property_files.Verify(output_zip, metadata[property_files.name].strip()) 1507 1508 # If requested, dump the metadata to a separate file. 1509 output_metadata_path = OPTIONS.output_metadata_path 1510 if output_metadata_path: 1511 WriteMetadata(metadata, output_metadata_path) 1512 1513 1514def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file): 1515 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) 1516 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) 1517 1518 target_api_version = target_info["recovery_api_version"] 1519 source_api_version = source_info["recovery_api_version"] 1520 if source_api_version == 0: 1521 logger.warning( 1522 "Generating edify script for a source that can't install it.") 1523 1524 script = edify_generator.EdifyGenerator( 1525 source_api_version, target_info, fstab=source_info["fstab"]) 1526 1527 if target_info.oem_props or source_info.oem_props: 1528 if not OPTIONS.oem_no_mount: 1529 source_info.WriteMountOemScript(script) 1530 1531 metadata = GetPackageMetadata(target_info, source_info) 1532 1533 if not OPTIONS.no_signing: 1534 staging_file = common.MakeTempFile(suffix='.zip') 1535 else: 1536 staging_file = output_file 1537 1538 output_zip = zipfile.ZipFile( 1539 staging_file, "w", compression=zipfile.ZIP_DEFLATED) 1540 1541 device_specific = common.DeviceSpecificParams( 1542 source_zip=source_zip, 1543 source_version=source_api_version, 1544 source_tmp=OPTIONS.source_tmp, 1545 target_zip=target_zip, 1546 target_version=target_api_version, 1547 target_tmp=OPTIONS.target_tmp, 1548 output_zip=output_zip, 1549 script=script, 1550 metadata=metadata, 1551 info_dict=source_info) 1552 1553 source_boot = common.GetBootableImage( 1554 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info) 1555 target_boot = common.GetBootableImage( 1556 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info) 1557 updating_boot = (not OPTIONS.two_step and 1558 (source_boot.data != target_boot.data)) 1559 1560 target_recovery = common.GetBootableImage( 1561 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 1562 1563 # See notes in common.GetUserImage() 1564 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or 1565 target_info.get('ext4_share_dup_blocks') == "true") 1566 system_src = common.GetUserImage("system", OPTIONS.source_tmp, source_zip, 1567 info_dict=source_info, 1568 allow_shared_blocks=allow_shared_blocks) 1569 1570 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator( 1571 "system", 4096, target_info) 1572 system_tgt = common.GetUserImage("system", OPTIONS.target_tmp, target_zip, 1573 info_dict=target_info, 1574 allow_shared_blocks=allow_shared_blocks, 1575 hashtree_info_generator= 1576 hashtree_info_generator) 1577 1578 blockimgdiff_version = max( 1579 int(i) for i in target_info.get("blockimgdiff_versions", "1").split(",")) 1580 assert blockimgdiff_version >= 3 1581 1582 # Check the first block of the source system partition for remount R/W only 1583 # if the filesystem is ext4. 1584 system_src_partition = source_info["fstab"]["/system"] 1585 check_first_block = system_src_partition.fs_type == "ext4" 1586 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be 1587 # in zip formats. However with squashfs, a) all files are compressed in LZ4; 1588 # b) the blocks listed in block map may not contain all the bytes for a given 1589 # file (because they're rounded to be 4K-aligned). 1590 system_tgt_partition = target_info["fstab"]["/system"] 1591 disable_imgdiff = (system_src_partition.fs_type == "squashfs" or 1592 system_tgt_partition.fs_type == "squashfs") 1593 system_diff = common.BlockDifference("system", system_tgt, system_src, 1594 check_first_block, 1595 version=blockimgdiff_version, 1596 disable_imgdiff=disable_imgdiff) 1597 1598 if HasVendorPartition(target_zip): 1599 if not HasVendorPartition(source_zip): 1600 raise RuntimeError("can't generate incremental that adds /vendor") 1601 vendor_src = common.GetUserImage("vendor", OPTIONS.source_tmp, source_zip, 1602 info_dict=source_info, 1603 allow_shared_blocks=allow_shared_blocks) 1604 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator( 1605 "vendor", 4096, target_info) 1606 vendor_tgt = common.GetUserImage( 1607 "vendor", OPTIONS.target_tmp, target_zip, 1608 info_dict=target_info, 1609 allow_shared_blocks=allow_shared_blocks, 1610 hashtree_info_generator=hashtree_info_generator) 1611 1612 # Check first block of vendor partition for remount R/W only if 1613 # disk type is ext4 1614 vendor_partition = source_info["fstab"]["/vendor"] 1615 check_first_block = vendor_partition.fs_type == "ext4" 1616 disable_imgdiff = vendor_partition.fs_type == "squashfs" 1617 vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src, 1618 check_first_block, 1619 version=blockimgdiff_version, 1620 disable_imgdiff=disable_imgdiff) 1621 else: 1622 vendor_diff = None 1623 1624 AddCompatibilityArchiveIfTrebleEnabled( 1625 target_zip, output_zip, target_info, source_info) 1626 1627 # Assertions (e.g. device properties check). 1628 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount) 1629 device_specific.IncrementalOTA_Assertions() 1630 1631 # Two-step incremental package strategy (in chronological order, 1632 # which is *not* the order in which the generated script has 1633 # things): 1634 # 1635 # if stage is not "2/3" or "3/3": 1636 # do verification on current system 1637 # write recovery image to boot partition 1638 # set stage to "2/3" 1639 # reboot to boot partition and restart recovery 1640 # else if stage is "2/3": 1641 # write recovery image to recovery partition 1642 # set stage to "3/3" 1643 # reboot to recovery partition and restart recovery 1644 # else: 1645 # (stage must be "3/3") 1646 # perform update: 1647 # patch system files, etc. 1648 # force full install of new boot image 1649 # set up system to update recovery partition on first boot 1650 # complete script normally 1651 # (allow recovery to mark itself finished and reboot) 1652 1653 if OPTIONS.two_step: 1654 if not source_info.get("multistage_support"): 1655 assert False, "two-step packages not supported by this build" 1656 fs = source_info["fstab"]["/misc"] 1657 assert fs.fs_type.upper() == "EMMC", \ 1658 "two-step packages only supported on devices with EMMC /misc partitions" 1659 bcb_dev = {"bcb_dev" : fs.device} 1660 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 1661 script.AppendExtra(""" 1662if get_stage("%(bcb_dev)s") == "2/3" then 1663""" % bcb_dev) 1664 1665 # Stage 2/3: Write recovery image to /recovery (currently running /boot). 1666 script.Comment("Stage 2/3") 1667 script.AppendExtra("sleep(20);\n") 1668 script.WriteRawImage("/recovery", "recovery.img") 1669 script.AppendExtra(""" 1670set_stage("%(bcb_dev)s", "3/3"); 1671reboot_now("%(bcb_dev)s", "recovery"); 1672else if get_stage("%(bcb_dev)s") != "3/3" then 1673""" % bcb_dev) 1674 1675 # Stage 1/3: (a) Verify the current system. 1676 script.Comment("Stage 1/3") 1677 1678 # Dump fingerprints 1679 script.Print("Source: {}".format(source_info.fingerprint)) 1680 script.Print("Target: {}".format(target_info.fingerprint)) 1681 1682 script.Print("Verifying current system...") 1683 1684 device_specific.IncrementalOTA_VerifyBegin() 1685 1686 WriteFingerprintAssertion(script, target_info, source_info) 1687 1688 # Check the required cache size (i.e. stashed blocks). 1689 size = [] 1690 if system_diff: 1691 size.append(system_diff.required_cache) 1692 if vendor_diff: 1693 size.append(vendor_diff.required_cache) 1694 1695 if updating_boot: 1696 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info) 1697 d = common.Difference(target_boot, source_boot) 1698 _, _, d = d.ComputePatch() 1699 if d is None: 1700 include_full_boot = True 1701 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 1702 else: 1703 include_full_boot = False 1704 1705 logger.info( 1706 "boot target: %d source: %d diff: %d", target_boot.size, 1707 source_boot.size, len(d)) 1708 1709 common.ZipWriteStr(output_zip, "boot.img.p", d) 1710 1711 script.PatchPartitionCheck( 1712 "{}:{}:{}:{}".format( 1713 boot_type, boot_device, target_boot.size, target_boot.sha1), 1714 "{}:{}:{}:{}".format( 1715 boot_type, boot_device, source_boot.size, source_boot.sha1)) 1716 1717 size.append(target_boot.size) 1718 1719 if size: 1720 script.CacheFreeSpaceCheck(max(size)) 1721 1722 device_specific.IncrementalOTA_VerifyEnd() 1723 1724 if OPTIONS.two_step: 1725 # Stage 1/3: (b) Write recovery image to /boot. 1726 _WriteRecoveryImageToBoot(script, output_zip) 1727 1728 script.AppendExtra(""" 1729set_stage("%(bcb_dev)s", "2/3"); 1730reboot_now("%(bcb_dev)s", ""); 1731else 1732""" % bcb_dev) 1733 1734 # Stage 3/3: Make changes. 1735 script.Comment("Stage 3/3") 1736 1737 # Verify the existing partitions. 1738 system_diff.WriteVerifyScript(script, touched_blocks_only=True) 1739 if vendor_diff: 1740 vendor_diff.WriteVerifyScript(script, touched_blocks_only=True) 1741 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences() 1742 if device_specific_diffs: 1743 assert all(isinstance(diff, common.BlockDifference) 1744 for diff in device_specific_diffs), \ 1745 "IncrementalOTA_GetBlockDifferences is not returning a list of " \ 1746 "BlockDifference objects" 1747 for diff in device_specific_diffs: 1748 diff.WriteVerifyScript(script, touched_blocks_only=True) 1749 1750 script.Comment("---- start making changes here ----") 1751 1752 device_specific.IncrementalOTA_InstallBegin() 1753 1754 block_diffs = [system_diff] 1755 progress_dict = {"system": 0.8 if vendor_diff else 0.9} 1756 if vendor_diff: 1757 block_diffs.append(vendor_diff) 1758 progress_dict["vendor"] = 0.1 1759 if device_specific_diffs: 1760 block_diffs += device_specific_diffs 1761 1762 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true": 1763 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true": 1764 raise RuntimeError( 1765 "can't generate incremental that disables dynamic partitions") 1766 dynamic_partitions_diff = common.DynamicPartitionsDifference( 1767 info_dict=OPTIONS.target_info_dict, 1768 source_info_dict=OPTIONS.source_info_dict, 1769 block_diffs=block_diffs, 1770 progress_dict=progress_dict) 1771 dynamic_partitions_diff.WriteScript( 1772 script, output_zip, write_verify_script=OPTIONS.verify) 1773 else: 1774 for block_diff in block_diffs: 1775 block_diff.WriteScript(script, output_zip, 1776 progress=progress_dict.get(block_diff.partition), 1777 write_verify_script=OPTIONS.verify) 1778 1779 if OPTIONS.two_step: 1780 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 1781 script.WriteRawImage("/boot", "boot.img") 1782 logger.info("writing full boot image (forced by two-step mode)") 1783 1784 if not OPTIONS.two_step: 1785 if updating_boot: 1786 if include_full_boot: 1787 logger.info("boot image changed; including full.") 1788 script.Print("Installing boot image...") 1789 script.WriteRawImage("/boot", "boot.img") 1790 else: 1791 # Produce the boot image by applying a patch to the current 1792 # contents of the boot partition, and write it back to the 1793 # partition. 1794 logger.info("boot image changed; including patch.") 1795 script.Print("Patching boot image...") 1796 script.ShowProgress(0.1, 10) 1797 script.PatchPartition( 1798 '{}:{}:{}:{}'.format( 1799 boot_type, boot_device, target_boot.size, target_boot.sha1), 1800 '{}:{}:{}:{}'.format( 1801 boot_type, boot_device, source_boot.size, source_boot.sha1), 1802 'boot.img.p') 1803 else: 1804 logger.info("boot image unchanged; skipping.") 1805 1806 # Do device-specific installation (eg, write radio image). 1807 device_specific.IncrementalOTA_InstallEnd() 1808 1809 if OPTIONS.extra_script is not None: 1810 script.AppendExtra(OPTIONS.extra_script) 1811 1812 if OPTIONS.wipe_user_data: 1813 script.Print("Erasing user data...") 1814 script.FormatPartition("/data") 1815 1816 if OPTIONS.two_step: 1817 script.AppendExtra(""" 1818set_stage("%(bcb_dev)s", ""); 1819endif; 1820endif; 1821""" % bcb_dev) 1822 1823 script.SetProgress(1) 1824 # For downgrade OTAs, we prefer to use the update-binary in the source 1825 # build that is actually newer than the one in the target build. 1826 if OPTIONS.downgrade: 1827 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary) 1828 else: 1829 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) 1830 metadata["ota-required-cache"] = str(script.required_cache) 1831 1832 # We haven't written the metadata entry yet, which will be handled in 1833 # FinalizeMetadata(). 1834 common.ZipClose(output_zip) 1835 1836 # Sign the generated zip package unless no_signing is specified. 1837 needed_property_files = ( 1838 NonAbOtaPropertyFiles(), 1839 ) 1840 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files) 1841 1842 1843def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False): 1844 """Returns a target-files.zip file for generating secondary payload. 1845 1846 Although the original target-files.zip already contains secondary slot 1847 images (i.e. IMAGES/system_other.img), we need to rename the files to the 1848 ones without _other suffix. Note that we cannot instead modify the names in 1849 META/ab_partitions.txt, because there are no matching partitions on device. 1850 1851 For the partitions that don't have secondary images, the ones for primary 1852 slot will be used. This is to ensure that we always have valid boot, vbmeta, 1853 bootloader images in the inactive slot. 1854 1855 Args: 1856 input_file: The input target-files.zip file. 1857 skip_postinstall: Whether to skip copying the postinstall config file. 1858 1859 Returns: 1860 The filename of the target-files.zip for generating secondary payload. 1861 """ 1862 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 1863 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True) 1864 1865 with zipfile.ZipFile(input_file, 'r') as input_zip: 1866 infolist = input_zip.infolist() 1867 namelist = input_zip.namelist() 1868 1869 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN) 1870 for info in infolist: 1871 unzipped_file = os.path.join(input_tmp, *info.filename.split('/')) 1872 if info.filename == 'IMAGES/system_other.img': 1873 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img') 1874 1875 # Primary images and friends need to be skipped explicitly. 1876 elif info.filename in ('IMAGES/system.img', 1877 'IMAGES/system.map'): 1878 pass 1879 1880 # Skip copying the postinstall config if requested. 1881 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG: 1882 pass 1883 1884 elif info.filename.startswith(('META/', 'IMAGES/', 'RADIO/')): 1885 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename) 1886 1887 common.ZipClose(target_zip) 1888 1889 return target_file 1890 1891 1892def GetTargetFilesZipWithoutPostinstallConfig(input_file): 1893 """Returns a target-files.zip that's not containing postinstall_config.txt. 1894 1895 This allows brillo_update_payload script to skip writing all the postinstall 1896 hooks in the generated payload. The input target-files.zip file will be 1897 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't 1898 contain the postinstall_config.txt entry, the input file will be returned. 1899 1900 Args: 1901 input_file: The input target-files.zip filename. 1902 1903 Returns: 1904 The filename of target-files.zip that doesn't contain postinstall config. 1905 """ 1906 # We should only make a copy if postinstall_config entry exists. 1907 with zipfile.ZipFile(input_file, 'r') as input_zip: 1908 if POSTINSTALL_CONFIG not in input_zip.namelist(): 1909 return input_file 1910 1911 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 1912 shutil.copyfile(input_file, target_file) 1913 common.ZipDelete(target_file, POSTINSTALL_CONFIG) 1914 return target_file 1915 1916 1917def GetTargetFilesZipForRetrofitDynamicPartitions(input_file, 1918 super_block_devices, 1919 dynamic_partition_list): 1920 """Returns a target-files.zip for retrofitting dynamic partitions. 1921 1922 This allows brillo_update_payload to generate an OTA based on the exact 1923 bits on the block devices. Postinstall is disabled. 1924 1925 Args: 1926 input_file: The input target-files.zip filename. 1927 super_block_devices: The list of super block devices 1928 dynamic_partition_list: The list of dynamic partitions 1929 1930 Returns: 1931 The filename of target-files.zip with *.img replaced with super_*.img for 1932 each block device in super_block_devices. 1933 """ 1934 assert super_block_devices, "No super_block_devices are specified." 1935 1936 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev) 1937 for dev in super_block_devices} 1938 1939 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip") 1940 shutil.copyfile(input_file, target_file) 1941 1942 with zipfile.ZipFile(input_file, 'r') as input_zip: 1943 namelist = input_zip.namelist() 1944 1945 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN) 1946 1947 # Remove partitions from META/ab_partitions.txt that is in 1948 # dynamic_partition_list but not in super_block_devices so that 1949 # brillo_update_payload won't generate update for those logical partitions. 1950 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/')) 1951 with open(ab_partitions_file) as f: 1952 ab_partitions_lines = f.readlines() 1953 ab_partitions = [line.strip() for line in ab_partitions_lines] 1954 # Assert that all super_block_devices are in ab_partitions 1955 super_device_not_updated = [partition for partition in super_block_devices 1956 if partition not in ab_partitions] 1957 assert not super_device_not_updated, \ 1958 "{} is in super_block_devices but not in {}".format( 1959 super_device_not_updated, AB_PARTITIONS) 1960 # ab_partitions -= (dynamic_partition_list - super_block_devices) 1961 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt") 1962 with open(new_ab_partitions, 'w') as f: 1963 for partition in ab_partitions: 1964 if (partition in dynamic_partition_list and 1965 partition not in super_block_devices): 1966 logger.info("Dropping %s from ab_partitions.txt", partition) 1967 continue 1968 f.write(partition + "\n") 1969 to_delete = [AB_PARTITIONS] 1970 1971 # Always skip postinstall for a retrofit update. 1972 to_delete += [POSTINSTALL_CONFIG] 1973 1974 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this 1975 # is a regular update on devices without dynamic partitions support. 1976 to_delete += [DYNAMIC_PARTITION_INFO] 1977 1978 # Remove the existing partition images as well as the map files. 1979 to_delete += replace.values() 1980 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices] 1981 1982 common.ZipDelete(target_file, to_delete) 1983 1984 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True) 1985 1986 # Write super_{foo}.img as {foo}.img. 1987 for src, dst in replace.items(): 1988 assert src in namelist, \ 1989 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst) 1990 unzipped_file = os.path.join(input_tmp, *src.split('/')) 1991 common.ZipWrite(target_zip, unzipped_file, arcname=dst) 1992 1993 # Write new ab_partitions.txt file 1994 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS) 1995 1996 common.ZipClose(target_zip) 1997 1998 return target_file 1999 2000 2001def WriteABOTAPackageWithBrilloScript(target_file, output_file, 2002 source_file=None): 2003 """Generates an Android OTA package that has A/B update payload.""" 2004 # Stage the output zip package for package signing. 2005 if not OPTIONS.no_signing: 2006 staging_file = common.MakeTempFile(suffix='.zip') 2007 else: 2008 staging_file = output_file 2009 output_zip = zipfile.ZipFile(staging_file, "w", 2010 compression=zipfile.ZIP_DEFLATED) 2011 2012 if source_file is not None: 2013 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts) 2014 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts) 2015 else: 2016 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts) 2017 source_info = None 2018 2019 # Metadata to comply with Android OTA package format. 2020 metadata = GetPackageMetadata(target_info, source_info) 2021 2022 if OPTIONS.retrofit_dynamic_partitions: 2023 target_file = GetTargetFilesZipForRetrofitDynamicPartitions( 2024 target_file, target_info.get("super_block_devices").strip().split(), 2025 target_info.get("dynamic_partition_list").strip().split()) 2026 elif OPTIONS.skip_postinstall: 2027 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file) 2028 2029 # Generate payload. 2030 payload = Payload() 2031 2032 # Enforce a max timestamp this payload can be applied on top of. 2033 if OPTIONS.downgrade: 2034 max_timestamp = source_info.GetBuildProp("ro.build.date.utc") 2035 else: 2036 max_timestamp = metadata["post-timestamp"] 2037 additional_args = ["--max_timestamp", max_timestamp] 2038 2039 payload.Generate(target_file, source_file, additional_args) 2040 2041 # Sign the payload. 2042 payload_signer = PayloadSigner() 2043 payload.Sign(payload_signer) 2044 2045 # Write the payload into output zip. 2046 payload.WriteToZip(output_zip) 2047 2048 # Generate and include the secondary payload that installs secondary images 2049 # (e.g. system_other.img). 2050 if OPTIONS.include_secondary: 2051 # We always include a full payload for the secondary slot, even when 2052 # building an incremental OTA. See the comments for "--include_secondary". 2053 secondary_target_file = GetTargetFilesZipForSecondaryImages( 2054 target_file, OPTIONS.skip_postinstall) 2055 secondary_payload = Payload(secondary=True) 2056 secondary_payload.Generate(secondary_target_file, 2057 additional_args=additional_args) 2058 secondary_payload.Sign(payload_signer) 2059 secondary_payload.WriteToZip(output_zip) 2060 2061 # If dm-verity is supported for the device, copy contents of care_map 2062 # into A/B OTA package. 2063 target_zip = zipfile.ZipFile(target_file, "r") 2064 if (target_info.get("verity") == "true" or 2065 target_info.get("avb_enable") == "true"): 2066 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if 2067 "META/" + x in target_zip.namelist()] 2068 2069 # Adds care_map if either the protobuf format or the plain text one exists. 2070 if care_map_list: 2071 care_map_name = care_map_list[0] 2072 care_map_data = target_zip.read("META/" + care_map_name) 2073 # In order to support streaming, care_map needs to be packed as 2074 # ZIP_STORED. 2075 common.ZipWriteStr(output_zip, care_map_name, care_map_data, 2076 compress_type=zipfile.ZIP_STORED) 2077 else: 2078 logger.warning("Cannot find care map file in target_file package") 2079 2080 AddCompatibilityArchiveIfTrebleEnabled( 2081 target_zip, output_zip, target_info, source_info) 2082 2083 common.ZipClose(target_zip) 2084 2085 # We haven't written the metadata entry yet, which will be handled in 2086 # FinalizeMetadata(). 2087 common.ZipClose(output_zip) 2088 2089 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers 2090 # all the info of the latter. However, system updaters and OTA servers need to 2091 # take time to switch to the new flag. We keep both of the flags for 2092 # P-timeframe, and will remove StreamingPropertyFiles in later release. 2093 needed_property_files = ( 2094 AbOtaPropertyFiles(), 2095 StreamingPropertyFiles(), 2096 ) 2097 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files) 2098 2099 2100def main(argv): 2101 2102 def option_handler(o, a): 2103 if o in ("-k", "--package_key"): 2104 OPTIONS.package_key = a 2105 elif o in ("-i", "--incremental_from"): 2106 OPTIONS.incremental_source = a 2107 elif o == "--full_radio": 2108 OPTIONS.full_radio = True 2109 elif o == "--full_bootloader": 2110 OPTIONS.full_bootloader = True 2111 elif o == "--wipe_user_data": 2112 OPTIONS.wipe_user_data = True 2113 elif o == "--downgrade": 2114 OPTIONS.downgrade = True 2115 OPTIONS.wipe_user_data = True 2116 elif o == "--override_timestamp": 2117 OPTIONS.downgrade = True 2118 elif o in ("-o", "--oem_settings"): 2119 OPTIONS.oem_source = a.split(',') 2120 elif o == "--oem_no_mount": 2121 OPTIONS.oem_no_mount = True 2122 elif o in ("-e", "--extra_script"): 2123 OPTIONS.extra_script = a 2124 elif o in ("-t", "--worker_threads"): 2125 if a.isdigit(): 2126 OPTIONS.worker_threads = int(a) 2127 else: 2128 raise ValueError("Cannot parse value %r for option %r - only " 2129 "integers are allowed." % (a, o)) 2130 elif o in ("-2", "--two_step"): 2131 OPTIONS.two_step = True 2132 elif o == "--include_secondary": 2133 OPTIONS.include_secondary = True 2134 elif o == "--no_signing": 2135 OPTIONS.no_signing = True 2136 elif o == "--verify": 2137 OPTIONS.verify = True 2138 elif o == "--block": 2139 OPTIONS.block_based = True 2140 elif o in ("-b", "--binary"): 2141 OPTIONS.updater_binary = a 2142 elif o == "--stash_threshold": 2143 try: 2144 OPTIONS.stash_threshold = float(a) 2145 except ValueError: 2146 raise ValueError("Cannot parse value %r for option %r - expecting " 2147 "a float" % (a, o)) 2148 elif o == "--log_diff": 2149 OPTIONS.log_diff = a 2150 elif o == "--payload_signer": 2151 OPTIONS.payload_signer = a 2152 elif o == "--payload_signer_args": 2153 OPTIONS.payload_signer_args = shlex.split(a) 2154 elif o == "--payload_signer_key_size": 2155 OPTIONS.payload_signer_key_size = a 2156 elif o == "--extracted_input_target_files": 2157 OPTIONS.extracted_input = a 2158 elif o == "--skip_postinstall": 2159 OPTIONS.skip_postinstall = True 2160 elif o == "--retrofit_dynamic_partitions": 2161 OPTIONS.retrofit_dynamic_partitions = True 2162 elif o == "--skip_compatibility_check": 2163 OPTIONS.skip_compatibility_check = True 2164 elif o == "--output_metadata_path": 2165 OPTIONS.output_metadata_path = a 2166 else: 2167 return False 2168 return True 2169 2170 args = common.ParseOptions(argv, __doc__, 2171 extra_opts="b:k:i:d:e:t:2o:", 2172 extra_long_opts=[ 2173 "package_key=", 2174 "incremental_from=", 2175 "full_radio", 2176 "full_bootloader", 2177 "wipe_user_data", 2178 "downgrade", 2179 "override_timestamp", 2180 "extra_script=", 2181 "worker_threads=", 2182 "two_step", 2183 "include_secondary", 2184 "no_signing", 2185 "block", 2186 "binary=", 2187 "oem_settings=", 2188 "oem_no_mount", 2189 "verify", 2190 "stash_threshold=", 2191 "log_diff=", 2192 "payload_signer=", 2193 "payload_signer_args=", 2194 "payload_signer_key_size=", 2195 "extracted_input_target_files=", 2196 "skip_postinstall", 2197 "retrofit_dynamic_partitions", 2198 "skip_compatibility_check", 2199 "output_metadata_path=", 2200 ], extra_option_handler=option_handler) 2201 2202 if len(args) != 2: 2203 common.Usage(__doc__) 2204 sys.exit(1) 2205 2206 common.InitLogging() 2207 2208 if OPTIONS.downgrade: 2209 # We should only allow downgrading incrementals (as opposed to full). 2210 # Otherwise the device may go back from arbitrary build with this full 2211 # OTA package. 2212 if OPTIONS.incremental_source is None: 2213 raise ValueError("Cannot generate downgradable full OTAs") 2214 2215 # Load the build info dicts from the zip directly or the extracted input 2216 # directory. We don't need to unzip the entire target-files zips, because they 2217 # won't be needed for A/B OTAs (brillo_update_payload does that on its own). 2218 # When loading the info dicts, we don't need to provide the second parameter 2219 # to common.LoadInfoDict(). Specifying the second parameter allows replacing 2220 # some properties with their actual paths, such as 'selinux_fc', 2221 # 'ramdisk_dir', which won't be used during OTA generation. 2222 if OPTIONS.extracted_input is not None: 2223 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input) 2224 else: 2225 with zipfile.ZipFile(args[0], 'r') as input_zip: 2226 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 2227 2228 logger.info("--- target info ---") 2229 common.DumpInfoDict(OPTIONS.info_dict) 2230 2231 # Load the source build dict if applicable. 2232 if OPTIONS.incremental_source is not None: 2233 OPTIONS.target_info_dict = OPTIONS.info_dict 2234 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip: 2235 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 2236 2237 logger.info("--- source info ---") 2238 common.DumpInfoDict(OPTIONS.source_info_dict) 2239 2240 # Load OEM dicts if provided. 2241 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source) 2242 2243 # Assume retrofitting dynamic partitions when base build does not set 2244 # use_dynamic_partitions but target build does. 2245 if (OPTIONS.source_info_dict and 2246 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and 2247 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"): 2248 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true": 2249 raise common.ExternalError( 2250 "Expect to generate incremental OTA for retrofitting dynamic " 2251 "partitions, but dynamic_partition_retrofit is not set in target " 2252 "build.") 2253 logger.info("Implicitly generating retrofit incremental OTA.") 2254 OPTIONS.retrofit_dynamic_partitions = True 2255 2256 # Skip postinstall for retrofitting dynamic partitions. 2257 if OPTIONS.retrofit_dynamic_partitions: 2258 OPTIONS.skip_postinstall = True 2259 2260 ab_update = OPTIONS.info_dict.get("ab_update") == "true" 2261 2262 # Use the default key to sign the package if not specified with package_key. 2263 # package_keys are needed on ab_updates, so always define them if an 2264 # ab_update is getting created. 2265 if not OPTIONS.no_signing or ab_update: 2266 if OPTIONS.package_key is None: 2267 OPTIONS.package_key = OPTIONS.info_dict.get( 2268 "default_system_dev_certificate", 2269 "build/target/product/security/testkey") 2270 # Get signing keys 2271 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 2272 2273 if ab_update: 2274 WriteABOTAPackageWithBrilloScript( 2275 target_file=args[0], 2276 output_file=args[1], 2277 source_file=OPTIONS.incremental_source) 2278 2279 logger.info("done.") 2280 return 2281 2282 # Sanity check the loaded info dicts first. 2283 if OPTIONS.info_dict.get("no_recovery") == "true": 2284 raise common.ExternalError( 2285 "--- target build has specified no recovery ---") 2286 2287 # Non-A/B OTAs rely on /cache partition to store temporary files. 2288 cache_size = OPTIONS.info_dict.get("cache_size") 2289 if cache_size is None: 2290 logger.warning("--- can't determine the cache partition size ---") 2291 OPTIONS.cache_size = cache_size 2292 2293 if OPTIONS.extra_script is not None: 2294 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 2295 2296 if OPTIONS.extracted_input is not None: 2297 OPTIONS.input_tmp = OPTIONS.extracted_input 2298 else: 2299 logger.info("unzipping target target-files...") 2300 OPTIONS.input_tmp = common.UnzipTemp(args[0], UNZIP_PATTERN) 2301 OPTIONS.target_tmp = OPTIONS.input_tmp 2302 2303 # If the caller explicitly specified the device-specific extensions path via 2304 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it 2305 # is present in the target target_files. Otherwise, take the path of the file 2306 # from 'tool_extensions' in the info dict and look for that in the local 2307 # filesystem, relative to the current directory. 2308 if OPTIONS.device_specific is None: 2309 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") 2310 if os.path.exists(from_input): 2311 logger.info("(using device-specific extensions from target_files)") 2312 OPTIONS.device_specific = from_input 2313 else: 2314 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions") 2315 2316 if OPTIONS.device_specific is not None: 2317 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) 2318 2319 # Generate a full OTA. 2320 if OPTIONS.incremental_source is None: 2321 with zipfile.ZipFile(args[0], 'r') as input_zip: 2322 WriteFullOTAPackage( 2323 input_zip, 2324 output_file=args[1]) 2325 2326 # Generate an incremental OTA. 2327 else: 2328 logger.info("unzipping source target-files...") 2329 OPTIONS.source_tmp = common.UnzipTemp( 2330 OPTIONS.incremental_source, UNZIP_PATTERN) 2331 with zipfile.ZipFile(args[0], 'r') as input_zip, \ 2332 zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip: 2333 WriteBlockIncrementalOTAPackage( 2334 input_zip, 2335 source_zip, 2336 output_file=args[1]) 2337 2338 if OPTIONS.log_diff: 2339 with open(OPTIONS.log_diff, 'w') as out_file: 2340 import target_files_diff 2341 target_files_diff.recursiveDiff( 2342 '', OPTIONS.source_tmp, OPTIONS.input_tmp, out_file) 2343 2344 logger.info("done.") 2345 2346 2347if __name__ == '__main__': 2348 try: 2349 common.CloseInheritedPipes() 2350 main(sys.argv[1:]) 2351 except common.ExternalError: 2352 logger.exception("\n ERROR:\n") 2353 sys.exit(1) 2354 finally: 2355 common.Cleanup() 2356