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
This commit is contained in:
Doug Zongker 2014-02-04 12:17:58 -08:00
parent e858190fc4
commit c9253822ea
4 changed files with 170 additions and 72 deletions

View File

@ -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

View File

@ -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)

View File

@ -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:])

View File

@ -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")