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
diff --git a/core/Makefile b/core/Makefile
index 8bf7565..22539e5 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1288,6 +1288,7 @@
$(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 d0d57a9..b27e4c1 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -84,13 +84,25 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..765063a
--- /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 1174075..652052d 100755
--- a/tools/releasetools/ota_from_target_files
+++ b/tools/releasetools/ota_from_target_files
@@ -352,58 +352,12 @@
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 @@
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 @@
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 @@
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 @@
print "Loading source..."
source_data = LoadSystemFiles(source_zip)
+ target_has_recovery_patch = HasRecoveryPatch(target_zip)
+
verbatim_targets = []
patch_list = []
diffs = []
@@ -854,10 +820,15 @@
# 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 @@
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")