Home | History | Annotate | Download | only in releasetools
      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 """
     18 Given a target-files zipfile, produces an OTA package that installs
     19 that build.  An incremental OTA is produced if -i is given, otherwise
     20 a full OTA is produced.
     21 
     22 Usage:  ota_from_target_files [flags] input_target_files output_ota_package
     23 
     24   --board_config  <file>
     25       Deprecated.
     26 
     27   -k (--package_key) <key> Key to use to sign the package (default is
     28       the value of default_system_dev_certificate from the input
     29       target-files's META/misc_info.txt, or
     30       "build/target/product/security/testkey" if that value is not
     31       specified).
     32 
     33       For incremental OTAs, the default value is based on the source
     34       target-file, not the target build.
     35 
     36   -i  (--incremental_from)  <file>
     37       Generate an incremental OTA using the given target-files zip as
     38       the starting build.
     39 
     40   --full_radio
     41       When generating an incremental OTA, always include a full copy of
     42       radio image. This option is only meaningful when -i is specified,
     43       because a full radio is always included in a full OTA if applicable.
     44 
     45   --full_bootloader
     46       Similar to --full_radio. When generating an incremental OTA, always
     47       include a full copy of bootloader image.
     48 
     49   -v  (--verify)
     50       Remount and verify the checksums of the files written to the
     51       system and vendor (if used) partitions.  Incremental builds only.
     52 
     53   -o  (--oem_settings)  <main_file[,additional_files...]>
     54       Comma seperated list of files used to specify the expected OEM-specific
     55       properties on the OEM partition of the intended device.
     56       Multiple expected values can be used by providing multiple files.
     57 
     58   --oem_no_mount
     59       For devices with OEM-specific properties but without an OEM partition,
     60       do not mount the OEM partition in the updater-script. This should be
     61       very rarely used, since it's expected to have a dedicated OEM partition
     62       for OEM-specific properties. Only meaningful when -o is specified.
     63 
     64   -w  (--wipe_user_data)
     65       Generate an OTA package that will wipe the user data partition
     66       when installed.
     67 
     68   --downgrade
     69       Intentionally generate an incremental OTA that updates from a newer
     70       build to an older one (based on timestamp comparison). "post-timestamp"
     71       will be replaced by "ota-downgrade=yes" in the metadata file. A data
     72       wipe will always be enforced, so "ota-wipe=yes" will also be included in
     73       the metadata file. The update-binary in the source build will be used in
     74       the OTA package, unless --binary flag is specified. Please also check the
     75       doc for --override_timestamp below.
     76 
     77   --override_timestamp
     78       Intentionally generate an incremental OTA that updates from a newer
     79       build to an older one (based on timestamp comparison), by overriding the
     80       timestamp in package metadata. This differs from --downgrade flag: we
     81       know for sure this is NOT an actual downgrade case, but two builds are
     82       cut in a reverse order. A legit use case is that we cut a new build C
     83       (after having A and B), but want to enfore an update path of A -> C -> B.
     84       Specifying --downgrade may not help since that would enforce a data wipe
     85       for C -> B update. The value of "post-timestamp" will be set to the newer
     86       timestamp plus one, so that the package can be pushed and applied.
     87 
     88   -e  (--extra_script)  <file>
     89       Insert the contents of file at the end of the update script.
     90 
     91   -2  (--two_step)
     92       Generate a 'two-step' OTA package, where recovery is updated
     93       first, so that any changes made to the system partition are done
     94       using the new recovery (new kernel, etc.).
     95 
     96   --block
     97       Generate a block-based OTA for non-A/B device. We have deprecated the
     98       support for file-based OTA since O. Block-based OTA will be used by
     99       default for all non-A/B devices. Keeping this flag here to not break
    100       existing callers.
    101 
    102   -b  (--binary)  <file>
    103       Use the given binary as the update-binary in the output package,
    104       instead of the binary in the build's target_files.  Use for
    105       development only.
    106 
    107   -t  (--worker_threads) <int>
    108       Specifies the number of worker-threads that will be used when
    109       generating patches for incremental updates (defaults to 3).
    110 
    111   --stash_threshold <float>
    112       Specifies the threshold that will be used to compute the maximum
    113       allowed stash size (defaults to 0.8).
    114 
    115   --gen_verify
    116       Generate an OTA package that verifies the partitions.
    117 
    118   --log_diff <file>
    119       Generate a log file that shows the differences in the source and target
    120       builds for an incremental package. This option is only meaningful when
    121       -i is specified.
    122 
    123   --payload_signer <signer>
    124       Specify the signer when signing the payload and metadata for A/B OTAs.
    125       By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
    126       with the package private key. If the private key cannot be accessed
    127       directly, a payload signer that knows how to do that should be specified.
    128       The signer will be supplied with "-inkey <path_to_key>",
    129       "-in <input_file>" and "-out <output_file>" parameters.
    130 
    131   --payload_signer_args <args>
    132       Specify the arguments needed for payload signer.
    133 """
    134 
    135 from __future__ import print_function
    136 
    137 import sys
    138 
    139 if sys.hexversion < 0x02070000:
    140   print("Python 2.7 or newer is required.", file=sys.stderr)
    141   sys.exit(1)
    142 
    143 import copy
    144 import multiprocessing
    145 import os.path
    146 import subprocess
    147 import shlex
    148 import tempfile
    149 import zipfile
    150 
    151 import common
    152 import edify_generator
    153 import sparse_img
    154 
    155 OPTIONS = common.OPTIONS
    156 OPTIONS.package_key = None
    157 OPTIONS.incremental_source = None
    158 OPTIONS.verify = False
    159 OPTIONS.patch_threshold = 0.95
    160 OPTIONS.wipe_user_data = False
    161 OPTIONS.downgrade = False
    162 OPTIONS.timestamp = False
    163 OPTIONS.extra_script = None
    164 OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
    165 if OPTIONS.worker_threads == 0:
    166   OPTIONS.worker_threads = 1
    167 OPTIONS.two_step = False
    168 OPTIONS.no_signing = False
    169 OPTIONS.block_based = True
    170 OPTIONS.updater_binary = None
    171 OPTIONS.oem_source = None
    172 OPTIONS.oem_no_mount = False
    173 OPTIONS.fallback_to_full = True
    174 OPTIONS.full_radio = False
    175 OPTIONS.full_bootloader = False
    176 # Stash size cannot exceed cache_size * threshold.
    177 OPTIONS.cache_size = None
    178 OPTIONS.stash_threshold = 0.8
    179 OPTIONS.gen_verify = False
    180 OPTIONS.log_diff = None
    181 OPTIONS.payload_signer = None
    182 OPTIONS.payload_signer_args = []
    183 OPTIONS.extracted_input = None
    184 OPTIONS.key_passwords = []
    185 
    186 METADATA_NAME = 'META-INF/com/android/metadata'
    187 UNZIP_PATTERN = ['IMAGES/*', 'META/*']
    188 
    189 
    190 def SignOutput(temp_zip_name, output_zip_name):
    191   pw = OPTIONS.key_passwords[OPTIONS.package_key]
    192 
    193   common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
    194                   whole_file=True)
    195 
    196 
    197 def AppendAssertions(script, info_dict, oem_dicts=None):
    198   oem_props = info_dict.get("oem_fingerprint_properties")
    199   if not oem_props:
    200     device = GetBuildProp("ro.product.device", info_dict)
    201     script.AssertDevice(device)
    202   else:
    203     if not oem_dicts:
    204       raise common.ExternalError(
    205           "No OEM file provided to answer expected assertions")
    206     for prop in oem_props.split():
    207       values = []
    208       for oem_dict in oem_dicts:
    209         if oem_dict.get(prop):
    210           values.append(oem_dict[prop])
    211       if not values:
    212         raise common.ExternalError(
    213             "The OEM file is missing the property %s" % prop)
    214       script.AssertOemProperty(prop, values)
    215 
    216 
    217 def _LoadOemDicts(script, recovery_mount_options=None):
    218   """Returns the list of loaded OEM properties dict."""
    219   oem_dicts = None
    220   if OPTIONS.oem_source is None:
    221     raise common.ExternalError("OEM source required for this build")
    222   if not OPTIONS.oem_no_mount and script:
    223     script.Mount("/oem", recovery_mount_options)
    224   oem_dicts = []
    225   for oem_file in OPTIONS.oem_source:
    226     oem_dicts.append(common.LoadDictionaryFromLines(
    227         open(oem_file).readlines()))
    228   return oem_dicts
    229 
    230 
    231 def _WriteRecoveryImageToBoot(script, output_zip):
    232   """Find and write recovery image to /boot in two-step OTA.
    233 
    234   In two-step OTAs, we write recovery image to /boot as the first step so that
    235   we can reboot to there and install a new recovery image to /recovery.
    236   A special "recovery-two-step.img" will be preferred, which encodes the correct
    237   path of "/boot". Otherwise the device may show "device is corrupt" message
    238   when booting into /boot.
    239 
    240   Fall back to using the regular recovery.img if the two-step recovery image
    241   doesn't exist. Note that rebuilding the special image at this point may be
    242   infeasible, because we don't have the desired boot signer and keys when
    243   calling ota_from_target_files.py.
    244   """
    245 
    246   recovery_two_step_img_name = "recovery-two-step.img"
    247   recovery_two_step_img_path = os.path.join(
    248       OPTIONS.input_tmp, "IMAGES", recovery_two_step_img_name)
    249   if os.path.exists(recovery_two_step_img_path):
    250     recovery_two_step_img = common.GetBootableImage(
    251         recovery_two_step_img_name, recovery_two_step_img_name,
    252         OPTIONS.input_tmp, "RECOVERY")
    253     common.ZipWriteStr(
    254         output_zip, recovery_two_step_img_name, recovery_two_step_img.data)
    255     print("two-step package: using %s in stage 1/3" % (
    256         recovery_two_step_img_name,))
    257     script.WriteRawImage("/boot", recovery_two_step_img_name)
    258   else:
    259     print("two-step package: using recovery.img in stage 1/3")
    260     # The "recovery.img" entry has been written into package earlier.
    261     script.WriteRawImage("/boot", "recovery.img")
    262 
    263 
    264 def HasRecoveryPatch(target_files_zip):
    265   namelist = [name for name in target_files_zip.namelist()]
    266   return ("SYSTEM/recovery-from-boot.p" in namelist or
    267           "SYSTEM/etc/recovery.img" in namelist)
    268 
    269 
    270 def HasVendorPartition(target_files_zip):
    271   try:
    272     target_files_zip.getinfo("VENDOR/")
    273     return True
    274   except KeyError:
    275     return False
    276 
    277 
    278 def GetOemProperty(name, oem_props, oem_dict, info_dict):
    279   if oem_props is not None and name in oem_props:
    280     return oem_dict[name]
    281   return GetBuildProp(name, info_dict)
    282 
    283 
    284 def CalculateFingerprint(oem_props, oem_dict, info_dict):
    285   if oem_props is None:
    286     return GetBuildProp("ro.build.fingerprint", info_dict)
    287   return "%s/%s/%s:%s" % (
    288       GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
    289       GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
    290       GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
    291       GetBuildProp("ro.build.thumbprint", info_dict))
    292 
    293 
    294 def GetImage(which, tmpdir):
    295   """Returns an image object suitable for passing to BlockImageDiff.
    296 
    297   'which' partition must be "system" or "vendor". A prebuilt image and file
    298   map must already exist in tmpdir.
    299   """
    300 
    301   assert which in ("system", "vendor")
    302 
    303   path = os.path.join(tmpdir, "IMAGES", which + ".img")
    304   mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
    305 
    306   # The image and map files must have been created prior to calling
    307   # ota_from_target_files.py (since LMP).
    308   assert os.path.exists(path) and os.path.exists(mappath)
    309 
    310   # Bug: http://b/20939131
    311   # In ext4 filesystems, block 0 might be changed even being mounted
    312   # R/O. We add it to clobbered_blocks so that it will be written to the
    313   # target unconditionally. Note that they are still part of care_map.
    314   clobbered_blocks = "0"
    315 
    316   return sparse_img.SparseImage(path, mappath, clobbered_blocks)
    317 
    318 
    319 def AddCompatibilityArchive(target_zip, output_zip, system_included=True,
    320                             vendor_included=True):
    321   """Adds compatibility info from target files into the output zip.
    322 
    323   Metadata used for on-device compatibility verification is retrieved from
    324   target_zip then added to compatibility.zip which is added to the output_zip
    325   archive.
    326 
    327   Compatibility archive should only be included for devices with a vendor
    328   partition as checking provides value when system and vendor are independently
    329   versioned.
    330 
    331   Args:
    332     target_zip: Zip file containing the source files to be included for OTA.
    333     output_zip: Zip file that will be sent for OTA.
    334     system_included: If True, the system image will be updated and therefore
    335         its metadata should be included.
    336     vendor_included: If True, the vendor image will be updated and therefore
    337         its metadata should be included.
    338   """
    339 
    340   # Determine what metadata we need. Files are names relative to META/.
    341   compatibility_files = []
    342   vendor_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
    343   system_metadata = ("system_manifest.xml", "system_matrix.xml")
    344   if vendor_included:
    345     compatibility_files += vendor_metadata
    346   if system_included:
    347     compatibility_files += system_metadata
    348 
    349   # Create new archive.
    350   compatibility_archive = tempfile.NamedTemporaryFile()
    351   compatibility_archive_zip = zipfile.ZipFile(compatibility_archive, "w",
    352       compression=zipfile.ZIP_DEFLATED)
    353 
    354   # Add metadata.
    355   for file_name in compatibility_files:
    356     target_file_name = "META/" + file_name
    357 
    358     if target_file_name in target_zip.namelist():
    359       data = target_zip.read(target_file_name)
    360       common.ZipWriteStr(compatibility_archive_zip, file_name, data)
    361 
    362   # Ensure files are written before we copy into output_zip.
    363   compatibility_archive_zip.close()
    364 
    365   # Only add the archive if we have any compatibility info.
    366   if compatibility_archive_zip.namelist():
    367     common.ZipWrite(output_zip, compatibility_archive.name,
    368                     arcname="compatibility.zip",
    369                     compress_type=zipfile.ZIP_STORED)
    370 
    371 
    372 def WriteFullOTAPackage(input_zip, output_zip):
    373   # TODO: how to determine this?  We don't know what version it will
    374   # be installed on top of. For now, we expect the API just won't
    375   # change very often. Similarly for fstab, it might have changed
    376   # in the target build.
    377   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
    378 
    379   recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
    380   oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
    381   oem_dicts = None
    382   if oem_props:
    383     oem_dicts = _LoadOemDicts(script, recovery_mount_options)
    384 
    385   target_fp = CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
    386                                    OPTIONS.info_dict)
    387   metadata = {
    388       "post-build": target_fp,
    389       "pre-device": GetOemProperty("ro.product.device", oem_props,
    390                                    oem_dicts and oem_dicts[0],
    391                                    OPTIONS.info_dict),
    392       "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
    393   }
    394 
    395   device_specific = common.DeviceSpecificParams(
    396       input_zip=input_zip,
    397       input_version=OPTIONS.info_dict["recovery_api_version"],
    398       output_zip=output_zip,
    399       script=script,
    400       input_tmp=OPTIONS.input_tmp,
    401       metadata=metadata,
    402       info_dict=OPTIONS.info_dict)
    403 
    404   assert HasRecoveryPatch(input_zip)
    405 
    406   metadata["ota-type"] = "BLOCK"
    407 
    408   ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
    409   ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
    410   script.AssertOlderBuild(ts, ts_text)
    411 
    412   AppendAssertions(script, OPTIONS.info_dict, oem_dicts)
    413   device_specific.FullOTA_Assertions()
    414 
    415   # Two-step package strategy (in chronological order, which is *not*
    416   # the order in which the generated script has things):
    417   #
    418   # if stage is not "2/3" or "3/3":
    419   #    write recovery image to boot partition
    420   #    set stage to "2/3"
    421   #    reboot to boot partition and restart recovery
    422   # else if stage is "2/3":
    423   #    write recovery image to recovery partition
    424   #    set stage to "3/3"
    425   #    reboot to recovery partition and restart recovery
    426   # else:
    427   #    (stage must be "3/3")
    428   #    set stage to ""
    429   #    do normal full package installation:
    430   #       wipe and install system, boot image, etc.
    431   #       set up system to update recovery partition on first boot
    432   #    complete script normally
    433   #    (allow recovery to mark itself finished and reboot)
    434 
    435   recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
    436                                          OPTIONS.input_tmp, "RECOVERY")
    437   if OPTIONS.two_step:
    438     if not OPTIONS.info_dict.get("multistage_support", None):
    439       assert False, "two-step packages not supported by this build"
    440     fs = OPTIONS.info_dict["fstab"]["/misc"]
    441     assert fs.fs_type.upper() == "EMMC", \
    442         "two-step packages only supported on devices with EMMC /misc partitions"
    443     bcb_dev = {"bcb_dev": fs.device}
    444     common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
    445     script.AppendExtra("""
    446 if get_stage("%(bcb_dev)s") == "2/3" then
    447 """ % bcb_dev)
    448 
    449     # Stage 2/3: Write recovery image to /recovery (currently running /boot).
    450     script.Comment("Stage 2/3")
    451     script.WriteRawImage("/recovery", "recovery.img")
    452     script.AppendExtra("""
    453 set_stage("%(bcb_dev)s", "3/3");
    454 reboot_now("%(bcb_dev)s", "recovery");
    455 else if get_stage("%(bcb_dev)s") == "3/3" then
    456 """ % bcb_dev)
    457 
    458     # Stage 3/3: Make changes.
    459     script.Comment("Stage 3/3")
    460 
    461   # Dump fingerprints
    462   script.Print("Target: %s" % target_fp)
    463 
    464   device_specific.FullOTA_InstallBegin()
    465 
    466   system_progress = 0.75
    467 
    468   if OPTIONS.wipe_user_data:
    469     system_progress -= 0.1
    470   if HasVendorPartition(input_zip):
    471     system_progress -= 0.1
    472 
    473   # Place a copy of file_contexts.bin into the OTA package which will be used
    474   # by the recovery program.
    475   if "selinux_fc" in OPTIONS.info_dict:
    476     WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
    477 
    478   recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
    479 
    480   script.ShowProgress(system_progress, 0)
    481 
    482   # Full OTA is done as an "incremental" against an empty source image. This
    483   # has the effect of writing new data from the package to the entire
    484   # partition, but lets us reuse the updater code that writes incrementals to
    485   # do it.
    486   system_tgt = GetImage("system", OPTIONS.input_tmp)
    487   system_tgt.ResetFileMap()
    488   system_diff = common.BlockDifference("system", system_tgt, src=None)
    489   system_diff.WriteScript(script, output_zip)
    490 
    491   boot_img = common.GetBootableImage(
    492       "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
    493 
    494   if HasVendorPartition(input_zip):
    495     script.ShowProgress(0.1, 0)
    496 
    497     vendor_tgt = GetImage("vendor", OPTIONS.input_tmp)
    498     vendor_tgt.ResetFileMap()
    499     vendor_diff = common.BlockDifference("vendor", vendor_tgt)
    500     vendor_diff.WriteScript(script, output_zip)
    501 
    502   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
    503   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
    504 
    505   script.ShowProgress(0.05, 5)
    506   script.WriteRawImage("/boot", "boot.img")
    507 
    508   script.ShowProgress(0.2, 10)
    509   device_specific.FullOTA_InstallEnd()
    510 
    511   if OPTIONS.extra_script is not None:
    512     script.AppendExtra(OPTIONS.extra_script)
    513 
    514   script.UnmountAll()
    515 
    516   if OPTIONS.wipe_user_data:
    517     script.ShowProgress(0.1, 10)
    518     script.FormatPartition("/data")
    519 
    520   if OPTIONS.two_step:
    521     script.AppendExtra("""
    522 set_stage("%(bcb_dev)s", "");
    523 """ % bcb_dev)
    524     script.AppendExtra("else\n")
    525 
    526     # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
    527     script.Comment("Stage 1/3")
    528     _WriteRecoveryImageToBoot(script, output_zip)
    529 
    530     script.AppendExtra("""
    531 set_stage("%(bcb_dev)s", "2/3");
    532 reboot_now("%(bcb_dev)s", "");
    533 endif;
    534 endif;
    535 """ % bcb_dev)
    536 
    537   script.SetProgress(1)
    538   script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
    539   metadata["ota-required-cache"] = str(script.required_cache)
    540   WriteMetadata(metadata, output_zip)
    541 
    542 
    543 def WritePolicyConfig(file_name, output_zip):
    544   common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
    545 
    546 
    547 def WriteMetadata(metadata, output_zip):
    548   value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.iteritems())])
    549   common.ZipWriteStr(output_zip, METADATA_NAME, value,
    550                      compress_type=zipfile.ZIP_STORED)
    551 
    552 
    553 def GetBuildProp(prop, info_dict):
    554   """Return the fingerprint of the build of a given target-files info_dict."""
    555   try:
    556     return info_dict.get("build.prop", {})[prop]
    557   except KeyError:
    558     raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
    559 
    560 
    561 def HandleDowngradeMetadata(metadata):
    562   # Only incremental OTAs are allowed to reach here.
    563   assert OPTIONS.incremental_source is not None
    564 
    565   post_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.target_info_dict)
    566   pre_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.source_info_dict)
    567   is_downgrade = long(post_timestamp) < long(pre_timestamp)
    568 
    569   if OPTIONS.downgrade:
    570     if not is_downgrade:
    571       raise RuntimeError("--downgrade specified but no downgrade detected: "
    572                          "pre: %s, post: %s" % (pre_timestamp, post_timestamp))
    573     metadata["ota-downgrade"] = "yes"
    574   elif OPTIONS.timestamp:
    575     if not is_downgrade:
    576       raise RuntimeError("--timestamp specified but no timestamp hack needed: "
    577                          "pre: %s, post: %s" % (pre_timestamp, post_timestamp))
    578     metadata["post-timestamp"] = str(long(pre_timestamp) + 1)
    579   else:
    580     if is_downgrade:
    581       raise RuntimeError("Downgrade detected based on timestamp check: "
    582                          "pre: %s, post: %s. Need to specify --timestamp OR "
    583                          "--downgrade to allow building the incremental." % (
    584                              pre_timestamp, post_timestamp))
    585     metadata["post-timestamp"] = post_timestamp
    586 
    587 
    588 def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
    589   source_version = OPTIONS.source_info_dict["recovery_api_version"]
    590   target_version = OPTIONS.target_info_dict["recovery_api_version"]
    591 
    592   if source_version == 0:
    593     print("WARNING: generating edify script for a source that "
    594           "can't install it.")
    595   script = edify_generator.EdifyGenerator(
    596       source_version, OPTIONS.target_info_dict,
    597       fstab=OPTIONS.source_info_dict["fstab"])
    598 
    599   recovery_mount_options = OPTIONS.source_info_dict.get(
    600       "recovery_mount_options")
    601   source_oem_props = OPTIONS.source_info_dict.get("oem_fingerprint_properties")
    602   target_oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
    603   oem_dicts = None
    604   if source_oem_props and target_oem_props:
    605     oem_dicts = _LoadOemDicts(script, recovery_mount_options)
    606 
    607   metadata = {
    608       "pre-device": GetOemProperty("ro.product.device", source_oem_props,
    609                                    oem_dicts and oem_dicts[0],
    610                                    OPTIONS.source_info_dict),
    611       "ota-type": "BLOCK",
    612   }
    613 
    614   HandleDowngradeMetadata(metadata)
    615 
    616   device_specific = common.DeviceSpecificParams(
    617       source_zip=source_zip,
    618       source_version=source_version,
    619       target_zip=target_zip,
    620       target_version=target_version,
    621       output_zip=output_zip,
    622       script=script,
    623       metadata=metadata,
    624       info_dict=OPTIONS.source_info_dict)
    625 
    626   source_fp = CalculateFingerprint(source_oem_props, oem_dicts and oem_dicts[0],
    627                                    OPTIONS.source_info_dict)
    628   target_fp = CalculateFingerprint(target_oem_props, oem_dicts and oem_dicts[0],
    629                                    OPTIONS.target_info_dict)
    630   metadata["pre-build"] = source_fp
    631   metadata["post-build"] = target_fp
    632   metadata["pre-build-incremental"] = GetBuildProp(
    633       "ro.build.version.incremental", OPTIONS.source_info_dict)
    634   metadata["post-build-incremental"] = GetBuildProp(
    635       "ro.build.version.incremental", OPTIONS.target_info_dict)
    636 
    637   source_boot = common.GetBootableImage(
    638       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
    639       OPTIONS.source_info_dict)
    640   target_boot = common.GetBootableImage(
    641       "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
    642   updating_boot = (not OPTIONS.two_step and
    643                    (source_boot.data != target_boot.data))
    644 
    645   target_recovery = common.GetBootableImage(
    646       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
    647 
    648   system_src = GetImage("system", OPTIONS.source_tmp)
    649   system_tgt = GetImage("system", OPTIONS.target_tmp)
    650 
    651   blockimgdiff_version = 1
    652   if OPTIONS.info_dict:
    653     blockimgdiff_version = max(
    654         int(i) for i in
    655         OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
    656 
    657   # Check the first block of the source system partition for remount R/W only
    658   # if the filesystem is ext4.
    659   system_src_partition = OPTIONS.source_info_dict["fstab"]["/system"]
    660   check_first_block = system_src_partition.fs_type == "ext4"
    661   # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
    662   # in zip formats. However with squashfs, a) all files are compressed in LZ4;
    663   # b) the blocks listed in block map may not contain all the bytes for a given
    664   # file (because they're rounded to be 4K-aligned).
    665   system_tgt_partition = OPTIONS.target_info_dict["fstab"]["/system"]
    666   disable_imgdiff = (system_src_partition.fs_type == "squashfs" or
    667                      system_tgt_partition.fs_type == "squashfs")
    668   system_diff = common.BlockDifference("system", system_tgt, system_src,
    669                                        check_first_block,
    670                                        version=blockimgdiff_version,
    671                                        disable_imgdiff=disable_imgdiff)
    672 
    673   if HasVendorPartition(target_zip):
    674     if not HasVendorPartition(source_zip):
    675       raise RuntimeError("can't generate incremental that adds /vendor")
    676     vendor_src = GetImage("vendor", OPTIONS.source_tmp)
    677     vendor_tgt = GetImage("vendor", OPTIONS.target_tmp)
    678 
    679     # Check first block of vendor partition for remount R/W only if
    680     # disk type is ext4
    681     vendor_partition = OPTIONS.source_info_dict["fstab"]["/vendor"]
    682     check_first_block = vendor_partition.fs_type == "ext4"
    683     disable_imgdiff = vendor_partition.fs_type == "squashfs"
    684     vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
    685                                          check_first_block,
    686                                          version=blockimgdiff_version,
    687                                          disable_imgdiff=disable_imgdiff)
    688   else:
    689     vendor_diff = None
    690 
    691   AppendAssertions(script, OPTIONS.target_info_dict, oem_dicts)
    692   device_specific.IncrementalOTA_Assertions()
    693 
    694   # Two-step incremental package strategy (in chronological order,
    695   # which is *not* the order in which the generated script has
    696   # things):
    697   #
    698   # if stage is not "2/3" or "3/3":
    699   #    do verification on current system
    700   #    write recovery image to boot partition
    701   #    set stage to "2/3"
    702   #    reboot to boot partition and restart recovery
    703   # else if stage is "2/3":
    704   #    write recovery image to recovery partition
    705   #    set stage to "3/3"
    706   #    reboot to recovery partition and restart recovery
    707   # else:
    708   #    (stage must be "3/3")
    709   #    perform update:
    710   #       patch system files, etc.
    711   #       force full install of new boot image
    712   #       set up system to update recovery partition on first boot
    713   #    complete script normally
    714   #    (allow recovery to mark itself finished and reboot)
    715 
    716   if OPTIONS.two_step:
    717     if not OPTIONS.source_info_dict.get("multistage_support", None):
    718       assert False, "two-step packages not supported by this build"
    719     fs = OPTIONS.source_info_dict["fstab"]["/misc"]
    720     assert fs.fs_type.upper() == "EMMC", \
    721         "two-step packages only supported on devices with EMMC /misc partitions"
    722     bcb_dev = {"bcb_dev": fs.device}
    723     common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
    724     script.AppendExtra("""
    725 if get_stage("%(bcb_dev)s") == "2/3" then
    726 """ % bcb_dev)
    727 
    728     # Stage 2/3: Write recovery image to /recovery (currently running /boot).
    729     script.Comment("Stage 2/3")
    730     script.AppendExtra("sleep(20);\n")
    731     script.WriteRawImage("/recovery", "recovery.img")
    732     script.AppendExtra("""
    733 set_stage("%(bcb_dev)s", "3/3");
    734 reboot_now("%(bcb_dev)s", "recovery");
    735 else if get_stage("%(bcb_dev)s") != "3/3" then
    736 """ % bcb_dev)
    737 
    738     # Stage 1/3: (a) Verify the current system.
    739     script.Comment("Stage 1/3")
    740 
    741   # Dump fingerprints
    742   script.Print("Source: %s" % (source_fp,))
    743   script.Print("Target: %s" % (target_fp,))
    744 
    745   script.Print("Verifying current system...")
    746 
    747   device_specific.IncrementalOTA_VerifyBegin()
    748 
    749   # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
    750   # patching on a device that's already on the target build will damage the
    751   # system. Because operations like move don't check the block state, they
    752   # always apply the changes unconditionally.
    753   if blockimgdiff_version <= 2:
    754     if source_oem_props is None:
    755       script.AssertSomeFingerprint(source_fp)
    756     else:
    757       script.AssertSomeThumbprint(
    758           GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
    759 
    760   else: # blockimgdiff_version > 2
    761     if source_oem_props is None and target_oem_props is None:
    762       script.AssertSomeFingerprint(source_fp, target_fp)
    763     elif source_oem_props is not None and target_oem_props is not None:
    764       script.AssertSomeThumbprint(
    765           GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
    766           GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
    767     elif source_oem_props is None and target_oem_props is not None:
    768       script.AssertFingerprintOrThumbprint(
    769           source_fp,
    770           GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict))
    771     else:
    772       script.AssertFingerprintOrThumbprint(
    773           target_fp,
    774           GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
    775 
    776   # Check the required cache size (i.e. stashed blocks).
    777   size = []
    778   if system_diff:
    779     size.append(system_diff.required_cache)
    780   if vendor_diff:
    781     size.append(vendor_diff.required_cache)
    782 
    783   if updating_boot:
    784     boot_type, boot_device = common.GetTypeAndDevice(
    785         "/boot", OPTIONS.source_info_dict)
    786     d = common.Difference(target_boot, source_boot)
    787     _, _, d = d.ComputePatch()
    788     if d is None:
    789       include_full_boot = True
    790       common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    791     else:
    792       include_full_boot = False
    793 
    794       print("boot      target: %d  source: %d  diff: %d" % (
    795           target_boot.size, source_boot.size, len(d)))
    796 
    797       common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
    798 
    799       script.PatchCheck("%s:%s:%d:%s:%d:%s" %
    800                         (boot_type, boot_device,
    801                          source_boot.size, source_boot.sha1,
    802                          target_boot.size, target_boot.sha1))
    803       size.append(target_boot.size)
    804 
    805   if size:
    806     script.CacheFreeSpaceCheck(max(size))
    807 
    808   device_specific.IncrementalOTA_VerifyEnd()
    809 
    810   if OPTIONS.two_step:
    811     # Stage 1/3: (b) Write recovery image to /boot.
    812     _WriteRecoveryImageToBoot(script, output_zip)
    813 
    814     script.AppendExtra("""
    815 set_stage("%(bcb_dev)s", "2/3");
    816 reboot_now("%(bcb_dev)s", "");
    817 else
    818 """ % bcb_dev)
    819 
    820     # Stage 3/3: Make changes.
    821     script.Comment("Stage 3/3")
    822 
    823   # Verify the existing partitions.
    824   system_diff.WriteVerifyScript(script, touched_blocks_only=True)
    825   if vendor_diff:
    826     vendor_diff.WriteVerifyScript(script, touched_blocks_only=True)
    827 
    828   script.Comment("---- start making changes here ----")
    829 
    830   device_specific.IncrementalOTA_InstallBegin()
    831 
    832   system_diff.WriteScript(script, output_zip,
    833                           progress=0.8 if vendor_diff else 0.9)
    834 
    835   if vendor_diff:
    836     vendor_diff.WriteScript(script, output_zip, progress=0.1)
    837 
    838   if OPTIONS.two_step:
    839     common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    840     script.WriteRawImage("/boot", "boot.img")
    841     print("writing full boot image (forced by two-step mode)")
    842 
    843   if not OPTIONS.two_step:
    844     if updating_boot:
    845       if include_full_boot:
    846         print("boot image changed; including full.")
    847         script.Print("Installing boot image...")
    848         script.WriteRawImage("/boot", "boot.img")
    849       else:
    850         # Produce the boot image by applying a patch to the current
    851         # contents of the boot partition, and write it back to the
    852         # partition.
    853         print("boot image changed; including patch.")
    854         script.Print("Patching boot image...")
    855         script.ShowProgress(0.1, 10)
    856         script.ApplyPatch("%s:%s:%d:%s:%d:%s"
    857                           % (boot_type, boot_device,
    858                              source_boot.size, source_boot.sha1,
    859                              target_boot.size, target_boot.sha1),
    860                           "-",
    861                           target_boot.size, target_boot.sha1,
    862                           source_boot.sha1, "patch/boot.img.p")
    863     else:
    864       print("boot image unchanged; skipping.")
    865 
    866   # Do device-specific installation (eg, write radio image).
    867   device_specific.IncrementalOTA_InstallEnd()
    868 
    869   if OPTIONS.extra_script is not None:
    870     script.AppendExtra(OPTIONS.extra_script)
    871 
    872   if OPTIONS.wipe_user_data:
    873     script.Print("Erasing user data...")
    874     script.FormatPartition("/data")
    875     metadata["ota-wipe"] = "yes"
    876 
    877   if OPTIONS.two_step:
    878     script.AppendExtra("""
    879 set_stage("%(bcb_dev)s", "");
    880 endif;
    881 endif;
    882 """ % bcb_dev)
    883 
    884   script.SetProgress(1)
    885   # For downgrade OTAs, we prefer to use the update-binary in the source
    886   # build that is actually newer than the one in the target build.
    887   if OPTIONS.downgrade:
    888     script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
    889   else:
    890     script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
    891   metadata["ota-required-cache"] = str(script.required_cache)
    892   WriteMetadata(metadata, output_zip)
    893 
    894 
    895 def WriteVerifyPackage(input_zip, output_zip):
    896   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
    897 
    898   oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
    899   recovery_mount_options = OPTIONS.info_dict.get(
    900       "recovery_mount_options")
    901   oem_dicts = None
    902   if oem_props:
    903     oem_dicts = _LoadOemDicts(script, recovery_mount_options)
    904 
    905   target_fp = CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
    906                                    OPTIONS.info_dict)
    907   metadata = {
    908       "post-build": target_fp,
    909       "pre-device": GetOemProperty("ro.product.device", oem_props,
    910                                    oem_dicts and oem_dicts[0],
    911                                    OPTIONS.info_dict),
    912       "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
    913   }
    914 
    915   device_specific = common.DeviceSpecificParams(
    916       input_zip=input_zip,
    917       input_version=OPTIONS.info_dict["recovery_api_version"],
    918       output_zip=output_zip,
    919       script=script,
    920       input_tmp=OPTIONS.input_tmp,
    921       metadata=metadata,
    922       info_dict=OPTIONS.info_dict)
    923 
    924   AppendAssertions(script, OPTIONS.info_dict, oem_dicts)
    925 
    926   script.Print("Verifying device images against %s..." % target_fp)
    927   script.AppendExtra("")
    928 
    929   script.Print("Verifying boot...")
    930   boot_img = common.GetBootableImage(
    931       "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
    932   boot_type, boot_device = common.GetTypeAndDevice(
    933       "/boot", OPTIONS.info_dict)
    934   script.Verify("%s:%s:%d:%s" % (
    935       boot_type, boot_device, boot_img.size, boot_img.sha1))
    936   script.AppendExtra("")
    937 
    938   script.Print("Verifying recovery...")
    939   recovery_img = common.GetBootableImage(
    940       "recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
    941   recovery_type, recovery_device = common.GetTypeAndDevice(
    942       "/recovery", OPTIONS.info_dict)
    943   script.Verify("%s:%s:%d:%s" % (
    944       recovery_type, recovery_device, recovery_img.size, recovery_img.sha1))
    945   script.AppendExtra("")
    946 
    947   system_tgt = GetImage("system", OPTIONS.input_tmp)
    948   system_tgt.ResetFileMap()
    949   system_diff = common.BlockDifference("system", system_tgt, src=None)
    950   system_diff.WriteStrictVerifyScript(script)
    951 
    952   if HasVendorPartition(input_zip):
    953     vendor_tgt = GetImage("vendor", OPTIONS.input_tmp)
    954     vendor_tgt.ResetFileMap()
    955     vendor_diff = common.BlockDifference("vendor", vendor_tgt, src=None)
    956     vendor_diff.WriteStrictVerifyScript(script)
    957 
    958   # Device specific partitions, such as radio, bootloader and etc.
    959   device_specific.VerifyOTA_Assertions()
    960 
    961   script.SetProgress(1.0)
    962   script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
    963   metadata["ota-required-cache"] = str(script.required_cache)
    964   WriteMetadata(metadata, output_zip)
    965 
    966 
    967 def WriteABOTAPackageWithBrilloScript(target_file, output_file,
    968                                       source_file=None):
    969   """Generate an Android OTA package that has A/B update payload."""
    970 
    971   def ComputeStreamingMetadata(zip_file, reserve_space=False,
    972                                expected_length=None):
    973     """Compute the streaming metadata for a given zip.
    974 
    975     When 'reserve_space' is True, we reserve extra space for the offset and
    976     length of the metadata entry itself, although we don't know the final
    977     values until the package gets signed. This function will be called again
    978     after signing. We then write the actual values and pad the string to the
    979     length we set earlier. Note that we can't use the actual length of the
    980     metadata entry in the second run. Otherwise the offsets for other entries
    981     will be changing again.
    982     """
    983 
    984     def ComputeEntryOffsetSize(name):
    985       """Compute the zip entry offset and size."""
    986       info = zip_file.getinfo(name)
    987       offset = info.header_offset + len(info.FileHeader())
    988       size = info.file_size
    989       return '%s:%d:%d' % (os.path.basename(name), offset, size)
    990 
    991     # payload.bin and payload_properties.txt must exist.
    992     offsets = [ComputeEntryOffsetSize('payload.bin'),
    993                ComputeEntryOffsetSize('payload_properties.txt')]
    994 
    995     # care_map.txt is available only if dm-verity is enabled.
    996     if 'care_map.txt' in zip_file.namelist():
    997       offsets.append(ComputeEntryOffsetSize('care_map.txt'))
    998 
    999     if 'compatibility.zip' in zip_file.namelist():
   1000       offsets.append(ComputeEntryOffsetSize('compatibility.zip'))
   1001 
   1002     # 'META-INF/com/android/metadata' is required. We don't know its actual
   1003     # offset and length (as well as the values for other entries). So we
   1004     # reserve 10-byte as a placeholder, which is to cover the space for metadata
   1005     # entry ('xx:xxx', since it's ZIP_STORED which should appear at the
   1006     # beginning of the zip), as well as the possible value changes in other
   1007     # entries.
   1008     if reserve_space:
   1009       offsets.append('metadata:' + ' ' * 10)
   1010     else:
   1011       offsets.append(ComputeEntryOffsetSize(METADATA_NAME))
   1012 
   1013     value = ','.join(offsets)
   1014     if expected_length is not None:
   1015       assert len(value) <= expected_length, \
   1016           'Insufficient reserved space: reserved=%d, actual=%d' % (
   1017               expected_length, len(value))
   1018       value += ' ' * (expected_length - len(value))
   1019     return value
   1020 
   1021   # The place where the output from the subprocess should go.
   1022   log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
   1023 
   1024   # A/B updater expects a signing key in RSA format. Gets the key ready for
   1025   # later use in step 3, unless a payload_signer has been specified.
   1026   if OPTIONS.payload_signer is None:
   1027     cmd = ["openssl", "pkcs8",
   1028            "-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
   1029            "-inform", "DER"]
   1030     pw = OPTIONS.key_passwords[OPTIONS.package_key]
   1031     cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
   1032     rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
   1033     cmd.extend(["-out", rsa_key])
   1034     p1 = common.Run(cmd, verbose=False, stdout=log_file, stderr=subprocess.STDOUT)
   1035     p1.communicate()
   1036     assert p1.returncode == 0, "openssl pkcs8 failed"
   1037 
   1038   # Stage the output zip package for package signing.
   1039   temp_zip_file = tempfile.NamedTemporaryFile()
   1040   output_zip = zipfile.ZipFile(temp_zip_file, "w",
   1041                                compression=zipfile.ZIP_DEFLATED)
   1042 
   1043   # Metadata to comply with Android OTA package format.
   1044   oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
   1045   oem_dicts = None
   1046   if oem_props:
   1047     oem_dicts = _LoadOemDicts(None)
   1048 
   1049   metadata = {
   1050       "post-build": CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
   1051                                          OPTIONS.info_dict),
   1052       "post-build-incremental" : GetBuildProp("ro.build.version.incremental",
   1053                                               OPTIONS.info_dict),
   1054       "pre-device": GetOemProperty("ro.product.device", oem_props,
   1055                                    oem_dicts and oem_dicts[0],
   1056                                    OPTIONS.info_dict),
   1057       "ota-required-cache": "0",
   1058       "ota-type": "AB",
   1059   }
   1060 
   1061   if source_file is not None:
   1062     metadata["pre-build"] = CalculateFingerprint(oem_props,
   1063                                                  oem_dicts and oem_dicts[0],
   1064                                                  OPTIONS.source_info_dict)
   1065     metadata["pre-build-incremental"] = GetBuildProp(
   1066         "ro.build.version.incremental", OPTIONS.source_info_dict)
   1067 
   1068     HandleDowngradeMetadata(metadata)
   1069   else:
   1070     metadata["post-timestamp"] = GetBuildProp(
   1071         "ro.build.date.utc", OPTIONS.info_dict)
   1072 
   1073   # 1. Generate payload.
   1074   payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
   1075   cmd = ["brillo_update_payload", "generate",
   1076          "--payload", payload_file,
   1077          "--target_image", target_file]
   1078   if source_file is not None:
   1079     cmd.extend(["--source_image", source_file])
   1080   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1081   p1.communicate()
   1082   assert p1.returncode == 0, "brillo_update_payload generate failed"
   1083 
   1084   # 2. Generate hashes of the payload and metadata files.
   1085   payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
   1086   metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
   1087   cmd = ["brillo_update_payload", "hash",
   1088          "--unsigned_payload", payload_file,
   1089          "--signature_size", "256",
   1090          "--metadata_hash_file", metadata_sig_file,
   1091          "--payload_hash_file", payload_sig_file]
   1092   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1093   p1.communicate()
   1094   assert p1.returncode == 0, "brillo_update_payload hash failed"
   1095 
   1096   # 3. Sign the hashes and insert them back into the payload file.
   1097   signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-",
   1098                                                 suffix=".bin")
   1099   signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-",
   1100                                                  suffix=".bin")
   1101   # 3a. Sign the payload hash.
   1102   if OPTIONS.payload_signer is not None:
   1103     cmd = [OPTIONS.payload_signer]
   1104     cmd.extend(OPTIONS.payload_signer_args)
   1105   else:
   1106     cmd = ["openssl", "pkeyutl", "-sign",
   1107            "-inkey", rsa_key,
   1108            "-pkeyopt", "digest:sha256"]
   1109   cmd.extend(["-in", payload_sig_file,
   1110               "-out", signed_payload_sig_file])
   1111   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1112   p1.communicate()
   1113   assert p1.returncode == 0, "openssl sign payload failed"
   1114 
   1115   # 3b. Sign the metadata hash.
   1116   if OPTIONS.payload_signer is not None:
   1117     cmd = [OPTIONS.payload_signer]
   1118     cmd.extend(OPTIONS.payload_signer_args)
   1119   else:
   1120     cmd = ["openssl", "pkeyutl", "-sign",
   1121            "-inkey", rsa_key,
   1122            "-pkeyopt", "digest:sha256"]
   1123   cmd.extend(["-in", metadata_sig_file,
   1124               "-out", signed_metadata_sig_file])
   1125   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1126   p1.communicate()
   1127   assert p1.returncode == 0, "openssl sign metadata failed"
   1128 
   1129   # 3c. Insert the signatures back into the payload file.
   1130   signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
   1131                                             suffix=".bin")
   1132   cmd = ["brillo_update_payload", "sign",
   1133          "--unsigned_payload", payload_file,
   1134          "--payload", signed_payload_file,
   1135          "--signature_size", "256",
   1136          "--metadata_signature_file", signed_metadata_sig_file,
   1137          "--payload_signature_file", signed_payload_sig_file]
   1138   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1139   p1.communicate()
   1140   assert p1.returncode == 0, "brillo_update_payload sign failed"
   1141 
   1142   # 4. Dump the signed payload properties.
   1143   properties_file = common.MakeTempFile(prefix="payload-properties-",
   1144                                         suffix=".txt")
   1145   cmd = ["brillo_update_payload", "properties",
   1146          "--payload", signed_payload_file,
   1147          "--properties_file", properties_file]
   1148   p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
   1149   p1.communicate()
   1150   assert p1.returncode == 0, "brillo_update_payload properties failed"
   1151 
   1152   if OPTIONS.wipe_user_data:
   1153     with open(properties_file, "a") as f:
   1154       f.write("POWERWASH=1\n")
   1155     metadata["ota-wipe"] = "yes"
   1156 
   1157   # Add the signed payload file and properties into the zip. In order to
   1158   # support streaming, we pack payload.bin, payload_properties.txt and
   1159   # care_map.txt as ZIP_STORED. So these entries can be read directly with
   1160   # the offset and length pairs.
   1161   common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
   1162                   compress_type=zipfile.ZIP_STORED)
   1163   common.ZipWrite(output_zip, properties_file,
   1164                   arcname="payload_properties.txt",
   1165                   compress_type=zipfile.ZIP_STORED)
   1166 
   1167   # If dm-verity is supported for the device, copy contents of care_map
   1168   # into A/B OTA package.
   1169   target_zip = zipfile.ZipFile(target_file, "r")
   1170   if (OPTIONS.info_dict.get("verity") == "true" or
   1171       OPTIONS.info_dict.get("avb_enable") == "true"):
   1172     care_map_path = "META/care_map.txt"
   1173     namelist = target_zip.namelist()
   1174     if care_map_path in namelist:
   1175       care_map_data = target_zip.read(care_map_path)
   1176       common.ZipWriteStr(output_zip, "care_map.txt", care_map_data,
   1177           compress_type=zipfile.ZIP_STORED)
   1178     else:
   1179       print("Warning: cannot find care map file in target_file package")
   1180 
   1181   if HasVendorPartition(target_zip):
   1182     update_vendor = True
   1183     update_system = True
   1184 
   1185     # If incremental then figure out what is being updated so metadata only for
   1186     # the updated image is included.
   1187     if source_file is not None:
   1188       input_tmp, input_zip = common.UnzipTemp(
   1189           target_file, UNZIP_PATTERN)
   1190       source_tmp, source_zip = common.UnzipTemp(
   1191           source_file, UNZIP_PATTERN)
   1192 
   1193       vendor_src = GetImage("vendor", source_tmp)
   1194       vendor_tgt = GetImage("vendor", input_tmp)
   1195       system_src = GetImage("system", source_tmp)
   1196       system_tgt = GetImage("system", input_tmp)
   1197 
   1198       update_system = system_src.TotalSha1() != system_tgt.TotalSha1()
   1199       update_vendor = vendor_src.TotalSha1() != vendor_tgt.TotalSha1()
   1200 
   1201       input_zip.close()
   1202       source_zip.close()
   1203 
   1204     target_zip = zipfile.ZipFile(target_file, "r")
   1205     AddCompatibilityArchive(target_zip, output_zip, update_system,
   1206                             update_vendor)
   1207   common.ZipClose(target_zip)
   1208 
   1209   # Write the current metadata entry with placeholders.
   1210   metadata['ota-streaming-property-files'] = ComputeStreamingMetadata(
   1211       output_zip, reserve_space=True)
   1212   WriteMetadata(metadata, output_zip)
   1213   common.ZipClose(output_zip)
   1214 
   1215   # SignOutput(), which in turn calls signapk.jar, will possibly reorder the
   1216   # zip entries, as well as padding the entry headers. We do a preliminary
   1217   # signing (with an incomplete metadata entry) to allow that to happen. Then
   1218   # compute the zip entry offsets, write back the final metadata and do the
   1219   # final signing.
   1220   prelim_signing = tempfile.NamedTemporaryFile()
   1221   SignOutput(temp_zip_file.name, prelim_signing.name)
   1222   common.ZipClose(temp_zip_file)
   1223 
   1224   # Open the signed zip. Compute the final metadata that's needed for streaming.
   1225   prelim_zip = zipfile.ZipFile(prelim_signing, "r",
   1226                                compression=zipfile.ZIP_DEFLATED)
   1227   expected_length = len(metadata['ota-streaming-property-files'])
   1228   metadata['ota-streaming-property-files'] = ComputeStreamingMetadata(
   1229       prelim_zip, reserve_space=False, expected_length=expected_length)
   1230 
   1231   # Copy the zip entries, as we cannot update / delete entries with zipfile.
   1232   final_signing = tempfile.NamedTemporaryFile()
   1233   output_zip = zipfile.ZipFile(final_signing, "w",
   1234                                compression=zipfile.ZIP_DEFLATED)
   1235   for item in prelim_zip.infolist():
   1236     if item.filename == METADATA_NAME:
   1237       continue
   1238 
   1239     data = prelim_zip.read(item.filename)
   1240     out_info = copy.copy(item)
   1241     common.ZipWriteStr(output_zip, out_info, data)
   1242 
   1243   # Now write the final metadata entry.
   1244   WriteMetadata(metadata, output_zip)
   1245   common.ZipClose(prelim_zip)
   1246   common.ZipClose(output_zip)
   1247 
   1248   # Re-sign the package after updating the metadata entry.
   1249   SignOutput(final_signing.name, output_file)
   1250   final_signing.close()
   1251 
   1252   # Reopen the final signed zip to double check the streaming metadata.
   1253   output_zip = zipfile.ZipFile(output_file, "r")
   1254   actual = metadata['ota-streaming-property-files'].strip()
   1255   expected = ComputeStreamingMetadata(output_zip)
   1256   assert actual == expected, \
   1257       "Mismatching streaming metadata: %s vs %s." % (actual, expected)
   1258   common.ZipClose(output_zip)
   1259 
   1260 
   1261 def main(argv):
   1262 
   1263   def option_handler(o, a):
   1264     if o == "--board_config":
   1265       pass   # deprecated
   1266     elif o in ("-k", "--package_key"):
   1267       OPTIONS.package_key = a
   1268     elif o in ("-i", "--incremental_from"):
   1269       OPTIONS.incremental_source = a
   1270     elif o == "--full_radio":
   1271       OPTIONS.full_radio = True
   1272     elif o == "--full_bootloader":
   1273       OPTIONS.full_bootloader = True
   1274     elif o in ("-w", "--wipe_user_data"):
   1275       OPTIONS.wipe_user_data = True
   1276     elif o == "--downgrade":
   1277       OPTIONS.downgrade = True
   1278       OPTIONS.wipe_user_data = True
   1279     elif o == "--override_timestamp":
   1280       OPTIONS.timestamp = True
   1281     elif o in ("-o", "--oem_settings"):
   1282       OPTIONS.oem_source = a.split(',')
   1283     elif o == "--oem_no_mount":
   1284       OPTIONS.oem_no_mount = True
   1285     elif o in ("-e", "--extra_script"):
   1286       OPTIONS.extra_script = a
   1287     elif o in ("-t", "--worker_threads"):
   1288       if a.isdigit():
   1289         OPTIONS.worker_threads = int(a)
   1290       else:
   1291         raise ValueError("Cannot parse value %r for option %r - only "
   1292                          "integers are allowed." % (a, o))
   1293     elif o in ("-2", "--two_step"):
   1294       OPTIONS.two_step = True
   1295     elif o == "--no_signing":
   1296       OPTIONS.no_signing = True
   1297     elif o == "--verify":
   1298       OPTIONS.verify = True
   1299     elif o == "--block":
   1300       OPTIONS.block_based = True
   1301     elif o in ("-b", "--binary"):
   1302       OPTIONS.updater_binary = a
   1303     elif o in ("--no_fallback_to_full",):
   1304       OPTIONS.fallback_to_full = False
   1305     elif o == "--stash_threshold":
   1306       try:
   1307         OPTIONS.stash_threshold = float(a)
   1308       except ValueError:
   1309         raise ValueError("Cannot parse value %r for option %r - expecting "
   1310                          "a float" % (a, o))
   1311     elif o == "--gen_verify":
   1312       OPTIONS.gen_verify = True
   1313     elif o == "--log_diff":
   1314       OPTIONS.log_diff = a
   1315     elif o == "--payload_signer":
   1316       OPTIONS.payload_signer = a
   1317     elif o == "--payload_signer_args":
   1318       OPTIONS.payload_signer_args = shlex.split(a)
   1319     elif o == "--extracted_input_target_files":
   1320       OPTIONS.extracted_input = a
   1321     else:
   1322       return False
   1323     return True
   1324 
   1325   args = common.ParseOptions(argv, __doc__,
   1326                              extra_opts="b:k:i:d:we:t:2o:",
   1327                              extra_long_opts=[
   1328                                  "board_config=",
   1329                                  "package_key=",
   1330                                  "incremental_from=",
   1331                                  "full_radio",
   1332                                  "full_bootloader",
   1333                                  "wipe_user_data",
   1334                                  "downgrade",
   1335                                  "override_timestamp",
   1336                                  "extra_script=",
   1337                                  "worker_threads=",
   1338                                  "two_step",
   1339                                  "no_signing",
   1340                                  "block",
   1341                                  "binary=",
   1342                                  "oem_settings=",
   1343                                  "oem_no_mount",
   1344                                  "verify",
   1345                                  "no_fallback_to_full",
   1346                                  "stash_threshold=",
   1347                                  "gen_verify",
   1348                                  "log_diff=",
   1349                                  "payload_signer=",
   1350                                  "payload_signer_args=",
   1351                                  "extracted_input_target_files=",
   1352                              ], extra_option_handler=option_handler)
   1353 
   1354   if len(args) != 2:
   1355     common.Usage(__doc__)
   1356     sys.exit(1)
   1357 
   1358   if OPTIONS.downgrade:
   1359     # Sanity check to enforce a data wipe.
   1360     if not OPTIONS.wipe_user_data:
   1361       raise ValueError("Cannot downgrade without a data wipe")
   1362 
   1363     # We should only allow downgrading incrementals (as opposed to full).
   1364     # Otherwise the device may go back from arbitrary build with this full
   1365     # OTA package.
   1366     if OPTIONS.incremental_source is None:
   1367       raise ValueError("Cannot generate downgradable full OTAs")
   1368 
   1369   assert not (OPTIONS.downgrade and OPTIONS.timestamp), \
   1370       "Cannot have --downgrade AND --override_timestamp both"
   1371 
   1372   # Load the dict file from the zip directly to have a peek at the OTA type.
   1373   # For packages using A/B update, unzipping is not needed.
   1374   if OPTIONS.extracted_input is not None:
   1375     OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input, OPTIONS.extracted_input)
   1376   else:
   1377     input_zip = zipfile.ZipFile(args[0], "r")
   1378     OPTIONS.info_dict = common.LoadInfoDict(input_zip)
   1379     common.ZipClose(input_zip)
   1380 
   1381   ab_update = OPTIONS.info_dict.get("ab_update") == "true"
   1382 
   1383   # Use the default key to sign the package if not specified with package_key.
   1384   # package_keys are needed on ab_updates, so always define them if an
   1385   # ab_update is getting created.
   1386   if not OPTIONS.no_signing or ab_update:
   1387     if OPTIONS.package_key is None:
   1388       OPTIONS.package_key = OPTIONS.info_dict.get(
   1389           "default_system_dev_certificate",
   1390           "build/target/product/security/testkey")
   1391     # Get signing keys
   1392     OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
   1393 
   1394   if ab_update:
   1395     if OPTIONS.incremental_source is not None:
   1396       OPTIONS.target_info_dict = OPTIONS.info_dict
   1397       source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
   1398       OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
   1399       common.ZipClose(source_zip)
   1400 
   1401     if OPTIONS.verbose:
   1402       print("--- target info ---")
   1403       common.DumpInfoDict(OPTIONS.info_dict)
   1404 
   1405       if OPTIONS.incremental_source is not None:
   1406         print("--- source info ---")
   1407         common.DumpInfoDict(OPTIONS.source_info_dict)
   1408 
   1409     WriteABOTAPackageWithBrilloScript(
   1410         target_file=args[0],
   1411         output_file=args[1],
   1412         source_file=OPTIONS.incremental_source)
   1413 
   1414     print("done.")
   1415     return
   1416 
   1417   if OPTIONS.extra_script is not None:
   1418     OPTIONS.extra_script = open(OPTIONS.extra_script).read()
   1419 
   1420   if OPTIONS.extracted_input is not None:
   1421     OPTIONS.input_tmp = OPTIONS.extracted_input
   1422     OPTIONS.target_tmp = OPTIONS.input_tmp
   1423     OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, OPTIONS.input_tmp)
   1424     input_zip = zipfile.ZipFile(args[0], "r")
   1425   else:
   1426     print("unzipping target target-files...")
   1427     OPTIONS.input_tmp, input_zip = common.UnzipTemp(
   1428         args[0], UNZIP_PATTERN)
   1429 
   1430     OPTIONS.target_tmp = OPTIONS.input_tmp
   1431     OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
   1432 
   1433   if OPTIONS.verbose:
   1434     print("--- target info ---")
   1435     common.DumpInfoDict(OPTIONS.info_dict)
   1436 
   1437   # If the caller explicitly specified the device-specific extensions
   1438   # path via -s/--device_specific, use that.  Otherwise, use
   1439   # META/releasetools.py if it is present in the target target_files.
   1440   # Otherwise, take the path of the file from 'tool_extensions' in the
   1441   # info dict and look for that in the local filesystem, relative to
   1442   # the current directory.
   1443 
   1444   if OPTIONS.device_specific is None:
   1445     from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
   1446     if os.path.exists(from_input):
   1447       print("(using device-specific extensions from target_files)")
   1448       OPTIONS.device_specific = from_input
   1449     else:
   1450       OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
   1451 
   1452   if OPTIONS.device_specific is not None:
   1453     OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
   1454 
   1455   if OPTIONS.info_dict.get("no_recovery") == "true":
   1456     raise common.ExternalError(
   1457         "--- target build has specified no recovery ---")
   1458 
   1459   # Set up the output zip. Create a temporary zip file if signing is needed.
   1460   if OPTIONS.no_signing:
   1461     if os.path.exists(args[1]):
   1462       os.unlink(args[1])
   1463     output_zip = zipfile.ZipFile(args[1], "w",
   1464                                  compression=zipfile.ZIP_DEFLATED)
   1465   else:
   1466     temp_zip_file = tempfile.NamedTemporaryFile()
   1467     output_zip = zipfile.ZipFile(temp_zip_file, "w",
   1468                                  compression=zipfile.ZIP_DEFLATED)
   1469 
   1470   # Non A/B OTAs rely on /cache partition to store temporary files.
   1471   cache_size = OPTIONS.info_dict.get("cache_size", None)
   1472   if cache_size is None:
   1473     print("--- can't determine the cache partition size ---")
   1474   OPTIONS.cache_size = cache_size
   1475 
   1476   # Generate a verify package.
   1477   if OPTIONS.gen_verify:
   1478     WriteVerifyPackage(input_zip, output_zip)
   1479 
   1480   # Generate a full OTA.
   1481   elif OPTIONS.incremental_source is None:
   1482     WriteFullOTAPackage(input_zip, output_zip)
   1483 
   1484   # Generate an incremental OTA. It will fall back to generate a full OTA on
   1485   # failure unless no_fallback_to_full is specified.
   1486   else:
   1487     print("unzipping source target-files...")
   1488     OPTIONS.source_tmp, source_zip = common.UnzipTemp(
   1489         OPTIONS.incremental_source,
   1490         UNZIP_PATTERN)
   1491     OPTIONS.target_info_dict = OPTIONS.info_dict
   1492     OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
   1493                                                    OPTIONS.source_tmp)
   1494     if OPTIONS.verbose:
   1495       print("--- source info ---")
   1496       common.DumpInfoDict(OPTIONS.source_info_dict)
   1497     try:
   1498       WriteBlockIncrementalOTAPackage(input_zip, source_zip, output_zip)
   1499       if OPTIONS.log_diff:
   1500         out_file = open(OPTIONS.log_diff, 'w')
   1501         import target_files_diff
   1502         target_files_diff.recursiveDiff('',
   1503                                         OPTIONS.source_tmp,
   1504                                         OPTIONS.input_tmp,
   1505                                         out_file)
   1506         out_file.close()
   1507     except ValueError:
   1508       if not OPTIONS.fallback_to_full:
   1509         raise
   1510       print("--- failed to build incremental; falling back to full ---")
   1511       OPTIONS.incremental_source = None
   1512       WriteFullOTAPackage(input_zip, output_zip)
   1513 
   1514   common.ZipClose(output_zip)
   1515 
   1516   # Sign the generated zip package unless no_signing is specified.
   1517   if not OPTIONS.no_signing:
   1518     SignOutput(temp_zip_file.name, args[1])
   1519     temp_zip_file.close()
   1520 
   1521   print("done.")
   1522 
   1523 
   1524 if __name__ == '__main__':
   1525   try:
   1526     common.CloseInheritedPipes()
   1527     main(sys.argv[1:])
   1528   except common.ExternalError as e:
   1529     print("\n   ERROR: %s\n" % (e,))
   1530     sys.exit(1)
   1531   finally:
   1532     common.Cleanup()
   1533