diff options
Diffstat (limited to 'tools/releasetools/ota_from_target_files')
-rwxr-xr-x | tools/releasetools/ota_from_target_files | 1014 |
1 files changed, 746 insertions, 268 deletions
diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files index e695218..5f2354c 100755 --- a/tools/releasetools/ota_from_target_files +++ b/tools/releasetools/ota_from_target_files @@ -21,7 +21,7 @@ a full OTA is produced. Usage: ota_from_target_files [flags] input_target_files output_ota_package - -b (--board_config) <file> + --board_config <file> Deprecated. -k (--package_key) <key> Key to use to sign the package (default is @@ -37,6 +37,10 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package Generate an incremental OTA using the given target-files zip as the starting build. + -o (--oem_settings) <file> + Use the file to specify the expected OEM-specific properties + on the OEM partition of the intended device. + -w (--wipe_user_data) Generate an OTA package that will wipe the user data partition when installed. @@ -57,6 +61,16 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package first, so that any changes made to the system partition are done using the new recovery (new kernel, etc.). + --block + Generate a block-based OTA if possible. Will fall back to a + file-based OTA if the target_files is older and doesn't support + block-based OTAs. + + -b (--binary) <file> + Use the given binary as the update-binary in the output package, + instead of the binary in the build's target_files. Use for + development only. + -t (--worker_threads) <int> Specifies the number of worker-threads that will be used when generating patches for incremental updates (defaults to 3). @@ -65,8 +79,8 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package import sys -if sys.hexversion < 0x02040000: - print >> sys.stderr, "Python 2.4 or newer is required." +if sys.hexversion < 0x02070000: + print >> sys.stderr, "Python 2.7 or newer is required." sys.exit(1) import copy @@ -84,7 +98,9 @@ except ImportError: from sha import sha as sha1 import common +import img_from_target_files import edify_generator +import build_image OPTIONS = common.OPTIONS OPTIONS.package_key = None @@ -99,6 +115,9 @@ OPTIONS.aslr_mode = True OPTIONS.worker_threads = 3 OPTIONS.two_step = False OPTIONS.no_signing = False +OPTIONS.block_based = False +OPTIONS.updater_binary = None +OPTIONS.oem_source = None def MostPopularKey(d, default): """Given a dict, return the key corresponding to the largest @@ -144,50 +163,21 @@ def ClosestFileMatch(src, tgtfiles, existing): return result return None -class Item: - """Items represent the metadata (user, group, mode) of files and - directories in the system image.""" - ITEMS = {} - def __init__(self, name, dir=False): - self.name = name - self.uid = None - self.gid = None - self.mode = None - self.selabel = None - self.capabilities = None - self.dir = dir - - if name: - self.parent = Item.Get(os.path.dirname(name), dir=True) - self.parent.children.append(self) - else: - self.parent = None - if dir: - self.children = [] - - def Dump(self, indent=0): - if self.uid is not None: - print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) - else: - print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) - if self.dir: - print "%s%s" % (" "*indent, self.descendants) - print "%s%s" % (" "*indent, self.best_subtree) - for i in self.children: - i.Dump(indent=indent+1) - - @classmethod - def Get(cls, name, dir=False): - if name not in cls.ITEMS: - cls.ITEMS[name] = Item(name, dir=dir) - return cls.ITEMS[name] +class ItemSet: + def __init__(self, partition, fs_config): + self.partition = partition + self.fs_config = fs_config + self.ITEMS = {} - @classmethod - def GetMetadata(cls, input_zip): + def Get(self, name, dir=False): + if name not in self.ITEMS: + self.ITEMS[name] = Item(self, name, dir=dir) + return self.ITEMS[name] + def GetMetadata(self, input_zip): # The target_files contains a record of what the uid, # gid, and mode are supposed to be. - output = input_zip.read("META/filesystem_config.txt") + output = input_zip.read(self.fs_config) for line in output.split("\n"): if not line: continue @@ -205,7 +195,7 @@ class Item: if key == "capabilities": capabilities = value - i = cls.ITEMS.get(name, None) + i = self.ITEMS.get(name, None) if i is not None: i.uid = int(uid) i.gid = int(gid) @@ -216,11 +206,44 @@ class Item: i.children.sort(key=lambda i: i.name) # set metadata for the files generated by this script. - i = cls.ITEMS.get("system/recovery-from-boot.p", None) + i = self.ITEMS.get("system/recovery-from-boot.p", None) if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0644, None, None - i = cls.ITEMS.get("system/etc/install-recovery.sh", None) + i = self.ITEMS.get("system/etc/install-recovery.sh", None) if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0544, None, None + +class Item: + """Items represent the metadata (user, group, mode) of files and + directories in the system image.""" + def __init__(self, itemset, name, dir=False): + self.itemset = itemset + self.name = name + self.uid = None + self.gid = None + self.mode = None + self.selabel = None + self.capabilities = None + self.dir = dir + + if name: + self.parent = itemset.Get(os.path.dirname(name), dir=True) + self.parent.children.append(self) + else: + self.parent = None + if dir: + self.children = [] + + def Dump(self, indent=0): + if self.uid is not None: + print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) + else: + print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) + if self.dir: + print "%s%s" % (" "*indent, self.descendants) + print "%s%s" % (" "*indent, self.best_subtree) + for i in self.children: + i.Dump(indent=indent+1) + def CountChildMetadata(self): """Count up the (uid, gid, mode, selabel, capabilities) tuples for all children and determine the best strategy for using set_perm_recursive and @@ -305,9 +328,8 @@ class Item: recurse(self, (-1, -1, -1, -1, None, None)) -def CopySystemFiles(input_zip, output_zip=None, - substitute=None): - """Copies files underneath system/ in the input zip to the output +def CopyPartitionFiles(itemset, input_zip, output_zip=None, substitute=None): + """Copies files for the partition in the input zip to the output zip. Populates the Item class with their metadata, and returns a list of symlinks. output_zip may be None, in which case the copy is skipped (but the other side effects still happen). substitute is an @@ -317,15 +339,17 @@ def CopySystemFiles(input_zip, output_zip=None, symlinks = [] + partition = itemset.partition + for info in input_zip.infolist(): - if info.filename.startswith("SYSTEM/"): + if info.filename.startswith(partition.upper() + "/"): basefilename = info.filename[7:] if IsSymlink(info): symlinks.append((input_zip.read(info.filename), - "/system/" + basefilename)) + "/" + partition + "/" + basefilename)) else: info2 = copy.copy(info) - fn = info2.filename = "system/" + basefilename + fn = info2.filename = partition + "/" + basefilename if substitute and fn in substitute and substitute[fn] is None: continue if output_zip is not None: @@ -335,9 +359,9 @@ def CopySystemFiles(input_zip, output_zip=None, data = input_zip.read(info.filename) output_zip.writestr(info2, data) if fn.endswith("/"): - Item.Get(fn[:-1], dir=True) + itemset.Get(fn[:-1], dir=True) else: - Item.Get(fn, dir=False) + itemset.Get(fn, dir=False) symlinks.sort() return symlinks @@ -351,64 +375,48 @@ def SignOutput(temp_zip_name, output_zip_name): whole_file=True) -def AppendAssertions(script, info_dict): - device = GetBuildProp("ro.product.device", info_dict) - script.AssertDevice(device) - - -def MakeRecoveryPatch(input_tmp, output_zip, recovery_img, boot_img): - """Generate a binary patch that creates the recovery image starting - with the boot image. (Most of the space in these images is just the - kernel, which is identical for the two, so the resulting patch - should be efficient.) Add it to the output zip, along with a shell - script that is run from init.rc on first boot to actually do the - patching and install the new recovery image. +def AppendAssertions(script, info_dict, oem_dict = None): + oem_props = info_dict.get("oem_fingerprint_properties") + if oem_props is None: + device = GetBuildProp("ro.product.device", info_dict) + script.AssertDevice(device) + else: + if oem_dict is None: + raise common.ExternalError("No OEM file provided to answer expected assertions") + for prop in oem_props.split(): + if oem_dict.get(prop) is None: + raise common.ExternalError("The OEM file is missing the property %s" % prop) + script.AssertOemProperty(prop, oem_dict.get(prop)) - recovery_img and boot_img should be File objects for the - corresponding images. info should be the dictionary returned by - common.LoadInfoDict() on the input target_files. - Returns an Item for the shell script, which must be made - executable. - """ - - diff_program = ["imgdiff"] - path = os.path.join(input_tmp, "SYSTEM", "etc", "recovery-resource.dat") - if os.path.exists(path): - diff_program.append("-b") - diff_program.append(path) - bonus_args = "-b /system/etc/recovery-resource.dat" - else: - bonus_args = "" +def HasRecoveryPatch(target_files_zip): + try: + target_files_zip.getinfo("SYSTEM/recovery-from-boot.p") + return True + except KeyError: + return False - d = common.Difference(recovery_img, boot_img, diff_program=diff_program) - _, _, patch = d.ComputePatch() - common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch) - Item.Get("system/recovery-from-boot.p", dir=False) +def HasVendorPartition(target_files_zip): + try: + target_files_zip.getinfo("VENDOR/") + return True + except KeyError: + return False - boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) - recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict) +def GetOemProperty(name, oem_props, oem_dict, info_dict): + if oem_props is not None and name in oem_props: + return oem_dict[name] + return GetBuildProp(name, info_dict) - sh = """#!/system/bin/sh -if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then - log -t recovery "Installing new recovery image" - applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p -else - log -t recovery "Recovery image already installed" -fi -""" % { 'boot_size': boot_img.size, - 'boot_sha1': boot_img.sha1, - 'recovery_size': recovery_img.size, - 'recovery_sha1': recovery_img.sha1, - 'boot_type': boot_type, - 'boot_device': boot_device, - 'recovery_type': recovery_type, - 'recovery_device': recovery_device, - 'bonus_args': bonus_args, - } - common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) - return Item.Get("system/etc/install-recovery.sh", dir=False) +def CalculateFingerprint(oem_props, oem_dict, info_dict): + if oem_props is None: + return GetBuildProp("ro.build.fingerprint", info_dict) + return "%s/%s/%s:%s" % ( + GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict), + GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict), + GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict), + GetBuildProp("ro.build.thumbprint", info_dict)) def WriteFullOTAPackage(input_zip, output_zip): # TODO: how to determine this? We don't know what version it will @@ -416,9 +424,17 @@ def WriteFullOTAPackage(input_zip, output_zip): # change very often. script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) - metadata = {"post-build": GetBuildProp("ro.build.fingerprint", - OPTIONS.info_dict), - "pre-device": GetBuildProp("ro.product.device", + oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") + oem_dict = None + if oem_props is not None: + if OPTIONS.oem_source is None: + raise common.ExternalError("OEM source required for this build") + script.Mount("/oem") + oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines()) + + metadata = {"post-build": CalculateFingerprint( + oem_props, oem_dict, OPTIONS.info_dict), + "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, OPTIONS.info_dict), "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), @@ -433,12 +449,15 @@ def WriteFullOTAPackage(input_zip, output_zip): metadata=metadata, info_dict=OPTIONS.info_dict) + has_recovery_patch = HasRecoveryPatch(input_zip) + block_based = OPTIONS.block_based and has_recovery_patch + if not OPTIONS.omit_prereq: ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) script.AssertOlderBuild(ts, ts_text) - AppendAssertions(script, OPTIONS.info_dict) + AppendAssertions(script, OPTIONS.info_dict, oem_dict) device_specific.FullOTA_Assertions() # Two-step package strategy (in chronological order, which is *not* @@ -482,37 +501,82 @@ else if get_stage("%(bcb_dev)s", "stage") == "3/3" then device_specific.FullOTA_InstallBegin() - script.ShowProgress(0.5, 0) + system_progress = 0.75 if OPTIONS.wipe_user_data: - script.FormatPartition("/data") + system_progress -= 0.1 + if HasVendorPartition(input_zip): + system_progress -= 0.1 if "selinux_fc" in OPTIONS.info_dict: WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) - script.FormatPartition("/system") - script.Mount("/system") - script.UnpackPackageDir("recovery", "/system") - script.UnpackPackageDir("system", "/system") + system_items = ItemSet("system", "META/filesystem_config.txt") + script.ShowProgress(system_progress, 0) + if block_based: + mapdata, data = img_from_target_files.BuildSystem( + OPTIONS.input_tmp, OPTIONS.info_dict, + sparse=False, map_file=True) + + common.ZipWriteStr(output_zip, "system.map", mapdata) + common.ZipWriteStr(output_zip, "system.muimg", data) + script.WipeBlockDevice("/system") + script.WriteRawImage("/system", "system.muimg", mapfn="system.map") + else: + script.FormatPartition("/system") + script.Mount("/system") + if not has_recovery_patch: + script.UnpackPackageDir("recovery", "/system") + script.UnpackPackageDir("system", "/system") - symlinks = CopySystemFiles(input_zip, output_zip) - script.MakeSymlinks(symlinks) + symlinks = CopyPartitionFiles(system_items, input_zip, output_zip) + script.MakeSymlinks(symlinks) boot_img = common.GetBootableImage("boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") - MakeRecoveryPatch(OPTIONS.input_tmp, output_zip, recovery_img, boot_img) - Item.GetMetadata(input_zip) - Item.Get("system").SetPermissions(script) + if not block_based: + def output_sink(fn, data): + common.ZipWriteStr(output_zip, "recovery/" + fn, data) + system_items.Get("system/" + fn, dir=False) + + common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, + recovery_img, boot_img) + + system_items.GetMetadata(input_zip) + system_items.Get("system").SetPermissions(script) + + if HasVendorPartition(input_zip): + vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") + script.ShowProgress(0.1, 0) + + if block_based: + mapdata, data = img_from_target_files.BuildVendor( + OPTIONS.input_tmp, OPTIONS.info_dict, + sparse=False, map_file=True) + + common.ZipWriteStr(output_zip, "vendor.map", mapdata) + common.ZipWriteStr(output_zip, "vendor.muimg", data) + script.WipeBlockDevice("/vendor") + script.WriteRawImage("/vendor", "vendor.muimg", mapfn="vendor.map") + else: + script.FormatPartition("/vendor") + script.Mount("/vendor") + script.UnpackPackageDir("vendor", "/vendor") + + symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip) + script.MakeSymlinks(symlinks) + + vendor_items.GetMetadata(input_zip) + vendor_items.Get("vendor").SetPermissions(script) common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) common.ZipWriteStr(output_zip, "boot.img", boot_img.data) - script.ShowProgress(0.2, 0) - script.ShowProgress(0.2, 10) + script.ShowProgress(0.05, 5) script.WriteRawImage("/boot", "boot.img") - script.ShowProgress(0.1, 0) + script.ShowProgress(0.2, 10) device_specific.FullOTA_InstallEnd() if OPTIONS.extra_script is not None: @@ -520,6 +584,10 @@ else if get_stage("%(bcb_dev)s", "stage") == "3/3" then script.UnmountAll() + if OPTIONS.wipe_user_data: + script.ShowProgress(0.1, 10) + script.FormatPartition("/data") + if OPTIONS.two_step: script.AppendExtra(""" set_stage("%(bcb_dev)s", ""); @@ -532,7 +600,7 @@ reboot_now("%(bcb_dev)s", ""); endif; endif; """ % bcb_dev) - script.AddToZip(input_zip, output_zip) + script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary) WriteMetadata(metadata, output_zip) def WritePolicyConfig(file_context, output_zip): @@ -546,14 +614,15 @@ def WriteMetadata(metadata, output_zip): "".join(["%s=%s\n" % kv for kv in sorted(metadata.iteritems())])) -def LoadSystemFiles(z): - """Load all the files from SYSTEM/... in a given target-files +def LoadPartitionFiles(z, partition): + """Load all the files from the given partition in a given target-files ZipFile, and return a dict of {filename: File object}.""" out = {} + prefix = partition.upper() + "/" for info in z.infolist(): - if info.filename.startswith("SYSTEM/") and not IsSymlink(info): + if info.filename.startswith(prefix) and not IsSymlink(info): basefilename = info.filename[7:] - fn = "system/" + basefilename + fn = partition + "/" + basefilename data = z.read(info.filename) out[fn] = common.File(fn, data) return out @@ -564,7 +633,7 @@ def GetBuildProp(prop, info_dict): try: return info_dict.get("build.prop", {})[prop] except KeyError: - raise common.ExternalError("couldn't find %s in build.prop" % (property,)) + raise common.ExternalError("couldn't find %s in build.prop" % (prop,)) def AddToKnownPaths(filename, known_paths): if filename[-1] == "/": @@ -577,7 +646,46 @@ def AddToKnownPaths(filename, known_paths): known_paths.add(path) dirs.pop() -def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): +class BlockDifference: + def __init__(self, partition, builder, output_zip): + with tempfile.NamedTemporaryFile() as src_file: + with tempfile.NamedTemporaryFile() as tgt_file: + print "building source " + partition + " image..." + src_file = tempfile.NamedTemporaryFile() + src_mapdata, src_data = builder(OPTIONS.source_tmp, + OPTIONS.source_info_dict, + sparse=False, map_file=True) + + self.src_sha1 = sha1(src_data).hexdigest() + print "source " + partition + " sha1:", self.src_sha1 + src_file.write(src_data) + + print "building target " + partition + " image..." + tgt_file = tempfile.NamedTemporaryFile() + tgt_mapdata, tgt_data = builder(OPTIONS.target_tmp, + OPTIONS.target_info_dict, + sparse=False, map_file=True) + self.tgt_sha1 = sha1(tgt_data).hexdigest() + print "target " + partition + " sha1:", self.tgt_sha1 + tgt_len = len(tgt_data) + tgt_file.write(tgt_data) + + system_type, self.device = common.GetTypeAndDevice("/" + partition, + OPTIONS.info_dict) + self.patch = common.MakePartitionPatch(src_file, tgt_file, partition) + + TestBlockPatch(src_data, src_mapdata, self.patch.data, + tgt_mapdata, self.tgt_sha1) + src_data = None + tgt_data = None + + self.patch.AddToZip(output_zip, compression=zipfile.ZIP_STORED) + self.src_mapfilename = self.patch.name + ".src.map" + common.ZipWriteStr(output_zip, self.src_mapfilename, src_mapdata) + self.tgt_mapfilename = self.patch.name + ".tgt.map" + common.ZipWriteStr(output_zip, self.tgt_mapfilename, tgt_mapdata) + +def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): source_version = OPTIONS.source_info_dict["recovery_api_version"] target_version = OPTIONS.target_info_dict["recovery_api_version"] @@ -603,81 +711,426 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): metadata=metadata, info_dict=OPTIONS.info_dict) - print "Loading target..." - target_data = LoadSystemFiles(target_zip) - print "Loading source..." - source_data = LoadSystemFiles(source_zip) - - verbatim_targets = [] - patch_list = [] - diffs = [] - renames = {} - known_paths = set() - largest_source_size = 0 - - matching_file_cache = {} - for fn, sf in source_data.items(): - assert fn == sf.name - matching_file_cache["path:" + fn] = sf - if fn in target_data.keys(): - AddToKnownPaths(fn, known_paths) - # Only allow eligibility for filename/sha matching - # if there isn't a perfect path match. - if target_data.get(sf.name) is None: - matching_file_cache["file:" + fn.split("/")[-1]] = sf - matching_file_cache["sha:" + sf.sha1] = sf - - for fn in sorted(target_data.keys()): - tf = target_data[fn] - assert fn == tf.name - sf = ClosestFileMatch(tf, matching_file_cache, renames) - if sf is not None and sf.name != tf.name: - print "File has moved from " + sf.name + " to " + tf.name - renames[sf.name] = tf - - if sf is None or fn in OPTIONS.require_verbatim: - # This file should be included verbatim - if fn in OPTIONS.prohibit_verbatim: - raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) - print "send", fn, "verbatim" - tf.AddToZip(output_zip) - verbatim_targets.append((fn, tf.size)) - if fn in target_data.keys(): - AddToKnownPaths(fn, known_paths) - elif tf.sha1 != sf.sha1: - # File is different; consider sending as a patch - diffs.append(common.Difference(tf, sf)) - else: - # Target file data identical to source (may still be renamed) - pass - - common.ComputeDifferences(diffs) - - for diff in diffs: - tf, sf, d = diff.GetPatch() - path = "/".join(tf.name.split("/")[:-1]) - if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \ - path not in known_paths: - # patch is almost as big as the file; don't bother patching - # or a patch + rename cannot take place due to the target - # directory not existing - tf.AddToZip(output_zip) - verbatim_targets.append((tf.name, tf.size)) - if sf.name in renames: - del renames[sf.name] - AddToKnownPaths(tf.name, known_paths) - else: - common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d) - patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest())) - largest_source_size = max(largest_source_size, sf.size) - source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) metadata["pre-build"] = source_fp metadata["post-build"] = target_fp + source_boot = common.GetBootableImage( + "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", + OPTIONS.source_info_dict) + target_boot = common.GetBootableImage( + "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") + updating_boot = (not OPTIONS.two_step and + (source_boot.data != target_boot.data)) + + source_recovery = common.GetBootableImage( + "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", + OPTIONS.source_info_dict) + target_recovery = common.GetBootableImage( + "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") + updating_recovery = (source_recovery.data != target_recovery.data) + + system_diff = BlockDifference("system", img_from_target_files.BuildSystem, + output_zip) + if HasVendorPartition(target_zip): + if not HasVendorPartition(source_zip): + raise RuntimeError("can't generate incremental that adds /vendor") + vendor_diff = BlockDifference("vendor", img_from_target_files.BuildVendor, + output_zip) + + oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties") + oem_dict = None + if oem_props is not None: + if OPTIONS.oem_source is None: + raise common.ExternalError("OEM source required for this build") + script.Mount("/oem") + oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines()) + + AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) + device_specific.IncrementalOTA_Assertions() + + # 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: + if not OPTIONS.info_dict.get("multistage_support", None): + assert False, "two-step packages not supported by this build" + fs = OPTIONS.info_dict["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", "stage") == "2/3" then +""" % bcb_dev) + script.AppendExtra("sleep(20);\n"); + script.WriteRawImage("/recovery", "recovery.img") + script.AppendExtra(""" +set_stage("%(bcb_dev)s", "3/3"); +reboot_now("%(bcb_dev)s", "recovery"); +else if get_stage("%(bcb_dev)s", "stage") != "3/3" then +""" % bcb_dev) + + script.Print("Verifying current system...") + + device_specific.IncrementalOTA_VerifyBegin() + + if oem_props is None: + script.AssertSomeFingerprint(source_fp, target_fp) + else: + script.AssertSomeThumbprint( + GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), + GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) + + if updating_boot: + d = common.Difference(target_boot, source_boot) + _, _, d = d.ComputePatch() + print "boot target: %d source: %d diff: %d" % ( + target_boot.size, source_boot.size, len(d)) + + common.ZipWriteStr(output_zip, "patch/boot.img.p", d) + + boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) + + script.PatchCheck("%s:%s:%d:%s:%d:%s" % + (boot_type, boot_device, + source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1)) + + device_specific.IncrementalOTA_VerifyEnd() + + if OPTIONS.two_step: + script.WriteRawImage("/boot", "recovery.img") + script.AppendExtra(""" +set_stage("%(bcb_dev)s", "2/3"); +reboot_now("%(bcb_dev)s", ""); +else +""" % bcb_dev) + + script.Comment("---- start making changes here ----") + + device_specific.IncrementalOTA_InstallBegin() + + if HasVendorPartition(target_zip): + script.Print("Patching vendor image...") + script.ShowProgress(0.1, 0) + script.Syspatch(vendor_diff.device, + vendor_diff.tgt_mapfilename, vendor_diff.tgt_sha1, + vendor_diff.src_mapfilename, vendor_diff.src_sha1, + vendor_diff.patch.name) + sys_progress = 0.8 + else: + sys_progress = 0.9 + + script.Print("Patching system image...") + script.ShowProgress(sys_progress, 0) + script.Syspatch(system_diff.device, + system_diff.tgt_mapfilename, system_diff.tgt_sha1, + system_diff.src_mapfilename, system_diff.src_sha1, + system_diff.patch.name) + + if OPTIONS.two_step: + common.ZipWriteStr(output_zip, "boot.img", target_boot.data) + script.WriteRawImage("/boot", "boot.img") + print "writing full boot image (forced by two-step mode)" + + if not OPTIONS.two_step: + if updating_boot: + # Produce the boot image by applying a patch to the current + # contents of the boot partition, and write it back to the + # partition. + script.Print("Patching boot image...") + script.ShowProgress(0.1, 10) + script.ApplyPatch("%s:%s:%d:%s:%d:%s" + % (boot_type, boot_device, + source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1), + "-", + target_boot.size, target_boot.sha1, + source_boot.sha1, "patch/boot.img.p") + print "boot image changed; including." + else: + print "boot image unchanged; skipping." + + # Do device-specific installation (eg, write radio image). + device_specific.IncrementalOTA_InstallEnd() + + if OPTIONS.extra_script is not None: + 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) + script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) + WriteMetadata(metadata, output_zip) + +def ParseMap(map_str): + x = map_str.split() + assert int(x[0]) == 4096 + assert int(x[1]) == len(x)-2 + return int(x[0]), [int(i) for i in x[2:]] + +def TestBlockPatch(src_muimg, src_map, patch_data, tgt_map, tgt_sha1): + src_blksize, src_regions = ParseMap(src_map) + tgt_blksize, tgt_regions = ParseMap(tgt_map) + + with tempfile.NamedTemporaryFile() as src_file,\ + tempfile.NamedTemporaryFile() as patch_file,\ + tempfile.NamedTemporaryFile() as src_map_file,\ + tempfile.NamedTemporaryFile() as tgt_map_file: + + src_total = sum(src_regions) * src_blksize + src_file.truncate(src_total) + p = 0 + for i in range(0, len(src_regions), 2): + c, dc = src_regions[i:i+2] + src_file.write(src_muimg[p:(p+c*src_blksize)]) + p += c*src_blksize + src_file.seek(dc*src_blksize, 1) + assert src_file.tell() == src_total + + patch_file.write(patch_data) + + src_map_file.write(src_map) + tgt_map_file.write(tgt_map) + + src_file.flush() + src_map_file.flush() + patch_file.flush() + tgt_map_file.flush() + + p = common.Run(["syspatch_host", src_file.name, src_map_file.name, + patch_file.name, src_file.name, tgt_map_file.name], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + stdoutdata, _ = p.communicate() + if p.returncode != 0: + print stdoutdata + raise ValueError("failed to reconstruct target system image from patch") + + h = sha1() + src_file.seek(0, 0) + for i in range(0, len(tgt_regions), 2): + c, dc = tgt_regions[i:i+2] + h.update(src_file.read(c*tgt_blksize)) + src_file.seek(dc*tgt_blksize, 1) + + if h.hexdigest() != tgt_sha1: + raise ValueError("patch reconstructed incorrect target system image") + + print "test of system image patch succeeded" + + +class FileDifference: + def __init__(self, partition, source_zip, target_zip, output_zip): + print "Loading target..." + self.target_data = target_data = LoadPartitionFiles(target_zip, partition) + print "Loading source..." + self.source_data = source_data = LoadPartitionFiles(source_zip, partition) + + self.verbatim_targets = verbatim_targets = [] + self.patch_list = patch_list = [] + diffs = [] + self.renames = renames = {} + known_paths = set() + largest_source_size = 0 + + matching_file_cache = {} + for fn, sf in source_data.items(): + assert fn == sf.name + matching_file_cache["path:" + fn] = sf + if fn in target_data.keys(): + AddToKnownPaths(fn, known_paths) + # Only allow eligibility for filename/sha matching + # if there isn't a perfect path match. + if target_data.get(sf.name) is None: + matching_file_cache["file:" + fn.split("/")[-1]] = sf + matching_file_cache["sha:" + sf.sha1] = sf + + for fn in sorted(target_data.keys()): + tf = target_data[fn] + assert fn == tf.name + sf = ClosestFileMatch(tf, matching_file_cache, renames) + if sf is not None and sf.name != tf.name: + print "File has moved from " + sf.name + " to " + tf.name + renames[sf.name] = tf + + if sf is None or fn in OPTIONS.require_verbatim: + # This file should be included verbatim + if fn in OPTIONS.prohibit_verbatim: + raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) + print "send", fn, "verbatim" + tf.AddToZip(output_zip) + verbatim_targets.append((fn, tf.size)) + if fn in target_data.keys(): + AddToKnownPaths(fn, known_paths) + elif tf.sha1 != sf.sha1: + # File is different; consider sending as a patch + diffs.append(common.Difference(tf, sf)) + else: + # Target file data identical to source (may still be renamed) + pass + + common.ComputeDifferences(diffs) + + for diff in diffs: + tf, sf, d = diff.GetPatch() + path = "/".join(tf.name.split("/")[:-1]) + if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \ + path not in known_paths: + # patch is almost as big as the file; don't bother patching + # or a patch + rename cannot take place due to the target + # directory not existing + tf.AddToZip(output_zip) + verbatim_targets.append((tf.name, tf.size)) + if sf.name in renames: + del renames[sf.name] + AddToKnownPaths(tf.name, known_paths) + else: + common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d) + patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest())) + largest_source_size = max(largest_source_size, sf.size) + + self.largest_source_size = largest_source_size + + def EmitVerification(self, script): + so_far = 0 + for tf, sf, size, patch_sha in self.patch_list: + if tf.name != sf.name: + script.SkipNextActionIfTargetExists(tf.name, tf.sha1) + script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1) + so_far += sf.size + return so_far + + def RemoveUnneededFiles(self, script, extras=()): + script.DeleteFiles(["/"+i[0] for i in self.verbatim_targets] + + ["/"+i for i in sorted(self.source_data) + if i not in self.target_data and + i not in self.renames] + + list(extras)) + + def TotalPatchSize(self): + return sum(i[1].size for i in self.patch_list) + + def EmitPatches(self, script, total_patch_size, so_far): + self.deferred_patch_list = deferred_patch_list = [] + for item in self.patch_list: + tf, sf, size, _ = item + if tf.name == "system/build.prop": + deferred_patch_list.append(item) + continue + if (sf.name != tf.name): + script.SkipNextActionIfTargetExists(tf.name, tf.sha1) + script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") + so_far += tf.size + script.SetProgress(so_far / total_patch_size) + return so_far + + def EmitDeferredPatches(self, script): + for item in self.deferred_patch_list: + tf, sf, size, _ = item + script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") + script.SetPermissions("/system/build.prop", 0, 0, 0644, None, None) + + def EmitRenames(self, script): + if len(self.renames) > 0: + script.Print("Renaming files...") + for src, tgt in self.renames.iteritems(): + print "Renaming " + src + " to " + tgt.name + script.RenameFile(src, tgt.name) + + + + +def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): + target_has_recovery_patch = HasRecoveryPatch(target_zip) + source_has_recovery_patch = HasRecoveryPatch(source_zip) + + if (OPTIONS.block_based and + target_has_recovery_patch and + source_has_recovery_patch): + return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip) + + source_version = OPTIONS.source_info_dict["recovery_api_version"] + target_version = OPTIONS.target_info_dict["recovery_api_version"] + + if source_version == 0: + print ("WARNING: generating edify script for a source that " + "can't install it.") + script = edify_generator.EdifyGenerator(source_version, + OPTIONS.target_info_dict) + + oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") + oem_dict = None + if oem_props is not None: + if OPTIONS.oem_source is None: + raise common.ExternalError("OEM source required for this build") + script.Mount("/oem") + oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines()) + + metadata = {"pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, + OPTIONS.source_info_dict), + "post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.target_info_dict), + } + + device_specific = common.DeviceSpecificParams( + source_zip=source_zip, + source_version=source_version, + target_zip=target_zip, + target_version=target_version, + output_zip=output_zip, + script=script, + metadata=metadata, + info_dict=OPTIONS.info_dict) + + system_diff = FileDifference("system", source_zip, target_zip, output_zip) script.Mount("/system") - script.AssertSomeFingerprint(source_fp, target_fp) + if HasVendorPartition(target_zip): + vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip) + script.Mount("/vendor") + else: + vendor_diff = None + + target_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.target_info_dict) + source_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.source_info_dict) + + if oem_props is None: + script.AssertSomeFingerprint(source_fp, target_fp) + else: + script.AssertSomeThumbprint( + GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), + GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) + + metadata["pre-build"] = source_fp + metadata["post-build"] = target_fp source_boot = common.GetBootableImage( "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", @@ -700,7 +1153,7 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): # 0.1 for unpacking verbatim files, symlinking, and doing the # device-specific commands. - AppendAssertions(script, OPTIONS.target_info_dict) + AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) device_specific.IncrementalOTA_Assertions() # Two-step incremental package strategy (in chronological order, @@ -748,17 +1201,9 @@ else if get_stage("%(bcb_dev)s", "stage") != "3/3" then device_specific.IncrementalOTA_VerifyBegin() script.ShowProgress(0.1, 0) - total_verify_size = float(sum([i[1].size for i in patch_list]) + 1) - if updating_boot: - total_verify_size += source_boot.size - so_far = 0 - - for tf, sf, size, patch_sha in patch_list: - if tf.name != sf.name: - script.SkipNextActionIfTargetExists(tf.name, tf.sha1) - script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1) - so_far += sf.size - script.SetProgress(so_far / total_verify_size) + so_far = system_diff.EmitVerification(script) + if vendor_diff: + so_far += vendor_diff.EmitVerification(script) if updating_boot: d = common.Difference(target_boot, source_boot) @@ -775,10 +1220,13 @@ else if get_stage("%(bcb_dev)s", "stage") != "3/3" then source_boot.size, source_boot.sha1, target_boot.size, target_boot.sha1)) so_far += source_boot.size - script.SetProgress(so_far / total_verify_size) - if patch_list or updating_recovery or updating_boot: - script.CacheFreeSpaceCheck(largest_source_size) + size = [] + if system_diff.patch_list: size.append(system_diff.largest_source_size) + if vendor_diff: + if vendor_diff.patch_list: size.append(vendor_diff.largest_source_size) + if size or updating_recovery or updating_boot: + script.CacheFreeSpaceCheck(max(size)) device_specific.IncrementalOTA_VerifyEnd() @@ -799,35 +1247,23 @@ else script.WriteRawImage("/boot", "boot.img") print "writing full boot image (forced by two-step mode)" - if OPTIONS.wipe_user_data: - script.Print("Erasing user data...") - script.FormatPartition("/data") - script.Print("Removing unneeded files...") - script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + - ["/"+i for i in sorted(source_data) - if i not in target_data and - i not in renames] + - ["/system/recovery.img"]) + system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",)) + if vendor_diff: + vendor_diff.RemoveUnneededFiles(script) script.ShowProgress(0.8, 0) - total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) + total_patch_size = 1.0 + system_diff.TotalPatchSize() + if vendor_diff: + total_patch_size += vendor_diff.TotalPatchSize() if updating_boot: total_patch_size += target_boot.size - so_far = 0 script.Print("Patching system files...") - deferred_patch_list = [] - for item in patch_list: - tf, sf, size, _ = item - if tf.name == "system/build.prop": - deferred_patch_list.append(item) - continue - if (sf.name != tf.name): - script.SkipNextActionIfTargetExists(tf.name, tf.sha1) - script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") - so_far += tf.size - script.SetProgress(so_far / total_patch_size) + so_far = system_diff.EmitPatches(script, total_patch_size, 0) + if vendor_diff: + script.Print("Patching vendor files...") + so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far) if not OPTIONS.two_step: if updating_boot: @@ -848,6 +1284,10 @@ else else: print "boot image unchanged; skipping." + system_items = ItemSet("system", "META/filesystem_config.txt") + if vendor_diff: + vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") + if updating_recovery: # Recovery is generated as a patch using both the boot image # (which contains the same linux kernel as recovery) and the file @@ -858,26 +1298,39 @@ else # For older builds where recovery-resource.dat is not present, we # use only the boot image as the source. - MakeRecoveryPatch(OPTIONS.target_tmp, output_zip, - target_recovery, target_boot) - script.DeleteFiles(["/system/recovery-from-boot.p", - "/system/etc/install-recovery.sh"]) + if not target_has_recovery_patch: + def output_sink(fn, data): + common.ZipWriteStr(output_zip, "recovery/" + fn, data) + system_items.Get("system/" + fn, dir=False) + + common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink, + target_recovery, target_boot) + script.DeleteFiles(["/system/recovery-from-boot.p", + "/system/etc/install-recovery.sh"]) print "recovery image changed; including as patch from boot." else: print "recovery image unchanged; skipping." script.ShowProgress(0.1, 10) - target_symlinks = CopySystemFiles(target_zip, None) + target_symlinks = CopyPartitionFiles(system_items, target_zip, None) + if vendor_diff: + target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None)) - target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) temp_script = script.MakeTemporary() - Item.GetMetadata(target_zip) - Item.Get("system").SetPermissions(temp_script) + system_items.GetMetadata(target_zip) + system_items.Get("system").SetPermissions(temp_script) + if vendor_diff: + vendor_items.GetMetadata(target_zip) + vendor_items.Get("vendor").SetPermissions(temp_script) + + # Note that this call will mess up the trees of Items, so make sure + # we're done with them. + source_symlinks = CopyPartitionFiles(system_items, source_zip, None) + if vendor_diff: + source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None)) - # Note that this call will mess up the tree of Items, so make sure - # we're done with it. - source_symlinks = CopySystemFiles(source_zip, None) + target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) # Delete all the symlinks in source that aren't in target. This @@ -889,20 +1342,20 @@ else to_delete.append(link) script.DeleteFiles(to_delete) - if verbatim_targets: - script.Print("Unpacking new files...") + if system_diff.verbatim_targets: + script.Print("Unpacking new system files...") script.UnpackPackageDir("system", "/system") + if vendor_diff and vendor_diff.verbatim_targets: + script.Print("Unpacking new vendor files...") + script.UnpackPackageDir("vendor", "/vendor") - if updating_recovery: + if updating_recovery and not target_has_recovery_patch: script.Print("Unpacking new recovery...") script.UnpackPackageDir("recovery", "/system") - if len(renames) > 0: - script.Print("Renaming files...") - - for src in renames: - print "Renaming " + src + " to " + renames[src].name - script.RenameFile(src, renames[src].name) + system_diff.EmitRenames(script) + if vendor_diff: + vendor_diff.EmitRenames(script) script.Print("Symlinks and permissions...") @@ -933,10 +1386,11 @@ else # device can still come up, it appears to be the old build and will # get set the OTA package again to retry. script.Print("Patching remaining system files...") - for item in deferred_patch_list: - tf, sf, size, _ = item - script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") - script.SetPermissions("/system/build.prop", 0, 0, 0644, None, None) + system_diff.EmitDeferredPatches(script) + + if OPTIONS.wipe_user_data: + script.Print("Erasing user data...") + script.FormatPartition("/data") if OPTIONS.two_step: script.AppendExtra(""" @@ -945,14 +1399,14 @@ endif; endif; """ % bcb_dev) - script.AddToZip(target_zip, output_zip) + script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) WriteMetadata(metadata, output_zip) def main(argv): def option_handler(o, a): - if o in ("-b", "--board_config"): + if o == "--board_config": pass # deprecated elif o in ("-k", "--package_key"): OPTIONS.package_key = a @@ -962,6 +1416,8 @@ def main(argv): OPTIONS.wipe_user_data = True elif o in ("-n", "--no_prereq"): OPTIONS.omit_prereq = True + elif o in ("-o", "--oem_settings"): + OPTIONS.oem_source = a elif o in ("-e", "--extra_script"): OPTIONS.extra_script = a elif o in ("-a", "--aslr_mode"): @@ -977,14 +1433,18 @@ def main(argv): "integers are allowed." % (a, o)) elif o in ("-2", "--two_step"): OPTIONS.two_step = True - elif o in ("--no_signing"): + elif o == "--no_signing": OPTIONS.no_signing = True + elif o == "--block": + OPTIONS.block_based = True + elif o in ("-b", "--binary"): + OPTIONS.updater_binary = a else: return False return True args = common.ParseOptions(argv, __doc__, - extra_opts="b:k:i:d:wne:t:a:2", + extra_opts="b:k:i:d:wne:t:a:2o:", extra_long_opts=["board_config=", "package_key=", "incremental_from=", @@ -995,6 +1455,9 @@ def main(argv): "aslr_mode=", "two_step", "no_signing", + "block", + "binary=", + "oem_settings=", ], extra_option_handler=option_handler) @@ -1023,11 +1486,23 @@ def main(argv): print "--- target info ---" common.DumpInfoDict(OPTIONS.info_dict) + # 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 is None: - OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) + from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") + if os.path.exists(from_input): + print "(using device-specific extensions from target_files)" + OPTIONS.device_specific = from_input + else: + OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) + if OPTIONS.device_specific is not None: - OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) - print "using device-specific extensions in", OPTIONS.device_specific + OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) if OPTIONS.no_signing: output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED) @@ -1047,6 +1522,9 @@ def main(argv): OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) OPTIONS.target_info_dict = OPTIONS.info_dict OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) + if "selinux_fc" in OPTIONS.source_info_dict: + OPTIONS.source_info_dict["selinux_fc"] = os.path.join(OPTIONS.source_tmp, "BOOT", "RAMDISK", + "file_contexts") if OPTIONS.package_key is None: OPTIONS.package_key = OPTIONS.source_info_dict.get( "default_system_dev_certificate", |