generate OTA packages that use edify

Split the details of generating script syntax into a generator class:
one for amend (whose output should be equivalent to the current
output), and one for edify.

Fix 'otatools' build rule to build imgdiff.
This commit is contained in:
Doug Zongker 2009-06-18 08:43:44 -07:00
parent 602a84e0bb
commit c494d7cee8
4 changed files with 563 additions and 125 deletions

View File

@ -754,6 +754,7 @@ otatools: $(HOST_OUT_EXECUTABLES)/minigzip \
$(HOST_OUT_EXECUTABLES)/zipalign \ $(HOST_OUT_EXECUTABLES)/zipalign \
$(HOST_OUT_EXECUTABLES)/aapt \ $(HOST_OUT_EXECUTABLES)/aapt \
$(HOST_OUT_EXECUTABLES)/bsdiff \ $(HOST_OUT_EXECUTABLES)/bsdiff \
$(HOST_OUT_EXECUTABLES)/imgdiff \
$(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar \ $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar \
$(HOST_OUT_JAVA_LIBRARIES)/signapk.jar $(HOST_OUT_JAVA_LIBRARIES)/signapk.jar

View File

@ -0,0 +1,205 @@
# Copyright (C) 2009 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 os
import common
class AmendGenerator(object):
"""Class to generate scripts in the 'amend' recovery script language
used up through cupcake."""
def __init__(self):
self.script = ['assert compatible_with("0.2") == "true"']
self.included_files = set()
def MakeTemporary(self):
"""Make a temporary script object whose commands can latter be
appended to the parent script with AppendScript(). Used when the
caller wants to generate script commands out-of-order."""
x = AmendGenerator()
x.script = []
x.included_files = self.included_files
return x
@staticmethod
def _FileRoot(fn):
"""Convert a file path to the 'root' notation used by amend."""
if fn.startswith("/system/"):
return "SYSTEM:" + fn[8:]
elif fn == "/system":
return "SYSTEM:"
elif fn.startswith("/tmp/"):
return "CACHE:.." + fn
else:
raise ValueError("don't know root for \"%s\"" % (fn,))
@staticmethod
def _PartitionRoot(partition):
"""Convert a partition name to the 'root' notation used by amend."""
if partition == "userdata":
return "DATA:"
else:
return partition.upper() + ":"
def AppendScript(self, other):
"""Append the contents of another script (which should be created
with temporary=True) to this one."""
self.script.extend(other.script)
self.included_files.update(other.included_files)
def AssertSomeFingerprint(self, *fp):
"""Assert that the current fingerprint is one of *fp."""
x = [('file_contains("SYSTEM:build.prop", '
'"ro.build.fingerprint=%s") == "true"') % i for i in fp]
self.script.append("assert %s" % (" || ".join(x),))
def AssertOlderBuild(self, timestamp):
"""Assert that the build on the device is older (or the same as)
the given timestamp."""
self.script.append("run_program PACKAGE:check_prereq %s" % (timestamp,))
self.included_files.add("check_prereq")
def AssertDevice(self, device):
"""Assert that the device identifier is the given string."""
self.script.append('assert getprop("ro.product.device") == "%s" || '
'getprop("ro.build.product") == "%s"' % (device, device))
def AssertSomeBootloader(self, *bootloaders):
"""Asert that the bootloader version is one of *bootloaders."""
self.script.append("assert " +
" || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
for b in bootloaders]))
def ShowProgress(self, frac, dur):
"""Update the progress bar, advancing it over 'frac' over the next
'dur' seconds."""
self.script.append("show_progress %f %d" % (frac, int(dur)))
def PatchCheck(self, filename, *sha1):
"""Check that the given file (or MTD reference) has one of the
given *sha1 hashes."""
out = ["run_program PACKAGE:applypatch -c %s" % (filename,)]
for i in sha1:
out.append(" " + i)
self.script.append("".join(out))
self.included_files.add("applypatch")
def CacheFreeSpaceCheck(self, amount):
"""Check that there's at least 'amount' space that can be made
available on /cache."""
self.script.append("run_program PACKAGE:applypatch -s %d" % (amount,))
self.included_files.add("applypatch")
def Mount(self, kind, what, path):
# no-op; amend uses it's 'roots' system to automatically mount
# things when they're referred to
pass
def UnpackPackageDir(self, src, dst):
"""Unpack a given directory from the OTA package into the given
destination directory."""
dst = self._FileRoot(dst)
self.script.append("copy_dir PACKAGE:%s %s" % (src, dst))
def Comment(self, comment):
"""Write a comment into the update script."""
self.script.append("")
for i in comment.split("\n"):
self.script.append("# " + i)
self.script.append("")
def Print(self, message):
"""Log a message to the screen (if the logs are visible)."""
# no way to do this from amend; substitute a script comment instead
self.Comment(message)
def FormatPartition(self, partition):
"""Format the given MTD partition."""
self.script.append("format %s" % (self._PartitionRoot(partition),))
def DeleteFiles(self, file_list):
"""Delete all files in file_list."""
line = []
t = 0
for i in file_list:
i = self._FileRoot(i)
line.append(i)
t += len(i) + 1
if t > 80:
self.script.append("delete " + " ".join(line))
line = []
t = 0
if line:
self.script.append("delete " + " ".join(line))
def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
"""Apply binary patches (in *patchpairs) to the given srcfile to
produce tgtfile (which may be "-" to indicate overwriting the
source file."""
if len(patchpairs) % 2 != 0:
raise ValueError("bad patches given to ApplyPatch")
self.script.append(
("run_program PACKAGE:applypatch %s %s %s %d " %
(srcfile, tgtfile, tgtsha1, tgtsize)) +
" ".join(["%s:%s" % patchpairs[i:i+2]
for i in range(0, len(patchpairs), 2)]))
self.included_files.add("applypatch")
def WriteFirmwareImage(self, kind, fn):
"""Arrange to update the given firmware image (kind must be
"hboot" or "radio") when recovery finishes."""
self.script.append("write_%s_image PACKAGE:%s" % (kind, fn))
def WriteRawImage(self, partition, fn):
"""Write the given file into the given MTD partition."""
self.script.append("write_raw_image PACKAGE:%s %s" %
(fn, self._PartitionRoot(partition)))
def SetPermissions(self, fn, uid, gid, mode):
"""Set file ownership and permissions."""
fn = self._FileRoot(fn)
self.script.append("set_perm %d %d 0%o %s" % (uid, gid, mode, fn))
def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
"""Recursively set path ownership and permissions."""
fn = self._FileRoot(fn)
self.script.append("set_perm_recursive %d %d 0%o 0%o %s" %
(uid, gid, dmode, fmode, fn))
def MakeSymlinks(self, symlink_list):
"""Create symlinks, given a list of (dest, link) pairs."""
self.script.extend(["symlink %s %s" % (i[0], self._FileRoot(i[1]))
for i in sorted(symlink_list)])
def AppendExtra(self, extra):
"""Append text verbatim to the output script."""
self.script.append(extra)
def AddToZip(self, input_zip, output_zip, input_path=None):
"""Write the accumulated script to the output_zip file. input_zip
is used as the source for any ancillary binaries needed by the
script. If input_path is not None, it will be used as a local
path for binaries instead of input_zip."""
common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-script",
"\n".join(self.script) + "\n")
for i in self.included_files:
try:
if input_path is None:
data = input_zip.read(os.path.join("OTA/bin", i))
else:
data = open(os.path.join(input_path, i)).read()
common.ZipWriteStr(output_zip, i, data, perms=0755)
except (IOError, KeyError), e:
raise ExternalError("unable to include binary %s: %s" % (i, e))

View File

@ -0,0 +1,226 @@
# Copyright (C) 2009 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 os
import re
import common
class EdifyGenerator(object):
"""Class to generate scripts in the 'edify' recovery script language
used from donut onwards."""
def __init__(self, version):
self.script = []
self.mounts = set()
self.version = version
def MakeTemporary(self):
"""Make a temporary script object whose commands can latter be
appended to the parent script with AppendScript(). Used when the
caller wants to generate script commands out-of-order."""
x = EdifyGenerator(self.version)
x.mounts = self.mounts
return x
@staticmethod
def _WordWrap(cmd, linelen=80):
"""'cmd' should be a function call with null characters after each
parameter (eg, "somefun(foo,\0bar,\0baz)"). This function wraps cmd
to a given line length, replacing nulls with spaces and/or newlines
to format it nicely."""
indent = cmd.index("(")+1
out = []
first = True
x = re.compile("^(.{,%d})\0" % (linelen-indent,))
while True:
if not first:
out.append(" " * indent)
first = False
m = x.search(cmd)
if not m:
parts = cmd.split("\0", 1)
out.append(parts[0]+"\n")
if len(parts) == 1:
break
else:
cmd = parts[1]
continue
out.append(m.group(1)+"\n")
cmd = cmd[m.end():]
return "".join(out).replace("\0", " ").rstrip("\n")
def AppendScript(self, other):
"""Append the contents of another script (which should be created
with temporary=True) to this one."""
self.script.extend(other.script)
def AssertSomeFingerprint(self, *fp):
"""Assert that the current system build fingerprint is one of *fp."""
if not fp:
raise ValueError("must specify some fingerprints")
cmd = ('assert(' +
' ||\0'.join([('file_getprop("/system/build.prop", '
'"ro.build.fingerprint") == "%s"')
% i for i in fp]) +
');')
self.script.append(self._WordWrap(cmd))
def AssertOlderBuild(self, timestamp):
"""Assert that the build on the device is older (or the same as)
the given timestamp."""
self.script.append(('assert(!less_than_int(%s, '
'getprop("ro.build.date.utc")));') % (timestamp,))
def AssertDevice(self, device):
"""Assert that the device identifier is the given string."""
cmd = ('assert(getprop("ro.product.device") == "%s" ||\0'
'getprop("ro.build.product") == "%s");' % (device, device))
self.script.append(self._WordWrap(cmd))
def AssertSomeBootloader(self, *bootloaders):
"""Asert that the bootloader version is one of *bootloaders."""
cmd = ("assert(" +
" ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,)
for b in bootloaders]) +
");")
self.script.append(self._WordWrap(cmd))
def ShowProgress(self, frac, dur):
"""Update the progress bar, advancing it over 'frac' over the next
'dur' seconds."""
self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
def PatchCheck(self, filename, *sha1):
"""Check that the given file (or MTD reference) has one of the
given *sha1 hashes."""
self.script.append('assert(apply_patch_check("%s"' % (filename,) +
"".join([', "%s"' % (i,) for i in sha1]) +
'));')
def CacheFreeSpaceCheck(self, amount):
"""Check that there's at least 'amount' space that can be made
available on /cache."""
self.script.append("assert(apply_patch_space(%d));" % (amount,))
def Mount(self, kind, what, path):
"""Mount the given 'what' at the given path. 'what' should be a
partition name if kind is "MTD", or a block device if kind is
"vfat". No other values of 'kind' are supported."""
self.script.append('mount("%s", "%s", "%s");' % (kind, what, path))
self.mounts.add(path)
def UnpackPackageDir(self, src, dst):
"""Unpack a given directory from the OTA package into the given
destination directory."""
self.script.append('package_extract_dir("%s", "%s");' % (src, dst))
def Comment(self, comment):
"""Write a comment into the update script."""
self.script.append("")
for i in comment.split("\n"):
self.script.append("# " + i)
self.script.append("")
def Print(self, message):
"""Log a message to the screen (if the logs are visible)."""
self.script.append('ui_print("%s");' % (message,))
def FormatPartition(self, partition):
"""Format the given MTD partition."""
self.script.append('format("MTD", "%s");' % (partition,))
def DeleteFiles(self, file_list):
"""Delete all files in file_list."""
if not file_list: return
cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");"
self.script.append(self._WordWrap(cmd))
def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
"""Apply binary patches (in *patchpairs) to the given srcfile to
produce tgtfile (which may be "-" to indicate overwriting the
source file."""
if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
raise ValueError("bad patches given to ApplyPatch")
cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
% (srcfile, tgtfile, tgtsha1, tgtsize)]
for i in range(0, len(patchpairs), 2):
cmd.append(',\0"%s:%s"' % patchpairs[i:i+2])
cmd.append(');')
cmd = "".join(cmd)
self.script.append(self._WordWrap(cmd))
def WriteFirmwareImage(self, kind, fn):
"""Arrange to update the given firmware image (kind must be
"hboot" or "radio") when recovery finishes."""
if self.version == 1:
self.script.append(
('assert(package_extract_file("%(fn)s", "/tmp/%(kind)s.img"),\n'
' write_firmware_image("/tmp/%(kind)s.img", "%(kind)s"));')
% {'kind': kind, 'fn': fn})
else:
self.script.append(
'write_firmware_image("PACKAGE:%s", "%s");' % (fn, kind))
def WriteRawImage(self, partition, fn):
"""Write the given package file into the given MTD partition."""
self.script.append(
('assert(package_extract_file("%(fn)s", "/tmp/%(partition)s.img"),\n'
' write_raw_image("/tmp/%(partition)s.img", "%(partition)s"),\n'
' delete("/tmp/%(partition)s.img"));')
% {'partition': partition, 'fn': fn})
def SetPermissions(self, fn, uid, gid, mode):
"""Set file ownership and permissions."""
self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
"""Recursively set path ownership and permissions."""
self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
% (uid, gid, dmode, fmode, fn))
def MakeSymlinks(self, symlink_list):
"""Create symlinks, given a list of (dest, link) pairs."""
by_dest = {}
for d, l in symlink_list:
by_dest.setdefault(d, []).append(l)
for dest, links in sorted(by_dest.iteritems()):
cmd = ('symlink("%s", ' % (dest,) +
",\0".join(['"' + i + '"' for i in sorted(links)]) + ");")
self.script.append(self._WordWrap(cmd))
def AppendExtra(self, extra):
"""Append text verbatim to the output script."""
self.script.append(extra)
def AddToZip(self, input_zip, output_zip, input_path=None):
"""Write the accumulated script to the output_zip file. input_zip
is used as the source for the 'updater' binary needed to run
script. If input_path is not None, it will be used as a local
path for the binary instead of input_zip."""
for p in sorted(self.mounts):
self.script.append('unmount("%s");' % (p,))
common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
"\n".join(self.script) + "\n")
if input_path is None:
data = input_zip.read("OTA/bin/updater")
else:
data = open(os.path.join(input_path, "updater")).read()
common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
data, perms=0755)

View File

@ -45,6 +45,10 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package
-e (--extra_script) <file> -e (--extra_script) <file>
Insert the contents of file at the end of the update script. Insert the contents of file at the end of the update script.
-m (--script_mode) <mode>
Specify 'amend' or 'edify' scripts, or 'auto' to pick
automatically (this is the default).
""" """
import sys import sys
@ -63,6 +67,8 @@ import time
import zipfile import zipfile
import common import common
import amend_generator
import edify_generator
OPTIONS = common.OPTIONS OPTIONS = common.OPTIONS
OPTIONS.package_key = "build/target/product/security/testkey" OPTIONS.package_key = "build/target/product/security/testkey"
@ -73,6 +79,7 @@ OPTIONS.patch_threshold = 0.95
OPTIONS.wipe_user_data = False OPTIONS.wipe_user_data = False
OPTIONS.omit_prereq = False OPTIONS.omit_prereq = False
OPTIONS.extra_script = None OPTIONS.extra_script = None
OPTIONS.script_mode = 'auto'
def MostPopularKey(d, default): def MostPopularKey(d, default):
"""Given a dict, return the key corresponding to the largest """Given a dict, return the key corresponding to the largest
@ -193,11 +200,10 @@ class Item:
return d return d
def SetPermissions(self, script, renamer=lambda x: x): def SetPermissions(self, script):
"""Append set_perm/set_perm_recursive commands to 'script' to """Append set_perm/set_perm_recursive commands to 'script' to
set all permissions, users, and groups for the tree of files set all permissions, users, and groups for the tree of files
rooted at 'self'. 'renamer' turns the filenames stored in the rooted at 'self'."""
tree of Items into the strings used in the script."""
self.CountChildMetadata() self.CountChildMetadata()
@ -208,22 +214,19 @@ class Item:
# supposed to be something different. # supposed to be something different.
if item.dir: if item.dir:
if current != item.best_subtree: if current != item.best_subtree:
script.append("set_perm_recursive %d %d 0%o 0%o %s" % script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
(item.best_subtree + (renamer(item.name),)))
current = item.best_subtree current = item.best_subtree
if item.uid != current[0] or item.gid != current[1] or \ if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[2]: item.mode != current[2]:
script.append("set_perm %d %d 0%o %s" % script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
(item.uid, item.gid, item.mode, renamer(item.name)))
for i in item.children: for i in item.children:
recurse(i, current) recurse(i, current)
else: else:
if item.uid != current[0] or item.gid != current[1] or \ if item.uid != current[0] or item.gid != current[1] or \
item.mode != current[3]: item.mode != current[3]:
script.append("set_perm %d %d 0%o %s" % script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
(item.uid, item.gid, item.mode, renamer(item.name)))
recurse(self, (-1, -1, -1, -1)) recurse(self, (-1, -1, -1, -1))
@ -245,7 +248,7 @@ def CopySystemFiles(input_zip, output_zip=None,
basefilename = info.filename[7:] basefilename = info.filename[7:]
if IsSymlink(info): if IsSymlink(info):
symlinks.append((input_zip.read(info.filename), symlinks.append((input_zip.read(info.filename),
"SYSTEM:" + basefilename)) "/system/" + basefilename))
else: else:
info2 = copy.copy(info) info2 = copy.copy(info)
fn = info2.filename = "system/" + basefilename fn = info2.filename = "system/" + basefilename
@ -266,11 +269,6 @@ def CopySystemFiles(input_zip, output_zip=None,
return symlinks return symlinks
def AddScript(script, output_zip):
common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-script",
"\n".join(script) + "\n")
def SignOutput(temp_zip_name, output_zip_name): def SignOutput(temp_zip_name, output_zip_name):
key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
pw = key_passwords[OPTIONS.package_key] pw = key_passwords[OPTIONS.package_key]
@ -278,35 +276,15 @@ def SignOutput(temp_zip_name, output_zip_name):
common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw) common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
def SubstituteRoot(s):
if s == "system": return "SYSTEM:"
assert s.startswith("system/")
return "SYSTEM:" + s[7:]
def FixPermissions(script): def FixPermissions(script):
Item.GetMetadata() Item.GetMetadata()
root = Item.Get("system") root = Item.Get("system")
root.SetPermissions(script, renamer=SubstituteRoot) root.SetPermissions(script)
def DeleteFiles(script, to_delete):
line = []
t = 0
for i in to_delete:
line.append(i)
t += len(i) + 1
if t > 80:
script.append("delete " + " ".join(line))
line = []
t = 0
if line:
script.append("delete " + " ".join(line))
def AppendAssertions(script, input_zip): def AppendAssertions(script, input_zip):
script.append('assert compatible_with("0.2") == "true"')
device = GetBuildProp("ro.product.device", input_zip) device = GetBuildProp("ro.product.device", input_zip)
script.append('assert getprop("ro.product.device") == "%s" || ' script.AssertDevice(device)
'getprop("ro.build.product") == "%s"' % (device, device))
info = input_zip.read("OTA/android-info.txt") info = input_zip.read("OTA/android-info.txt")
m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info) m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
@ -314,47 +292,39 @@ def AppendAssertions(script, input_zip):
raise ExternalError("failed to find required bootloaders in " raise ExternalError("failed to find required bootloaders in "
"android-info.txt") "android-info.txt")
bootloaders = m.group(1).split("|") bootloaders = m.group(1).split("|")
script.append("assert " + script.AssertSomeBootloader(*bootloaders)
" || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
for b in bootloaders]))
def IncludeBinary(name, input_zip, output_zip, input_path=None):
try:
if input_path is not None:
data = open(input_path).read()
else:
data = input_zip.read(os.path.join("OTA/bin", name))
common.ZipWriteStr(output_zip, name, data, perms=0755)
except IOError:
raise ExternalError('unable to include device binary "%s"' % (name,))
def WriteFullOTAPackage(input_zip, output_zip): def WriteFullOTAPackage(input_zip, output_zip):
script = [] if OPTIONS.script_mode in ("amend", "auto"):
script = amend_generator.AmendGenerator()
else:
# TODO: how to determine this? We don't know what version it will
# be installed on top of. For now, we expect the API just won't
# change very often.
script = edify_generator.EdifyGenerator(1)
if not OPTIONS.omit_prereq: if not OPTIONS.omit_prereq:
ts = GetBuildProp("ro.build.date.utc", input_zip) ts = GetBuildProp("ro.build.date.utc", input_zip)
script.append("run_program PACKAGE:check_prereq %s" % (ts,)) script.AssertOlderBuild(ts)
IncludeBinary("check_prereq", input_zip, output_zip)
AppendAssertions(script, input_zip) AppendAssertions(script, input_zip)
script.append("format BOOT:") script.ShowProgress(0.1, 0)
script.append("show_progress 0.1 0")
common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image")) common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
script.append("write_radio_image PACKAGE:radio.img") script.WriteFirmwareImage("radio", "radio.img")
script.append("show_progress 0.5 0") script.ShowProgress(0.5, 0)
if OPTIONS.wipe_user_data: if OPTIONS.wipe_user_data:
script.append("format DATA:") script.FormatPartition("userdata")
script.append("format SYSTEM:") script.FormatPartition("system")
script.append("copy_dir PACKAGE:system SYSTEM:") script.Mount("MTD", "system", "/system")
script.UnpackPackageDir("system", "/system")
symlinks = CopySystemFiles(input_zip, output_zip) symlinks = CopySystemFiles(input_zip, output_zip)
script.extend(["symlink %s %s" % s for s in symlinks]) script.MakeSymlinks(symlinks)
common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"), common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
"system/recovery.img", output_zip) "system/recovery.img", output_zip)
@ -363,14 +333,15 @@ def WriteFullOTAPackage(input_zip, output_zip):
FixPermissions(script) FixPermissions(script)
common.AddBoot(output_zip) common.AddBoot(output_zip)
script.append("show_progress 0.2 0") script.ShowProgress(0.2, 0)
script.append("write_raw_image PACKAGE:boot.img BOOT:")
script.append("show_progress 0.2 10") script.WriteRawImage("boot", "boot.img")
script.ShowProgress(0.2, 10)
if OPTIONS.extra_script is not None: if OPTIONS.extra_script is not None:
script.append(OPTIONS.extra_script) script.AppendExtra(OPTIONS.extra_script)
AddScript(script, output_zip) script.AddToZip(input_zip, output_zip)
class File(object): class File(object):
@ -448,8 +419,38 @@ def GetBuildProp(property, z):
return m.group(1).strip() return m.group(1).strip()
def GetRecoveryAPIVersion(zip):
"""Returns the version of the recovery API. Version 0 is the older
amend code (no separate binary)."""
try:
version = zip.read("META/recovery-api-version.txt")
return int(version)
except KeyError:
try:
# version one didn't have the recovery-api-version.txt file, but
# it did include an updater binary.
zip.getinfo("OTA/bin/updater")
return 1
except KeyError:
return 0
def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
script = [] source_version = GetRecoveryAPIVersion(source_zip)
if OPTIONS.script_mode == 'amend':
script = amend_generator.AmendGenerator()
elif OPTIONS.script_mode == 'edify':
if source_version == 0:
print ("WARNING: generating edify script for a source that "
"can't install it.")
script = edify_generator.EdifyGenerator(source_version)
elif OPTIONS.script_mode == 'auto':
if source_version > 0:
script = edify_generator.EdifyGenerator(source_version)
else:
script = amend_generator.AmendGenerator()
else:
raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
print "Loading target..." print "Loading target..."
target_data = LoadSystemFiles(target_zip) target_data = LoadSystemFiles(target_zip)
@ -496,11 +497,8 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
source_fp = GetBuildProp("ro.build.fingerprint", source_zip) source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
target_fp = GetBuildProp("ro.build.fingerprint", target_zip) target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
script.append(('assert file_contains("SYSTEM:build.prop", ' script.Mount("MTD", "system", "/system")
'"ro.build.fingerprint=%s") == "true" || ' script.AssertSomeFingerprint(source_fp, target_fp)
'file_contains("SYSTEM:build.prop", '
'"ro.build.fingerprint=%s") == "true"') %
(source_fp, target_fp))
source_boot = File("/tmp/boot.img", source_boot = File("/tmp/boot.img",
common.BuildBootableImage( common.BuildBootableImage(
@ -532,6 +530,8 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
AppendAssertions(script, target_zip) AppendAssertions(script, target_zip)
script.Print("Verifying current system...")
pb_verify = progress_bar_total * 0.3 * \ pb_verify = progress_bar_total * 0.3 * \
(total_patched_size / (total_patched_size /
float(total_patched_size+total_verbatim_size+1)) float(total_patched_size+total_verbatim_size+1))
@ -539,10 +539,9 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
for i, (fn, tf, sf, size) in enumerate(patch_list): for i, (fn, tf, sf, size) in enumerate(patch_list):
if i % 5 == 0: if i % 5 == 0:
next_sizes = sum([i[3] for i in patch_list[i:i+5]]) next_sizes = sum([i[3] for i in patch_list[i:i+5]])
script.append("show_progress %f 1" % script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
(next_sizes * pb_verify / (total_patched_size+1),))
script.append("run_program PACKAGE:applypatch -c /%s %s %s" % script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
(fn, tf.sha1, sf.sha1))
if updating_recovery: if updating_recovery:
d = Difference(target_recovery, source_recovery, "imgdiff") d = Difference(target_recovery, source_recovery, "imgdiff")
@ -551,10 +550,9 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
common.ZipWriteStr(output_zip, "patch/recovery.img.p", d) common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
script.append(("run_program PACKAGE:applypatch -c " script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
"MTD:recovery:%d:%s:%d:%s") % (source_recovery.size, source_recovery.sha1,
(source_recovery.size, source_recovery.sha1, target_recovery.size, target_recovery.sha1))
target_recovery.size, target_recovery.sha1))
if updating_boot: if updating_boot:
d = Difference(target_boot, source_boot, "imgdiff") d = Difference(target_boot, source_boot, "imgdiff")
@ -563,36 +561,35 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
common.ZipWriteStr(output_zip, "patch/boot.img.p", d) common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
script.append(("run_program PACKAGE:applypatch -c " script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
"MTD:boot:%d:%s:%d:%s") % (source_boot.size, source_boot.sha1,
(source_boot.size, source_boot.sha1, target_boot.size, target_boot.sha1))
target_boot.size, target_boot.sha1))
if patch_list or updating_recovery or updating_boot: if patch_list or updating_recovery or updating_boot:
script.append("run_program PACKAGE:applypatch -s %d" % script.CacheFreeSpaceCheck(largest_source_size)
(largest_source_size,)) script.Print("Unpacking patches...")
script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp") script.UnpackPackageDir("patch", "/tmp/patchtmp")
IncludeBinary("applypatch", target_zip, output_zip)
script.append("\n# ---- start making changes here\n") script.Comment("---- start making changes here ----")
if OPTIONS.wipe_user_data: if OPTIONS.wipe_user_data:
script.append("format DATA:") script.Print("Erasing user data...")
script.FormatPartition("userdata")
DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets]) script.Print("Removing unneeded files...")
script.DeleteFiles(["/"+i[0] for i in verbatim_targets])
if updating_boot: if updating_boot:
# Produce the boot image by applying a patch to the current # Produce the boot image by applying a patch to the current
# contents of the boot partition, and write it back to the # contents of the boot partition, and write it back to the
# partition. # partition.
script.append(("run_program PACKAGE:applypatch " script.Print("Patching boot image...")
"MTD:boot:%d:%s:%d:%s - " script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
"%s %d %s:/tmp/patchtmp/boot.img.p") % (source_boot.size, source_boot.sha1,
% (source_boot.size, source_boot.sha1, target_boot.size, target_boot.sha1),
target_boot.size, target_boot.sha1, "-",
target_boot.sha1, target_boot.size, target_boot.sha1,
target_boot.size, source_boot.sha1, "/tmp/patchtmp/boot.img.p")
source_boot.sha1))
print "boot image changed; including." print "boot image changed; including."
else: else:
print "boot image unchanged; skipping." print "boot image unchanged; skipping."
@ -600,41 +597,41 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
if updating_recovery: if updating_recovery:
# Produce /system/recovery.img by applying a patch to the current # Produce /system/recovery.img by applying a patch to the current
# contents of the recovery partition. # contents of the recovery partition.
script.append(("run_program PACKAGE:applypatch MTD:recovery:%d:%s:%d:%s " script.Print("Patching recovery image...")
"/system/recovery.img %s %d %s:/tmp/patchtmp/recovery.img.p") script.ApplyPatch("MTD:recovery:%d:%s:%d:%s"
% (source_recovery.size, source_recovery.sha1, % (source_recovery.size, source_recovery.sha1,
target_recovery.size, target_recovery.sha1, target_recovery.size, target_recovery.sha1),
target_recovery.sha1, "/system/recovery.img",
target_recovery.size, target_recovery.size, target_recovery.sha1,
source_recovery.sha1)) source_recovery.sha1, "/tmp/patchtmp/recovery.img.p")
print "recovery image changed; including." print "recovery image changed; including."
else: else:
print "recovery image unchanged; skipping." print "recovery image unchanged; skipping."
if updating_radio: if updating_radio:
script.append("show_progress 0.3 10") script.ShowProgress(0.3, 10)
script.append("write_radio_image PACKAGE:radio.img") script.Print("Writing radio image...")
script.WriteFirmwareImage("radio", "radio.img")
common.ZipWriteStr(output_zip, "radio.img", target_radio) common.ZipWriteStr(output_zip, "radio.img", target_radio)
print "radio image changed; including." print "radio image changed; including."
else: else:
print "radio image unchanged; skipping." print "radio image unchanged; skipping."
script.Print("Patching system files...")
pb_apply = progress_bar_total * 0.7 * \ pb_apply = progress_bar_total * 0.7 * \
(total_patched_size / (total_patched_size /
float(total_patched_size+total_verbatim_size+1)) float(total_patched_size+total_verbatim_size+1))
for i, (fn, tf, sf, size) in enumerate(patch_list): for i, (fn, tf, sf, size) in enumerate(patch_list):
if i % 5 == 0: if i % 5 == 0:
next_sizes = sum([i[3] for i in patch_list[i:i+5]]) next_sizes = sum([i[3] for i in patch_list[i:i+5]])
script.append("show_progress %f 1" % script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
(next_sizes * pb_apply / (total_patched_size+1),)) script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
script.append(("run_program PACKAGE:applypatch " sf.sha1, "/tmp/patchtmp/"+fn+".p")
"/%s - %s %d %s:/tmp/patchtmp/%s.p") %
(fn, tf.sha1, tf.size, sf.sha1, fn))
target_symlinks = CopySystemFiles(target_zip, None) target_symlinks = CopySystemFiles(target_zip, None)
target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
temp_script = [] temp_script = script.MakeTemporary()
FixPermissions(temp_script) FixPermissions(temp_script)
# Note that this call will mess up the tree of Items, so make sure # Note that this call will mess up the tree of Items, so make sure
@ -649,14 +646,17 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
for dest, link in source_symlinks: for dest, link in source_symlinks:
if link not in target_symlinks_d: if link not in target_symlinks_d:
to_delete.append(link) to_delete.append(link)
DeleteFiles(script, to_delete) script.DeleteFiles(to_delete)
if verbatim_targets: if verbatim_targets:
pb_verbatim = progress_bar_total * \ pb_verbatim = progress_bar_total * \
(total_verbatim_size / (total_verbatim_size /
float(total_patched_size+total_verbatim_size+1)) float(total_patched_size+total_verbatim_size+1))
script.append("show_progress %f 5" % (pb_verbatim,)) script.ShowProgress(pb_verbatim, 5)
script.append("copy_dir PACKAGE:system SYSTEM:") script.Print("Unpacking new files...")
script.UnpackPackageDir("system", "/system")
script.Print("Finishing up...")
# Create all the symlinks that don't already exist, or point to # Create all the symlinks that don't already exist, or point to
# somewhere different than what we want. Delete each symlink before # somewhere different than what we want. Delete each symlink before
@ -668,17 +668,17 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
to_create.append((dest, link)) to_create.append((dest, link))
else: else:
to_create.append((dest, link)) to_create.append((dest, link))
DeleteFiles(script, [i[1] for i in to_create]) script.DeleteFiles([i[1] for i in to_create])
script.extend(["symlink %s %s" % s for s in to_create]) script.MakeSymlinks(to_create)
# Now that the symlinks are created, we can set all the # Now that the symlinks are created, we can set all the
# permissions. # permissions.
script.extend(temp_script) script.AppendScript(temp_script)
if OPTIONS.extra_script is not None: if OPTIONS.extra_script is not None:
script.append(OPTIONS.extra_script) scirpt.AppendExtra(OPTIONS.extra_script)
AddScript(script, output_zip) script.AddToZip(target_zip, output_zip)
def main(argv): def main(argv):
@ -696,18 +696,21 @@ def main(argv):
OPTIONS.omit_prereq = True OPTIONS.omit_prereq = True
elif o in ("-e", "--extra_script"): elif o in ("-e", "--extra_script"):
OPTIONS.extra_script = a OPTIONS.extra_script = a
elif o in ("-m", "--script_mode"):
OPTIONS.script_mode = a
else: else:
return False return False
return True return True
args = common.ParseOptions(argv, __doc__, args = common.ParseOptions(argv, __doc__,
extra_opts="b:k:i:d:wne:", extra_opts="b:k:i:d:wne:m:",
extra_long_opts=["board_config=", extra_long_opts=["board_config=",
"package_key=", "package_key=",
"incremental_from=", "incremental_from=",
"wipe_user_data", "wipe_user_data",
"no_prereq", "no_prereq",
"extra_script="], "extra_script=",
"script_mode="],
extra_option_handler=option_handler) extra_option_handler=option_handler)
if len(args) != 2: if len(args) != 2:
@ -721,6 +724,9 @@ def main(argv):
print " images don't exceed partition sizes." print " images don't exceed partition sizes."
print print
if OPTIONS.script_mode not in ("amend", "edify", "auto"):
raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
if OPTIONS.extra_script is not None: if OPTIONS.extra_script is not None:
OPTIONS.extra_script = open(OPTIONS.extra_script).read() OPTIONS.extra_script = open(OPTIONS.extra_script).read()