# Copyright (C) 2020 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
import collections import logging import os import zipfile
import common import edify_generator import verity_utils from check_target_files_vintf import CheckVintfIfTrebleEnabled, HasPartition from common import OPTIONS from ota_utils import UNZIP_PATTERN, FinalizeMetadata, GetPackageMetadata, PropertyFiles import subprocess
logger = logging.getLogger(__name__)
def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
device_specific): """Returns a ordered dict of block differences with partition name as key."""
# Check the first block of the source system partition for remount R/W only # if the filesystem is ext4.
partition_source_info = source_info["fstab"]["/" + name]
check_first_block = partition_source_info.fs_type == "ext4" # Disable imgdiff because it relies on zlib to produce stable output # across different versions, which is often not the case. return common.BlockDifference(name, partition_tgt, partition_src,
check_first_block,
version=blockimgdiff_version,
disable_imgdiff=True)
if source_zip: # See notes in common.GetUserImage()
allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true"or
target_info.get('ext4_share_dup_blocks') == "true")
blockimgdiff_version = max(
int(i) for i in target_info.get( "blockimgdiff_versions", "1").split(",")) assert blockimgdiff_version >= 3
block_diff_dict = collections.OrderedDict()
partition_names = ["system", "vendor", "product", "odm", "system_ext", "vendor_dlkm", "odm_dlkm", "system_dlkm"] for partition in partition_names: ifnot HasPartition(target_zip, partition): continue # Full OTA update. ifnot source_zip:
tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
info_dict=target_info,
reset_file_map=True)
block_diff_dict[partition] = common.BlockDifference(partition, tgt,
src=None) # Incremental OTA update. else:
block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
partition) assert"system"in block_diff_dict
# Get the block diffs from the device specific script. If there is a # duplicate block diff for a partition, ignore the diff in the generic script # and use the one in the device specific script instead. if source_zip:
device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
function_name = "IncrementalOTA_GetBlockDifferences" else:
device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
function_name = "FullOTA_GetBlockDifferences"
if device_specific_diffs: assert all(isinstance(diff, common.BlockDifference) for diff in device_specific_diffs), \ "{} is not returning a list of BlockDifference objects".format(
function_name) for diff in device_specific_diffs: if diff.partition in block_diff_dict:
logger.warning("Duplicate block difference found. Device specific block" " diff for partition '%s' overrides the one in generic" " script.", diff.partition)
block_diff_dict[diff.partition] = diff
# We don't know what version it will be installed on top of. We expect the API # just won't change very often. Similarly for fstab, it might have changed in # the target build.
target_api_version = target_info["recovery_api_version"]
script = edify_generator.EdifyGenerator(target_api_version, target_info)
if target_info.oem_props andnot OPTIONS.oem_no_mount:
target_info.WriteMountOemScript(script)
# Two-step package strategy (in chronological order, which is *not* # the order in which the generated script has things): # # if stage is not "2/3" or "3/3": # write recovery image to boot partition # set stage to "2/3" # reboot to boot partition and restart recovery # else if stage is "2/3": # write recovery image to recovery partition # set stage to "3/3" # reboot to recovery partition and restart recovery # else: # (stage must be "3/3") # set stage to "" # do normal full package installation: # wipe and install system, boot image, etc. # set up system to update recovery partition on first boot # complete script normally # (allow recovery to mark itself finished and reboot)
recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
OPTIONS.input_tmp, "RECOVERY") if OPTIONS.two_step: ifnot target_info.get("multistage_support"): assertFalse, "two-step packages not supported by this build"
fs = target_info["fstab"]["/misc"] assert fs.fs_type.upper() == "EMMC", \ "two-step packages only supported on devices with EMMC /misc partitions"
bcb_dev = {"bcb_dev": fs.device}
common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
script.AppendExtra(""" if get_stage("%(bcb_dev)s") == "2/3" then """ % bcb_dev)
# All other partitions as well as the data wipe use 10% of the progress, and # the update of the system partition takes the remaining progress.
system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1 if OPTIONS.wipe_user_data:
system_progress -= 0.1
progress_dict = {partition: 0.1for partition in block_diff_dict}
progress_dict["system"] = system_progress
if target_info.get('use_dynamic_partitions') == "true": # Use empty source_info_dict to indicate that all partitions / groups must # be re-added.
dynamic_partitions_diff = common.DynamicPartitionsDifference(
info_dict=OPTIONS.info_dict,
block_diffs=block_diff_dict.values(),
progress_dict=progress_dict)
dynamic_partitions_diff.WriteScript(script, output_zip,
write_verify_script=OPTIONS.verify) else: for block_diff in block_diff_dict.values():
block_diff.WriteScript(script, output_zip,
progress=progress_dict.get(block_diff.partition),
write_verify_script=OPTIONS.verify)
# Two-step incremental package strategy (in chronological order, # which is *not* the order in which the generated script has # things): # # if stage is not "2/3" or "3/3": # do verification on current system # write recovery image to boot partition # set stage to "2/3" # reboot to boot partition and restart recovery # else if stage is "2/3": # write recovery image to recovery partition # set stage to "3/3" # reboot to recovery partition and restart recovery # else: # (stage must be "3/3") # perform update: # patch system files, etc. # force full install of new boot image # set up system to update recovery partition on first boot # complete script normally # (allow recovery to mark itself finished and reboot)
if OPTIONS.two_step: ifnot source_info.get("multistage_support"): assertFalse, "two-step packages not supported by this build"
fs = source_info["fstab"]["/misc"] assert fs.fs_type.upper() == "EMMC", \ "two-step packages only supported on devices with EMMC /misc partitions"
bcb_dev = {"bcb_dev": fs.device}
common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
script.AppendExtra(""" if get_stage("%(bcb_dev)s") == "2/3" then """ % bcb_dev)
if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true": if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true": raise RuntimeError( "can't generate incremental that disables dynamic partitions")
dynamic_partitions_diff = common.DynamicPartitionsDifference(
info_dict=OPTIONS.target_info_dict,
source_info_dict=OPTIONS.source_info_dict,
block_diffs=block_diff_dict.values(),
progress_dict=progress_dict)
dynamic_partitions_diff.WriteScript(
script, output_zip, write_verify_script=OPTIONS.verify) else: for block_diff in block_diff_dict.values():
block_diff.WriteScript(script, output_zip,
progress=progress_dict.get(block_diff.partition),
write_verify_script=OPTIONS.verify)
if OPTIONS.two_step:
common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
script.WriteRawImage("/boot", "boot.img")
logger.info("writing full boot image (forced by two-step mode)")
ifnot OPTIONS.two_step: if updating_boot: if include_full_boot:
logger.info("boot image changed; including full.")
script.Print("Installing boot image...")
script.WriteRawImage("/boot", "boot.img") else: # Produce the boot image by applying a patch to the current # contents of the boot partition, and write it back to the # partition.
logger.info("boot image changed; including patch.")
script.Print("Patching boot image...")
script.ShowProgress(0.1, 10)
target_expr = 'concat("{}:",{},":{}:{}")'.format(
boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
source_expr = 'concat("{}:",{},":{}:{}")'.format(
boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
script.PatchPartitionExpr(target_expr, source_expr, '"boot.img.p"') else:
logger.info("boot image unchanged; skipping.")
# Do device-specific installation (eg, write radio image).
device_specific.IncrementalOTA_InstallEnd()
if OPTIONS.extra_script isnotNone:
script.AppendExtra(OPTIONS.extra_script)
if OPTIONS.wipe_user_data:
script.Print("Erasing user data...")
script.FormatPartition("/data")
if OPTIONS.two_step:
script.AppendExtra("""
set_stage("%(bcb_dev)s", "");
endif;
endif; """ % bcb_dev)
script.SetProgress(1) # For downgrade OTAs, we prefer to use the update-binary in the source # build that is actually newer than the one in the target build. if OPTIONS.downgrade:
script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary) else:
script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
metadata.required_cache = script.required_cache
# We haven't written the metadata entry yet, which will be handled in # FinalizeMetadata().
common.ZipClose(output_zip)
# Sign the generated zip package unless no_signing is specified.
needed_property_files = (
NonAbOtaPropertyFiles(),
)
FinalizeMetadata(metadata, staging_file, output_file,
needed_property_files, package_key=OPTIONS.package_key)
def GenerateNonAbOtaPackage(target_file, output_file, source_file=None): """Generates a non-A/B OTA package.""" # Check the loaded info dicts first. if OPTIONS.info_dict.get("no_recovery") == "true": raise common.ExternalError( "--- target build has specified no recovery ---")
# Non-A/B OTAs rely on /cache partition to store temporary files.
cache_size = OPTIONS.info_dict.get("cache_size") if cache_size isNone:
logger.warning("--- can't determine the cache partition size ---")
OPTIONS.cache_size = cache_size
if OPTIONS.extra_script isnotNone: with open(OPTIONS.extra_script) as fp:
OPTIONS.extra_script = fp.read()
# If the caller explicitly specified the device-specific extensions path via # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it # is present in the target target_files. Otherwise, take the path of the file # from 'tool_extensions' in the info dict and look for that in the local # filesystem, relative to the current directory. if OPTIONS.device_specific isNone:
from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") if os.path.exists(from_input):
logger.info("(using device-specific extensions from target_files)")
OPTIONS.device_specific = from_input else:
OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
if OPTIONS.device_specific isnotNone:
OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
# Generate a full OTA. if source_file isNone: with zipfile.ZipFile(target_file) as input_zip:
WriteFullOTAPackage(
input_zip,
output_file)
# Generate an incremental OTA. else:
logger.info("unzipping source target-files...")
OPTIONS.source_tmp = common.UnzipTemp(
OPTIONS.incremental_source, UNZIP_PATTERN) with zipfile.ZipFile(target_file) as input_zip, \
zipfile.ZipFile(source_file) as source_zip:
WriteBlockIncrementalOTAPackage(
input_zip,
source_zip,
output_file)
class NonAbOtaPropertyFiles(PropertyFiles): """The property-files for non-A/B OTA.
For non-A/B OTA, the property-files string contains the info for METADATA
entry, with which a system updater can be fetched the package metadata prior
to downloading the entire package. """
def _WriteRecoveryImageToBoot(script, output_zip): """Find and write recovery image to /boot in two-step OTA.
In two-step OTAs, we write recovery image to /boot as the first step so that
we can reboot to there and install a new recovery image to /recovery.
A special "recovery-two-step.img" will be preferred, which encodes the correct
path of "/boot". Otherwise the device may show "device is corrupt" message
when booting into /boot.
Fall back to using the regular recovery.img if the two-step recovery image
doesn't exist. Note that rebuilding the special image at this point may be
infeasible, because we don't have the desired boot signer and keys when
calling ota_from_target_files.py. """
recovery_two_step_img_name = "recovery-two-step.img"
recovery_two_step_img_path = os.path.join(
OPTIONS.input_tmp, "OTA", recovery_two_step_img_name) if os.path.exists(recovery_two_step_img_path):
common.ZipWrite(
output_zip,
recovery_two_step_img_path,
arcname=recovery_two_step_img_name)
logger.info( "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
script.WriteRawImage("/boot", recovery_two_step_img_name) else:
logger.info("two-step package: using recovery.img in stage 1/3") # The "recovery.img" entry has been written into package earlier.
script.WriteRawImage("/boot", "recovery.img")
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.