diff --git a/core/Makefile b/core/Makefile index 3e7826ecad..84a511a590 100644 --- a/core/Makefile +++ b/core/Makefile @@ -755,6 +755,7 @@ otatools: $(HOST_OUT_EXECUTABLES)/minigzip \ $(HOST_OUT_EXECUTABLES)/zipalign \ $(HOST_OUT_EXECUTABLES)/aapt \ $(HOST_OUT_EXECUTABLES)/bsdiff \ + $(HOST_OUT_EXECUTABLES)/imgdiff \ $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar \ $(HOST_OUT_JAVA_LIBRARIES)/signapk.jar diff --git a/tools/releasetools/amend_generator.py b/tools/releasetools/amend_generator.py new file mode 100644 index 0000000000..83415998c8 --- /dev/null +++ b/tools/releasetools/amend_generator.py @@ -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)) diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py new file mode 100644 index 0000000000..e7a15cd24b --- /dev/null +++ b/tools/releasetools/edify_generator.py @@ -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) diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files index 307ca4f4c3..11e6695719 100755 --- a/tools/releasetools/ota_from_target_files +++ b/tools/releasetools/ota_from_target_files @@ -45,6 +45,10 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package -e (--extra_script) Insert the contents of file at the end of the update script. + -m (--script_mode) + Specify 'amend' or 'edify' scripts, or 'auto' to pick + automatically (this is the default). + """ import sys @@ -63,6 +67,8 @@ import time import zipfile import common +import amend_generator +import edify_generator OPTIONS = common.OPTIONS OPTIONS.package_key = "build/target/product/security/testkey" @@ -73,6 +79,7 @@ OPTIONS.patch_threshold = 0.95 OPTIONS.wipe_user_data = False OPTIONS.omit_prereq = False OPTIONS.extra_script = None +OPTIONS.script_mode = 'auto' def MostPopularKey(d, default): """Given a dict, return the key corresponding to the largest @@ -193,11 +200,10 @@ class Item: return d - def SetPermissions(self, script, renamer=lambda x: x): + def SetPermissions(self, script): """Append set_perm/set_perm_recursive commands to 'script' to set all permissions, users, and groups for the tree of files - rooted at 'self'. 'renamer' turns the filenames stored in the - tree of Items into the strings used in the script.""" + rooted at 'self'.""" self.CountChildMetadata() @@ -208,22 +214,19 @@ class Item: # supposed to be something different. if item.dir: if current != item.best_subtree: - script.append("set_perm_recursive %d %d 0%o 0%o %s" % - (item.best_subtree + (renamer(item.name),))) + script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) current = item.best_subtree if item.uid != current[0] or item.gid != current[1] or \ item.mode != current[2]: - script.append("set_perm %d %d 0%o %s" % - (item.uid, item.gid, item.mode, renamer(item.name))) + script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) for i in item.children: recurse(i, current) else: if item.uid != current[0] or item.gid != current[1] or \ item.mode != current[3]: - script.append("set_perm %d %d 0%o %s" % - (item.uid, item.gid, item.mode, renamer(item.name))) + script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) recurse(self, (-1, -1, -1, -1)) @@ -245,7 +248,7 @@ def CopySystemFiles(input_zip, output_zip=None, basefilename = info.filename[7:] if IsSymlink(info): symlinks.append((input_zip.read(info.filename), - "SYSTEM:" + basefilename)) + "/system/" + basefilename)) else: info2 = copy.copy(info) fn = info2.filename = "system/" + basefilename @@ -266,11 +269,6 @@ def CopySystemFiles(input_zip, output_zip=None, 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): key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) pw = key_passwords[OPTIONS.package_key] @@ -278,85 +276,57 @@ def SignOutput(temp_zip_name, output_zip_name): 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): Item.GetMetadata() 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): - script.append('assert compatible_with("0.2") == "true"') - device = GetBuildProp("ro.product.device", input_zip) - script.append('assert getprop("ro.product.device") == "%s" || ' - 'getprop("ro.build.product") == "%s"' % (device, device)) + script.AssertDevice(device) info = input_zip.read("OTA/android-info.txt") m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info) if m: bootloaders = m.group(1).split("|") - script.append("assert " + - " || ".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,)) + script.AssertSomeBootloader(*bootloaders) 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: ts = GetBuildProp("ro.build.date.utc", input_zip) - script.append("run_program PACKAGE:check_prereq %s" % (ts,)) - IncludeBinary("check_prereq", input_zip, output_zip) + script.AssertOlderBuild(ts) AppendAssertions(script, input_zip) - script.append("format BOOT:") - script.append("show_progress 0.1 0") + script.ShowProgress(0.1, 0) try: common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image")) - script.append("write_radio_image PACKAGE:radio.img") + script.WriteFirmwareImage("radio", "radio.img") except KeyError: pass - script.append("show_progress 0.5 0") + script.ShowProgress(0.5, 0) if OPTIONS.wipe_user_data: - script.append("format DATA:") + script.FormatPartition("userdata") - script.append("format SYSTEM:") - script.append("copy_dir PACKAGE:system SYSTEM:") + script.FormatPartition("system") + script.Mount("MTD", "system", "/system") + script.UnpackPackageDir("system", "/system") 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"), "system/recovery.img", output_zip) @@ -365,14 +335,15 @@ def WriteFullOTAPackage(input_zip, output_zip): FixPermissions(script) common.AddBoot(output_zip) - script.append("show_progress 0.2 0") - script.append("write_raw_image PACKAGE:boot.img BOOT:") - script.append("show_progress 0.2 10") + script.ShowProgress(0.2, 0) + + script.WriteRawImage("boot", "boot.img") + script.ShowProgress(0.2, 10) 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): @@ -450,8 +421,38 @@ def GetBuildProp(property, z): 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): - 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..." target_data = LoadSystemFiles(target_zip) @@ -498,11 +499,8 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): source_fp = GetBuildProp("ro.build.fingerprint", source_zip) target_fp = GetBuildProp("ro.build.fingerprint", target_zip) - script.append(('assert file_contains("SYSTEM:build.prop", ' - '"ro.build.fingerprint=%s") == "true" || ' - 'file_contains("SYSTEM:build.prop", ' - '"ro.build.fingerprint=%s") == "true"') % - (source_fp, target_fp)) + script.Mount("MTD", "system", "/system") + script.AssertSomeFingerprint(source_fp, target_fp) source_boot = File("/tmp/boot.img", common.BuildBootableImage( @@ -534,6 +532,8 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): AppendAssertions(script, target_zip) + script.Print("Verifying current system...") + pb_verify = progress_bar_total * 0.3 * \ (total_patched_size / float(total_patched_size+total_verbatim_size+1)) @@ -541,10 +541,9 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): for i, (fn, tf, sf, size) in enumerate(patch_list): if i % 5 == 0: next_sizes = sum([i[3] for i in patch_list[i:i+5]]) - script.append("show_progress %f 1" % - (next_sizes * pb_verify / (total_patched_size+1),)) - script.append("run_program PACKAGE:applypatch -c /%s %s %s" % - (fn, tf.sha1, sf.sha1)) + script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1) + + script.PatchCheck("/"+fn, tf.sha1, sf.sha1) if updating_recovery: d = Difference(target_recovery, source_recovery, "imgdiff") @@ -553,10 +552,9 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): common.ZipWriteStr(output_zip, "patch/recovery.img.p", d) - script.append(("run_program PACKAGE:applypatch -c " - "MTD:recovery:%d:%s:%d:%s") % - (source_recovery.size, source_recovery.sha1, - target_recovery.size, target_recovery.sha1)) + script.PatchCheck("MTD:recovery:%d:%s:%d:%s" % + (source_recovery.size, source_recovery.sha1, + target_recovery.size, target_recovery.sha1)) if updating_boot: d = Difference(target_boot, source_boot, "imgdiff") @@ -565,36 +563,35 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): common.ZipWriteStr(output_zip, "patch/boot.img.p", d) - script.append(("run_program PACKAGE:applypatch -c " - "MTD:boot:%d:%s:%d:%s") % - (source_boot.size, source_boot.sha1, - target_boot.size, target_boot.sha1)) + script.PatchCheck("MTD:boot:%d:%s:%d:%s" % + (source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1)) if patch_list or updating_recovery or updating_boot: - script.append("run_program PACKAGE:applypatch -s %d" % - (largest_source_size,)) - script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp") - IncludeBinary("applypatch", target_zip, output_zip) + script.CacheFreeSpaceCheck(largest_source_size) + script.Print("Unpacking patches...") + script.UnpackPackageDir("patch", "/tmp/patchtmp") - script.append("\n# ---- start making changes here\n") + script.Comment("---- start making changes here ----") 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: # Produce the boot image by applying a patch to the current # contents of the boot partition, and write it back to the # partition. - script.append(("run_program PACKAGE:applypatch " - "MTD:boot:%d:%s:%d:%s - " - "%s %d %s:/tmp/patchtmp/boot.img.p") - % (source_boot.size, source_boot.sha1, - target_boot.size, target_boot.sha1, - target_boot.sha1, - target_boot.size, - source_boot.sha1)) + script.Print("Patching boot image...") + script.ApplyPatch("MTD:boot:%d:%s:%d:%s" + % (source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1), + "-", + target_boot.size, target_boot.sha1, + source_boot.sha1, "/tmp/patchtmp/boot.img.p") print "boot image changed; including." else: print "boot image unchanged; skipping." @@ -602,41 +599,41 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): if updating_recovery: # Produce /system/recovery.img by applying a patch to the current # contents of the recovery partition. - script.append(("run_program PACKAGE:applypatch MTD:recovery:%d:%s:%d:%s " - "/system/recovery.img %s %d %s:/tmp/patchtmp/recovery.img.p") - % (source_recovery.size, source_recovery.sha1, - target_recovery.size, target_recovery.sha1, - target_recovery.sha1, - target_recovery.size, - source_recovery.sha1)) + script.Print("Patching recovery image...") + script.ApplyPatch("MTD:recovery:%d:%s:%d:%s" + % (source_recovery.size, source_recovery.sha1, + target_recovery.size, target_recovery.sha1), + "/system/recovery.img", + target_recovery.size, target_recovery.sha1, + source_recovery.sha1, "/tmp/patchtmp/recovery.img.p") print "recovery image changed; including." else: print "recovery image unchanged; skipping." if updating_radio: - script.append("show_progress 0.3 10") - script.append("write_radio_image PACKAGE:radio.img") + script.ShowProgress(0.3, 10) + script.Print("Writing radio image...") + script.WriteFirmwareImage("radio", "radio.img") common.ZipWriteStr(output_zip, "radio.img", target_radio) print "radio image changed; including." else: print "radio image unchanged; skipping." + script.Print("Patching system files...") pb_apply = progress_bar_total * 0.7 * \ (total_patched_size / float(total_patched_size+total_verbatim_size+1)) for i, (fn, tf, sf, size) in enumerate(patch_list): if i % 5 == 0: next_sizes = sum([i[3] for i in patch_list[i:i+5]]) - script.append("show_progress %f 1" % - (next_sizes * pb_apply / (total_patched_size+1),)) - script.append(("run_program PACKAGE:applypatch " - "/%s - %s %d %s:/tmp/patchtmp/%s.p") % - (fn, tf.sha1, tf.size, sf.sha1, fn)) + script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1) + script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, + sf.sha1, "/tmp/patchtmp/"+fn+".p") target_symlinks = CopySystemFiles(target_zip, None) target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) - temp_script = [] + temp_script = script.MakeTemporary() FixPermissions(temp_script) # Note that this call will mess up the tree of Items, so make sure @@ -651,14 +648,17 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): for dest, link in source_symlinks: if link not in target_symlinks_d: to_delete.append(link) - DeleteFiles(script, to_delete) + script.DeleteFiles(to_delete) if verbatim_targets: pb_verbatim = progress_bar_total * \ (total_verbatim_size / float(total_patched_size+total_verbatim_size+1)) - script.append("show_progress %f 5" % (pb_verbatim,)) - script.append("copy_dir PACKAGE:system SYSTEM:") + script.ShowProgress(pb_verbatim, 5) + 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 # somewhere different than what we want. Delete each symlink before @@ -670,17 +670,17 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): to_create.append((dest, link)) else: to_create.append((dest, link)) - DeleteFiles(script, [i[1] for i in to_create]) - script.extend(["symlink %s %s" % s for s in to_create]) + script.DeleteFiles([i[1] for i in to_create]) + script.MakeSymlinks(to_create) # Now that the symlinks are created, we can set all the # permissions. - script.extend(temp_script) + script.AppendScript(temp_script) 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): @@ -698,18 +698,21 @@ def main(argv): OPTIONS.omit_prereq = True elif o in ("-e", "--extra_script"): OPTIONS.extra_script = a + elif o in ("-m", "--script_mode"): + OPTIONS.script_mode = a else: return False return True 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=", "package_key=", "incremental_from=", "wipe_user_data", "no_prereq", - "extra_script="], + "extra_script=", + "script_mode="], extra_option_handler=option_handler) if len(args) != 2: @@ -723,6 +726,9 @@ def main(argv): print " images don't exceed partition sizes." 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: OPTIONS.extra_script = open(OPTIONS.extra_script).read()