releasetools: Support generating verify packages.
We can generate a special OTA package that verifies all the partitions (boot, recovery, system, vendor and etc) on a device. It also calls device-specific script to verify bootloader and radio images. This ensures a flashed device contains all the desired images faithfully. Usage: ota_from_target_files.py --gen_verify target_files.zip output.zip Bug: 24679956 Change-Id: Ib3091d98c4b17a6fad305b3edf16d09efbda5c38
This commit is contained in:
@@ -1085,6 +1085,9 @@ class DeviceSpecificParams(object):
|
|||||||
processor."""
|
processor."""
|
||||||
return self._DoCall("IncrementalOTA_InstallEnd")
|
return self._DoCall("IncrementalOTA_InstallEnd")
|
||||||
|
|
||||||
|
def VerifyOTA_Assertions(self):
|
||||||
|
return self._DoCall("VerifyOTA_Assertions")
|
||||||
|
|
||||||
class File(object):
|
class File(object):
|
||||||
def __init__(self, name, data):
|
def __init__(self, name, data):
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -1279,6 +1282,25 @@ class BlockDifference(object):
|
|||||||
self._WriteUpdate(script, output_zip)
|
self._WriteUpdate(script, output_zip)
|
||||||
self._WritePostInstallVerifyScript(script)
|
self._WritePostInstallVerifyScript(script)
|
||||||
|
|
||||||
|
def WriteStrictVerifyScript(self, script):
|
||||||
|
"""Verify all the blocks in the care_map, including clobbered blocks.
|
||||||
|
|
||||||
|
This differs from the WriteVerifyScript() function: a) it prints different
|
||||||
|
error messages; b) it doesn't allow half-way updated images to pass the
|
||||||
|
verification."""
|
||||||
|
|
||||||
|
partition = self.partition
|
||||||
|
script.Print("Verifying %s..." % (partition,))
|
||||||
|
ranges = self.tgt.care_map
|
||||||
|
ranges_str = ranges.to_string_raw()
|
||||||
|
script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
|
||||||
|
'ui_print(" Verified.") || '
|
||||||
|
'ui_print("\\"%s\\" has unexpected contents.");' % (
|
||||||
|
self.device, ranges_str,
|
||||||
|
self.tgt.TotalSha1(include_clobbered_blocks=True),
|
||||||
|
self.device))
|
||||||
|
script.AppendExtra("")
|
||||||
|
|
||||||
def WriteVerifyScript(self, script):
|
def WriteVerifyScript(self, script):
|
||||||
partition = self.partition
|
partition = self.partition
|
||||||
if not self.src:
|
if not self.src:
|
||||||
|
@@ -152,6 +152,15 @@ class EdifyGenerator(object):
|
|||||||
"".join([', "%s"' % (i,) for i in sha1]) +
|
"".join([', "%s"' % (i,) for i in sha1]) +
|
||||||
') || abort("\\"%s\\" has unexpected contents.");' % (filename,))
|
') || abort("\\"%s\\" has unexpected contents.");' % (filename,))
|
||||||
|
|
||||||
|
def Verify(self, filename):
|
||||||
|
"""Check that the given file (or MTD reference) has one of the
|
||||||
|
given hashes (encoded in the filename)."""
|
||||||
|
self.script.append(
|
||||||
|
'apply_patch_check("{filename}") && '
|
||||||
|
'ui_print(" Verified.") || '
|
||||||
|
'ui_print("\\"{filename}\\" has unexpected contents.");'.format(
|
||||||
|
filename=filename))
|
||||||
|
|
||||||
def FileCheck(self, filename, *sha1):
|
def FileCheck(self, filename, *sha1):
|
||||||
"""Check that the given file (or MTD reference) has one of the
|
"""Check that the given file (or MTD reference) has one of the
|
||||||
given *sha1 hashes."""
|
given *sha1 hashes."""
|
||||||
|
@@ -91,6 +91,9 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package
|
|||||||
--stash_threshold <float>
|
--stash_threshold <float>
|
||||||
Specifies the threshold that will be used to compute the maximum
|
Specifies the threshold that will be used to compute the maximum
|
||||||
allowed stash size (defaults to 0.8).
|
allowed stash size (defaults to 0.8).
|
||||||
|
|
||||||
|
--gen_verify
|
||||||
|
Generate an OTA package that verifies the partitions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -133,6 +136,7 @@ OPTIONS.full_bootloader = False
|
|||||||
# Stash size cannot exceed cache_size * threshold.
|
# Stash size cannot exceed cache_size * threshold.
|
||||||
OPTIONS.cache_size = None
|
OPTIONS.cache_size = None
|
||||||
OPTIONS.stash_threshold = 0.8
|
OPTIONS.stash_threshold = 0.8
|
||||||
|
OPTIONS.gen_verify = False
|
||||||
|
|
||||||
def MostPopularKey(d, default):
|
def MostPopularKey(d, default):
|
||||||
"""Given a dict, return the key corresponding to the largest
|
"""Given a dict, return the key corresponding to the largest
|
||||||
@@ -984,6 +988,79 @@ endif;
|
|||||||
WriteMetadata(metadata, output_zip)
|
WriteMetadata(metadata, output_zip)
|
||||||
|
|
||||||
|
|
||||||
|
def WriteVerifyPackage(input_zip, output_zip):
|
||||||
|
script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
|
||||||
|
|
||||||
|
oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
|
||||||
|
recovery_mount_options = OPTIONS.info_dict.get(
|
||||||
|
"recovery_mount_options")
|
||||||
|
oem_dict = None
|
||||||
|
if oem_props is not None and len(oem_props) > 0:
|
||||||
|
if OPTIONS.oem_source is None:
|
||||||
|
raise common.ExternalError("OEM source required for this build")
|
||||||
|
script.Mount("/oem", recovery_mount_options)
|
||||||
|
oem_dict = common.LoadDictionaryFromLines(
|
||||||
|
open(OPTIONS.oem_source).readlines())
|
||||||
|
|
||||||
|
target_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.info_dict)
|
||||||
|
metadata = {
|
||||||
|
"post-build": target_fp,
|
||||||
|
"pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
|
||||||
|
OPTIONS.info_dict),
|
||||||
|
"post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
|
||||||
|
}
|
||||||
|
|
||||||
|
device_specific = common.DeviceSpecificParams(
|
||||||
|
input_zip=input_zip,
|
||||||
|
input_version=OPTIONS.info_dict["recovery_api_version"],
|
||||||
|
output_zip=output_zip,
|
||||||
|
script=script,
|
||||||
|
input_tmp=OPTIONS.input_tmp,
|
||||||
|
metadata=metadata,
|
||||||
|
info_dict=OPTIONS.info_dict)
|
||||||
|
|
||||||
|
AppendAssertions(script, OPTIONS.info_dict, oem_dict)
|
||||||
|
|
||||||
|
script.Print("Verifying device images against %s..." % target_fp)
|
||||||
|
script.AppendExtra("")
|
||||||
|
|
||||||
|
script.Print("Verifying boot...")
|
||||||
|
boot_img = common.GetBootableImage(
|
||||||
|
"boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
|
||||||
|
boot_type, boot_device = common.GetTypeAndDevice(
|
||||||
|
"/boot", OPTIONS.info_dict)
|
||||||
|
script.Verify("%s:%s:%d:%s" % (
|
||||||
|
boot_type, boot_device, boot_img.size, boot_img.sha1))
|
||||||
|
script.AppendExtra("")
|
||||||
|
|
||||||
|
script.Print("Verifying recovery...")
|
||||||
|
recovery_img = common.GetBootableImage(
|
||||||
|
"recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
|
||||||
|
recovery_type, recovery_device = common.GetTypeAndDevice(
|
||||||
|
"/recovery", OPTIONS.info_dict)
|
||||||
|
script.Verify("%s:%s:%d:%s" % (
|
||||||
|
recovery_type, recovery_device, recovery_img.size, recovery_img.sha1))
|
||||||
|
script.AppendExtra("")
|
||||||
|
|
||||||
|
system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
|
||||||
|
system_tgt.ResetFileMap()
|
||||||
|
system_diff = common.BlockDifference("system", system_tgt, src=None)
|
||||||
|
system_diff.WriteStrictVerifyScript(script)
|
||||||
|
|
||||||
|
if HasVendorPartition(input_zip):
|
||||||
|
vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
|
||||||
|
vendor_tgt.ResetFileMap()
|
||||||
|
vendor_diff = common.BlockDifference("vendor", vendor_tgt, src=None)
|
||||||
|
vendor_diff.WriteStrictVerifyScript(script)
|
||||||
|
|
||||||
|
# Device specific partitions, such as radio, bootloader and etc.
|
||||||
|
device_specific.VerifyOTA_Assertions()
|
||||||
|
|
||||||
|
script.SetProgress(1.0)
|
||||||
|
script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
|
||||||
|
WriteMetadata(metadata, output_zip)
|
||||||
|
|
||||||
|
|
||||||
class FileDifference(object):
|
class FileDifference(object):
|
||||||
def __init__(self, partition, source_zip, target_zip, output_zip):
|
def __init__(self, partition, source_zip, target_zip, output_zip):
|
||||||
self.deferred_patch_list = None
|
self.deferred_patch_list = None
|
||||||
@@ -1552,6 +1629,8 @@ def main(argv):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Cannot parse value %r for option %r - expecting "
|
raise ValueError("Cannot parse value %r for option %r - expecting "
|
||||||
"a float" % (a, o))
|
"a float" % (a, o))
|
||||||
|
elif o == "--gen_verify":
|
||||||
|
OPTIONS.gen_verify = True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -1577,6 +1656,7 @@ def main(argv):
|
|||||||
"verify",
|
"verify",
|
||||||
"no_fallback_to_full",
|
"no_fallback_to_full",
|
||||||
"stash_threshold=",
|
"stash_threshold=",
|
||||||
|
"gen_verify"
|
||||||
], extra_option_handler=option_handler)
|
], extra_option_handler=option_handler)
|
||||||
|
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
@@ -1641,8 +1721,12 @@ def main(argv):
|
|||||||
print "--- can't determine the cache partition size ---"
|
print "--- can't determine the cache partition size ---"
|
||||||
OPTIONS.cache_size = cache_size
|
OPTIONS.cache_size = cache_size
|
||||||
|
|
||||||
|
# Generate a verify package.
|
||||||
|
if OPTIONS.gen_verify:
|
||||||
|
WriteVerifyPackage(input_zip, output_zip)
|
||||||
|
|
||||||
# Generate a full OTA.
|
# Generate a full OTA.
|
||||||
if OPTIONS.incremental_source is None:
|
elif OPTIONS.incremental_source is None:
|
||||||
WriteFullOTAPackage(input_zip, output_zip)
|
WriteFullOTAPackage(input_zip, output_zip)
|
||||||
|
|
||||||
# Generate an incremental OTA. It will fall back to generate a full OTA on
|
# Generate an incremental OTA. It will fall back to generate a full OTA on
|
||||||
|
Reference in New Issue
Block a user