diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/releasetools/blockimgdiff.py | 1 | ||||
| -rw-r--r-- | tools/releasetools/common.py | 82 | ||||
| -rw-r--r-- | tools/releasetools/edify_generator.py | 118 | ||||
| -rwxr-xr-x | tools/releasetools/ota_from_target_files.py | 49 |
4 files changed, 199 insertions, 51 deletions
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py index 72f065d19d..8b6a6900d5 100644 --- a/tools/releasetools/blockimgdiff.py +++ b/tools/releasetools/blockimgdiff.py @@ -1558,6 +1558,7 @@ class BlockImageDiff(object): split_large_apks = [] cache_size = common.OPTIONS.cache_size split_threshold = 0.125 + assert cache_size is not None max_blocks_per_transfer = int(cache_size * split_threshold / self.tgt.blocksize) empty = RangeSet() diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 42c29c01c9..96f93a82c7 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -677,9 +677,14 @@ def LoadInfoDict(input_file, repacking=False): makeint("userdata_size") makeint("cache_size") makeint("recovery_size") - makeint("boot_size") makeint("fstab_version") + boot_images = "boot.img" + if "boot_images" in d: + boot_images = d["boot_images"] + for b in boot_images.split(): + makeint(b.replace(".img","_size")) + # Load recovery fstab if applicable. d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper) @@ -843,12 +848,13 @@ class PartitionBuildProps(object): def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path, system_root_image=False): class Partition(object): - def __init__(self, mount_point, fs_type, device, length, context): + def __init__(self, mount_point, fs_type, device, length, context, slotselect): self.mount_point = mount_point self.fs_type = fs_type self.device = device self.length = length self.context = context + self.slotselect = slotselect try: data = read_helper(recovery_fstab_path) @@ -876,10 +882,13 @@ def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path, # It's a good line, parse it. length = 0 + slotselect = False options = options.split(",") for i in options: if i.startswith("length="): length = int(i[7:]) + elif i == "slotselect": + slotselect = True else: # Ignore all unknown options in the unified fstab. continue @@ -893,7 +902,8 @@ def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path, mount_point = pieces[1] d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2], - device=pieces[0], length=length, context=context) + device=pieces[0], length=length, context=context, + slotselect=slotselect) # / is used for the system mount point when the root directory is included in # system. Other areas assume system is always at "/system" so point /system @@ -908,7 +918,8 @@ def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper): """Finds the path to recovery fstab and loads its contents.""" # recovery fstab is only meaningful when installing an update via recovery # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA. - if info_dict.get('ab_update') == 'true': + if info_dict.get('ab_update') == 'true' and \ + info_dict.get("allow_non_ab") != "true": return None # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to @@ -1334,7 +1345,10 @@ def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None, # AVB: if enabled, calculate and add hash to boot.img or recovery.img. if info_dict.get("avb_enable") == "true": avbtool = info_dict["avb_avbtool"] - part_size = info_dict[partition_name + "_size"] + if partition_name == "recovery": + part_size = info_dict["recovery_size"] + else: + part_size = info_dict[image_name.replace(".img","_size")] cmd = [avbtool, "add_hash_footer", "--image", img.name, "--partition_size", str(part_size), "--partition_name", partition_name] @@ -2672,11 +2686,12 @@ class BlockDifference(object): self.device = 'map_partition("%s")' % partition else: if OPTIONS.source_info_dict is None: - _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict) + _, device_expr = GetTypeAndDeviceExpr("/" + partition, + OPTIONS.info_dict) else: - _, device_path = GetTypeAndDevice("/" + partition, - OPTIONS.source_info_dict) - self.device = '"%s"' % device_path + _, device_expr = GetTypeAndDeviceExpr("/" + partition, + OPTIONS.source_info_dict) + self.device = device_expr @property def required_cache(self): @@ -2908,16 +2923,51 @@ PARTITION_TYPES = { "squashfs": "EMMC" } - -def GetTypeAndDevice(mount_point, info): +def GetTypeAndDevice(mount_point, info, check_no_slot=True): + """ + Use GetTypeAndDeviceExpr whenever possible. This function is kept for + backwards compatibility. It aborts if the fstab entry has slotselect option + (unless check_no_slot is explicitly set to False). + """ fstab = info["fstab"] if fstab: + if check_no_slot: + assert not fstab[mount_point].slotselect, \ + "Use GetTypeAndDeviceExpr instead" return (PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device) else: raise KeyError +def GetTypeAndDeviceExpr(mount_point, info): + """ + Return the filesystem of the partition, and an edify expression that evaluates + to the device at runtime. + """ + fstab = info["fstab"] + if fstab: + p = fstab[mount_point] + device_expr = '"%s"' % fstab[mount_point].device + if p.slotselect: + device_expr = 'add_slot_suffix(%s)' % device_expr + return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr) + else: + raise KeyError + + +def GetEntryForDevice(fstab, device): + """ + Returns: + The first entry in fstab whose device is the given value. + """ + if not fstab: + return None + for mount_point in fstab: + if fstab[mount_point].device == device: + return fstab[mount_point] + return None + def ParseCertificate(data): """Parses and converts a PEM-encoded certificate into DER-encoded. @@ -3042,8 +3092,10 @@ def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img, try: # The following GetTypeAndDevice()s need to use the path in the target # info_dict instead of source_info_dict. - boot_type, boot_device = GetTypeAndDevice("/boot", info_dict) - recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict) + boot_type, boot_device = GetTypeAndDevice("/boot", info_dict, + check_no_slot=False) + recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict, + check_no_slot=False) except KeyError: return @@ -3085,8 +3137,8 @@ fi 'recovery_size': recovery_img.size, 'recovery_sha1': recovery_img.sha1, 'boot_type': boot_type, - 'boot_device': boot_device, - 'recovery_type': recovery_type, + 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)', + 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)', 'recovery_device': recovery_device, 'bonus_args': bonus_args} diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py index 7ed85fecc9..99e21f1c04 100644 --- a/tools/releasetools/edify_generator.py +++ b/tools/releasetools/edify_generator.py @@ -183,11 +183,30 @@ class EdifyGenerator(object): It checks the checksums of the given partitions. If none of them matches the expected checksum, updater will additionally look for a backup on /cache. """ + self._CheckSecondTokenNotSlotSuffixed(target, "PatchPartitionExprCheck") + self._CheckSecondTokenNotSlotSuffixed(source, "PatchPartitionExprCheck") + self.PatchPartitionExprCheck('"%s"' % target, '"%s"' % source) + + def PatchPartitionExprCheck(self, target_expr, source_expr): + """Checks whether updater can patch the given partitions. + + It checks the checksums of the given partitions. If none of them matches the + expected checksum, updater will additionally look for a backup on /cache. + + Args: + target_expr: an Edify expression that serves as the target arg to + patch_partition. Must be evaluated to a string in the form of + foo:bar:baz:quux + source_expr: an Edify expression that serves as the source arg to + patch_partition. Must be evaluated to a string in the form of + foo:bar:baz:quux + """ self.script.append(self.WordWrap(( - 'patch_partition_check("{target}",\0"{source}") ||\n abort(' - '"E{code}: \\"{target}\\" or \\"{source}\\" has unexpected ' - 'contents.");').format( - target=target, source=source, + 'patch_partition_check({target},\0{source}) ||\n abort(' + 'concat("E{code}: \\"",{target},"\\" or \\"",{source},"\\" has ' + 'unexpected contents."));').format( + target=target_expr, + source=source_expr, code=common.ErrorCode.BAD_PATCH_FILE))) def CacheFreeSpaceCheck(self, amount): @@ -218,8 +237,9 @@ class EdifyGenerator(object): mount_flags = mount_dict.get(p.fs_type, "") if p.context is not None: mount_flags = p.context + ("," + mount_flags if mount_flags else "") - self.script.append('mount("%s", "%s", "%s", "%s", "%s");' % ( - p.fs_type, common.PARTITION_TYPES[p.fs_type], p.device, + self.script.append('mount("%s", "%s", %s, "%s", "%s");' % ( + p.fs_type, common.PARTITION_TYPES[p.fs_type], + self._GetSlotSuffixDeviceForEntry(p), p.mount_point, mount_flags)) self.mounts.add(p.mount_point) @@ -242,8 +262,9 @@ class EdifyGenerator(object): raise ValueError("Partition %s cannot be tuned\n" % (partition,)) self.script.append( 'tune2fs(' + "".join(['"%s", ' % (i,) for i in options]) + - '"%s") || abort("E%d: Failed to tune partition %s");' % ( - p.device, common.ErrorCode.TUNE_PARTITION_FAILURE, partition)) + '%s) || abort("E%d: Failed to tune partition %s");' % ( + self._GetSlotSuffixDeviceForEntry(p), + common.ErrorCode.TUNE_PARTITION_FAILURE, partition)) def FormatPartition(self, partition): """Format the given partition, specified by its mount point (eg, @@ -252,18 +273,19 @@ class EdifyGenerator(object): fstab = self.fstab if fstab: p = fstab[partition] - self.script.append('format("%s", "%s", "%s", "%s", "%s");' % + self.script.append('format("%s", "%s", %s, "%s", "%s");' % (p.fs_type, common.PARTITION_TYPES[p.fs_type], - p.device, p.length, p.mount_point)) + self._GetSlotSuffixDeviceForEntry(p), + p.length, p.mount_point)) def WipeBlockDevice(self, partition): if partition not in ("/system", "/vendor"): raise ValueError(("WipeBlockDevice doesn't work on %s\n") % (partition,)) fstab = self.fstab size = self.info.get(partition.lstrip("/") + "_size", None) - device = fstab[partition].device + device = self._GetSlotSuffixDeviceForEntry(fstab[partition]) - self.script.append('wipe_block_device("%s", %s);' % (device, size)) + self.script.append('wipe_block_device(%s, %s);' % (device, size)) def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs): """Apply binary patches (in *patchpairs) to the given srcfile to @@ -296,14 +318,69 @@ class EdifyGenerator(object): self.PatchPartition(target, source, patch) def PatchPartition(self, target, source, patch): - """Applies the patch to the source partition and writes it to target.""" + """ + Applies the patch to the source partition and writes it to target. + + Args: + target: the target arg to patch_partition. Must be in the form of + foo:bar:baz:quux + source: the source arg to patch_partition. Must be in the form of + foo:bar:baz:quux + patch: the patch arg to patch_partition. Must be an unquoted string. + """ + self._CheckSecondTokenNotSlotSuffixed(target, "PatchPartitionExpr") + self._CheckSecondTokenNotSlotSuffixed(source, "PatchPartitionExpr") + self.PatchPartitionExpr('"%s"' % target, '"%s"' % source, '"%s"' % patch) + + def PatchPartitionExpr(self, target_expr, source_expr, patch_expr): + """ + Applies the patch to the source partition and writes it to target. + + Args: + target_expr: an Edify expression that serves as the target arg to + patch_partition. Must be evaluated to a string in the form of + foo:bar:baz:quux + source_expr: an Edify expression that serves as the source arg to + patch_partition. Must be evaluated to a string in the form of + foo:bar:baz:quux + patch_expr: an Edify expression that serves as the patch arg to + patch_partition. Must be evaluated to a string. + """ self.script.append(self.WordWrap(( - 'patch_partition("{target}",\0"{source}",\0' - 'package_extract_file("{patch}")) ||\n' - ' abort("E{code}: Failed to apply patch to {source}");').format( - target=target, source=source, patch=patch, + 'patch_partition({target},\0{source},\0' + 'package_extract_file({patch})) ||\n' + ' abort(concat(' + ' "E{code}: Failed to apply patch to ",{source}));').format( + target=target_expr, + source=source_expr, + patch=patch_expr, code=common.ErrorCode.APPLY_PATCH_FAILURE))) + def _GetSlotSuffixDeviceForEntry(self, entry=None): + """ + Args: + entry: the fstab entry of device "foo" + Returns: + An edify expression. Caller must not quote result. + If foo is slot suffixed, it returns + 'add_slot_suffix("foo")' + Otherwise it returns + '"foo"' (quoted) + """ + assert entry is not None + if entry.slotselect: + return 'add_slot_suffix("%s")' % entry.device + return '"%s"' % entry.device + + def _CheckSecondTokenNotSlotSuffixed(self, s, fn): + lst = s.split(':') + assert(len(s) == 4), "{} does not contain 4 tokens".format(s) + if self.fstab: + entry = common.GetEntryForDevice(s[1]) + if entry is not None: + assert not entry.slotselect, \ + "Use %s because %s is slot suffixed" % (fn, s[1]) + def WriteRawImage(self, mount_point, fn, mapfn=None): """Write the given package file into the partition for the given mount point.""" @@ -312,15 +389,16 @@ class EdifyGenerator(object): if fstab: p = fstab[mount_point] partition_type = common.PARTITION_TYPES[p.fs_type] - args = {'device': p.device, 'fn': fn} + device = self._GetSlotSuffixDeviceForEntry(p) + args = {'device': device, 'fn': fn} if partition_type == "EMMC": if mapfn: args["map"] = mapfn self.script.append( - 'package_extract_file("%(fn)s", "%(device)s", "%(map)s");' % args) + 'package_extract_file("%(fn)s", %(device)s, "%(map)s");' % args) else: self.script.append( - 'package_extract_file("%(fn)s", "%(device)s");' % args) + 'package_extract_file("%(fn)s", %(device)s);' % args) else: raise ValueError( "don't know how to write \"%s\" partitions" % p.fs_type) diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index ad001d13c7..16b278a519 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -78,6 +78,13 @@ Common options that apply to both of non-A/B and A/B OTAs Write a copy of the metadata to a separate file. Therefore, users can read the post build fingerprint without extracting the OTA package. + --force_non_ab + This flag can only be set on an A/B device that also supports non-A/B + updates. Implies --two_step. + If set, generate that non-A/B update package. + If not set, generates A/B package for A/B device and non-A/B package for + non-A/B device. + Non-A/B OTA specific options -b (--binary) <file> @@ -251,6 +258,7 @@ OPTIONS.skip_compatibility_check = False OPTIONS.output_metadata_path = None OPTIONS.disable_fec_computation = False OPTIONS.boot_variable_values = None +OPTIONS.force_non_ab = False METADATA_NAME = 'META-INF/com/android/metadata' @@ -933,7 +941,7 @@ def GetPackageMetadata(target_info, source_info=None): 'ro.build.version.security_patch'), } - if target_info.is_ab: + if target_info.is_ab and not OPTIONS.force_non_ab: metadata['ota-type'] = 'AB' metadata['ota-required-cache'] = '0' else: @@ -1455,7 +1463,8 @@ else if get_stage("%(bcb_dev)s") != "3/3" then required_cache_sizes = [diff.required_cache for diff in block_diff_dict.values()] if updating_boot: - boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info) + boot_type, boot_device_expr = common.GetTypeAndDeviceExpr("/boot", + source_info) d = common.Difference(target_boot, source_boot) _, _, d = d.ComputePatch() if d is None: @@ -1470,11 +1479,11 @@ else if get_stage("%(bcb_dev)s") != "3/3" then common.ZipWriteStr(output_zip, "boot.img.p", d) - script.PatchPartitionCheck( - "{}:{}:{}:{}".format( - boot_type, boot_device, target_boot.size, target_boot.sha1), - "{}:{}:{}:{}".format( - boot_type, boot_device, source_boot.size, source_boot.sha1)) + 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.PatchPartitionExprCheck(target_expr, source_expr) required_cache_sizes.append(target_boot.size) @@ -1542,12 +1551,11 @@ else logger.info("boot image changed; including patch.") script.Print("Patching boot image...") script.ShowProgress(0.1, 10) - script.PatchPartition( - '{}:{}:{}:{}'.format( - boot_type, boot_device, target_boot.size, target_boot.sha1), - '{}:{}:{}:{}'.format( - boot_type, boot_device, source_boot.size, source_boot.sha1), - 'boot.img.p') + 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.") @@ -2067,6 +2075,8 @@ def main(argv): OPTIONS.output_metadata_path = a elif o == "--disable_fec_computation": OPTIONS.disable_fec_computation = True + elif o == "--force_non_ab": + OPTIONS.force_non_ab = True else: return False return True @@ -2103,6 +2113,7 @@ def main(argv): "skip_compatibility_check", "output_metadata_path=", "disable_fec_computation", + "force_non_ab", ], extra_option_handler=option_handler) if len(args) != 2: @@ -2164,11 +2175,17 @@ def main(argv): OPTIONS.skip_postinstall = True ab_update = OPTIONS.info_dict.get("ab_update") == "true" + allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true" + if OPTIONS.force_non_ab: + assert allow_non_ab, "--force_non_ab only allowed on devices that supports non-A/B" + assert ab_update, "--force_non_ab only allowed on A/B devices" + + generate_ab = not OPTIONS.force_non_ab and ab_update # Use the default key to sign the package if not specified with package_key. # package_keys are needed on ab_updates, so always define them if an - # ab_update is getting created. - if not OPTIONS.no_signing or ab_update: + # A/B update is getting created. + if not OPTIONS.no_signing or generate_ab: if OPTIONS.package_key is None: OPTIONS.package_key = OPTIONS.info_dict.get( "default_system_dev_certificate", @@ -2176,7 +2193,7 @@ def main(argv): # Get signing keys OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) - if ab_update: + if generate_ab: GenerateAbOtaPackage( target_file=args[0], output_file=args[1], |