From c9253822ea31c1d35d3fc2b495b45b476c240a1d Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Tue, 4 Feb 2014 12:17:58 -0800 Subject: [PATCH] add recovery update code to system images Currently, the "img" zip files generated by the build system lack the script and data needed to rewrite the recovery partition, while the "ota" zip files do (when installed). In order to move towards block-based OTAs, we want the result of flashing an image and the result of installing the corresponding OTA package to be identical. Generate the recovery-from-boot patch and install script as part of the process of building the target-files. This requires breaking the code to generate that out of ota_from_target_files into its own tool that we can run from the Makefile. (ota_from_target_files can still do this, so it continues to work with older target-files.) Bug: 12893978 Change-Id: I80e62268840780b81216e548be89b47baf81b4ac --- core/Makefile | 1 + tools/releasetools/common.py | 102 +++++++++++++++++++++++++++---- tools/releasetools/make_recovery_patch | 50 +++++++++++++++ tools/releasetools/ota_from_target_files | 89 +++++++++------------------ 4 files changed, 170 insertions(+), 72 deletions(-) create mode 100755 tools/releasetools/make_recovery_patch diff --git a/core/Makefile b/core/Makefile index 8bf7565a3..22539e586 100644 --- a/core/Makefile +++ b/core/Makefile @@ -1288,6 +1288,7 @@ endif $(hide) echo "multistage_support=1" >> $(zip_root)/META/misc_info.txt $(hide) echo "update_rename_support=1" >> $(zip_root)/META/misc_info.txt $(call generate-userimage-prop-dictionary, $(zip_root)/META/misc_info.txt) + $(hide) ./build/tools/releasetools/make_recovery_patch $(zip_root) $(zip_root) @# Zip everything up, preserving symlinks $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .) @# Run fs_config on all the system, boot ramdisk, and recovery ramdisk files in the zip, and save the output diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index d0d57a994..b27e4c153 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -84,13 +84,25 @@ def CloseInheritedPipes(): pass -def LoadInfoDict(zip): +def LoadInfoDict(input): """Read and parse the META/misc_info.txt key/value pairs from the input target files and return a dict.""" + def read_helper(fn): + if isinstance(input, zipfile.ZipFile): + return input.read(fn) + else: + path = os.path.join(input, *fn.split("/")) + try: + with open(path) as f: + return f.read() + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError(fn) + d = {} try: - for line in zip.read("META/misc_info.txt").split("\n"): + for line in read_helper("META/misc_info.txt").split("\n"): line = line.strip() if not line or line.startswith("#"): continue k, v = line.split("=", 1) @@ -105,20 +117,20 @@ def LoadInfoDict(zip): if "mkyaffs2_extra_flags" not in d: try: - d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip() + d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip() except KeyError: # ok if flags don't exist pass if "recovery_api_version" not in d: try: - d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip() + d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip() except KeyError: raise ValueError("can't find recovery API version in input target-files") if "tool_extensions" not in d: try: - d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip() + d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip() except KeyError: # ok if extensions don't exist pass @@ -127,7 +139,7 @@ def LoadInfoDict(zip): d["fstab_version"] = "1" try: - data = zip.read("META/imagesizes.txt") + data = read_helper("META/imagesizes.txt") for line in data.split("\n"): if not line: continue name, value = line.split(" ", 1) @@ -152,13 +164,13 @@ def LoadInfoDict(zip): makeint("boot_size") makeint("fstab_version") - d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"]) - d["build.prop"] = LoadBuildProp(zip) + d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"]) + d["build.prop"] = LoadBuildProp(read_helper) return d -def LoadBuildProp(zip): +def LoadBuildProp(read_helper): try: - data = zip.read("SYSTEM/build.prop") + data = read_helper("SYSTEM/build.prop") except KeyError: print "Warning: could not find SYSTEM/build.prop in %s" % zip data = "" @@ -171,14 +183,14 @@ def LoadBuildProp(zip): d[name] = value return d -def LoadRecoveryFSTab(zip, fstab_version): +def LoadRecoveryFSTab(read_helper, fstab_version): class Partition(object): pass try: - data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab") + data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab") except KeyError: - print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip + print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab" data = "" if fstab_version == 1: @@ -973,3 +985,67 @@ def ParseCertificate(data): save = True cert = "".join(cert).decode('base64') return cert + + +def MakeRecoveryPatch(input_dir, output_sink, 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. + + 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. + """ + + diff_program = ["imgdiff"] + path = os.path.join(input_dir, "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 = "" + + d = Difference(recovery_img, boot_img, diff_program=diff_program) + _, _, patch = d.ComputePatch() + output_sink("recovery-from-boot.p", patch) + + boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict) + recovery_type, recovery_device = GetTypeAndDevice("/recovery", OPTIONS.info_dict) + + sh = """#!/system/bin/sh +if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then + 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 && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed" +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, + } + + # The install script location moved from /system/etc to /system/bin + # in the L release. Parse the init.rc file to find out where the + # target-files expects it to be, and put it there. + sh_location = "etc/install-recovery.sh" + try: + with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f: + for line in f: + m = re.match("^service flash_recovery /system/(\S+)\s*$", line) + if m: + sh_location = m.group(1) + print "putting script in", sh_location + break + except (OSError, IOError), e: + print "failed to read init.rc: %s" % (e,) + + output_sink(sh_location, sh) diff --git a/tools/releasetools/make_recovery_patch b/tools/releasetools/make_recovery_patch new file mode 100755 index 000000000..765063afb --- /dev/null +++ b/tools/releasetools/make_recovery_patch @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright (C) 2014 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 sys + +if sys.hexversion < 0x02040000: + print >> sys.stderr, "Python 2.4 or newer is required." + sys.exit(1) + +import os +import common + +OPTIONS = common.OPTIONS + +def main(argv): + # def option_handler(o, a): + # return False + + args = common.ParseOptions(argv, __doc__) + input_dir, output_dir = args + + OPTIONS.info_dict = common.LoadInfoDict(input_dir) + + recovery_img = common.GetBootableImage("recovery.img", "recovery.img", + input_dir, "RECOVERY") + boot_img = common.GetBootableImage("boot.img", "boot.img", + input_dir, "BOOT") + + def output_sink(fn, data): + with open(os.path.join(output_dir, "SYSTEM", *fn.split("/")), "wb") as f: + f.write(data) + + common.MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files index 117407513..652052dbb 100755 --- a/tools/releasetools/ota_from_target_files +++ b/tools/releasetools/ota_from_target_files @@ -352,58 +352,12 @@ def AppendAssertions(script, 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. - - 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 = "" - - 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) - - boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) - recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.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 HasRecoveryPatch(target_files_zip): + try: + target_files_zip.getinfo("SYSTEM/recovery-from-boot.p") + return True + except KeyError: + return False def WriteFullOTAPackage(input_zip, output_zip): @@ -429,6 +383,8 @@ def WriteFullOTAPackage(input_zip, output_zip): metadata=metadata, info_dict=OPTIONS.info_dict) + has_recovery_patch = HasRecoveryPatch(input_zip) + if not OPTIONS.omit_prereq: ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) @@ -488,7 +444,8 @@ else if get_stage("%(bcb_dev)s", "stage") == "3/3" then script.FormatPartition("/system") script.Mount("/system") - script.UnpackPackageDir("recovery", "/system") + if not has_recovery_patch: + script.UnpackPackageDir("recovery", "/system") script.UnpackPackageDir("system", "/system") symlinks = CopySystemFiles(input_zip, output_zip) @@ -496,7 +453,14 @@ else if get_stage("%(bcb_dev)s", "stage") == "3/3" then boot_img = common.GetBootableImage("boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") - MakeRecoveryPatch(OPTIONS.input_tmp, output_zip, recovery_img, boot_img) + + if not has_recovery_patch: + def output_sink(fn, data): + common.ZipWriteStr(output_zip, "recovery/" + fn, data) + Item.Get("system/" + fn, dir=False) + + common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, + recovery_img, boot_img) Item.GetMetadata(input_zip) Item.Get("system").SetPermissions(script) @@ -604,6 +568,8 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): print "Loading source..." source_data = LoadSystemFiles(source_zip) + target_has_recovery_patch = HasRecoveryPatch(target_zip) + verbatim_targets = [] patch_list = [] diffs = [] @@ -854,10 +820,15 @@ 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) + Item.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." @@ -889,7 +860,7 @@ else script.Print("Unpacking new files...") script.UnpackPackageDir("system", "/system") - if updating_recovery: + if updating_recovery and not target_has_recovery_patch: script.Print("Unpacking new recovery...") script.UnpackPackageDir("recovery", "/system") -- 2.11.0