Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2008 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Given a target-files zipfile, produces an OTA package that installs
     19 that build.  An incremental OTA is produced if -i is given, otherwise
     20 a full OTA is produced.
     21 
     22 Usage:  ota_from_target_files [flags] input_target_files output_ota_package
     23 
     24   -b  (--board_config)  <file>
     25       Deprecated.
     26 
     27   -k (--package_key) <key> Key to use to sign the package (default is
     28       the value of default_system_dev_certificate from the input
     29       target-files's META/misc_info.txt, or
     30       "build/target/product/security/testkey" if that value is not
     31       specified).
     32 
     33       For incremental OTAs, the default value is based on the source
     34       target-file, not the target build.
     35 
     36   -i  (--incremental_from)  <file>
     37       Generate an incremental OTA using the given target-files zip as
     38       the starting build.
     39 
     40   -w  (--wipe_user_data)
     41       Generate an OTA package that will wipe the user data partition
     42       when installed.
     43 
     44   -n  (--no_prereq)
     45       Omit the timestamp prereq check normally included at the top of
     46       the build scripts (used for developer OTA packages which
     47       legitimately need to go back and forth).
     48 
     49   -e  (--extra_script)  <file>
     50       Insert the contents of file at the end of the update script.
     51 
     52   -a  (--aslr_mode)  <on|off>
     53       Specify whether to turn on ASLR for the package (on by default).
     54 """
     55 
     56 import sys
     57 
     58 if sys.hexversion < 0x02040000:
     59   print >> sys.stderr, "Python 2.4 or newer is required."
     60   sys.exit(1)
     61 
     62 import copy
     63 import errno
     64 import os
     65 import re
     66 import subprocess
     67 import tempfile
     68 import time
     69 import zipfile
     70 
     71 try:
     72   from hashlib import sha1 as sha1
     73 except ImportError:
     74   from sha import sha as sha1
     75 
     76 import common
     77 import edify_generator
     78 
     79 OPTIONS = common.OPTIONS
     80 OPTIONS.package_key = None
     81 OPTIONS.incremental_source = None
     82 OPTIONS.require_verbatim = set()
     83 OPTIONS.prohibit_verbatim = set(("system/build.prop",))
     84 OPTIONS.patch_threshold = 0.95
     85 OPTIONS.wipe_user_data = False
     86 OPTIONS.omit_prereq = False
     87 OPTIONS.extra_script = None
     88 OPTIONS.aslr_mode = True
     89 OPTIONS.worker_threads = 3
     90 
     91 def MostPopularKey(d, default):
     92   """Given a dict, return the key corresponding to the largest
     93   value.  Returns 'default' if the dict is empty."""
     94   x = [(v, k) for (k, v) in d.iteritems()]
     95   if not x: return default
     96   x.sort()
     97   return x[-1][1]
     98 
     99 
    100 def IsSymlink(info):
    101   """Return true if the zipfile.ZipInfo object passed in represents a
    102   symlink."""
    103   return (info.external_attr >> 16) == 0120777
    104 
    105 def IsRegular(info):
    106   """Return true if the zipfile.ZipInfo object passed in represents a
    107   symlink."""
    108   return (info.external_attr >> 28) == 010
    109 
    110 class Item:
    111   """Items represent the metadata (user, group, mode) of files and
    112   directories in the system image."""
    113   ITEMS = {}
    114   def __init__(self, name, dir=False):
    115     self.name = name
    116     self.uid = None
    117     self.gid = None
    118     self.mode = None
    119     self.dir = dir
    120 
    121     if name:
    122       self.parent = Item.Get(os.path.dirname(name), dir=True)
    123       self.parent.children.append(self)
    124     else:
    125       self.parent = None
    126     if dir:
    127       self.children = []
    128 
    129   def Dump(self, indent=0):
    130     if self.uid is not None:
    131       print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
    132     else:
    133       print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
    134     if self.dir:
    135       print "%s%s" % ("  "*indent, self.descendants)
    136       print "%s%s" % ("  "*indent, self.best_subtree)
    137       for i in self.children:
    138         i.Dump(indent=indent+1)
    139 
    140   @classmethod
    141   def Get(cls, name, dir=False):
    142     if name not in cls.ITEMS:
    143       cls.ITEMS[name] = Item(name, dir=dir)
    144     return cls.ITEMS[name]
    145 
    146   @classmethod
    147   def GetMetadata(cls, input_zip):
    148 
    149     try:
    150       # See if the target_files contains a record of what the uid,
    151       # gid, and mode is supposed to be.
    152       output = input_zip.read("META/filesystem_config.txt")
    153     except KeyError:
    154       # Run the external 'fs_config' program to determine the desired
    155       # uid, gid, and mode for every Item object.  Note this uses the
    156       # one in the client now, which might not be the same as the one
    157       # used when this target_files was built.
    158       p = common.Run(["fs_config"], stdin=subprocess.PIPE,
    159                      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    160       suffix = { False: "", True: "/" }
    161       input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
    162                        for i in cls.ITEMS.itervalues() if i.name])
    163       output, error = p.communicate(input)
    164       assert not error
    165 
    166     for line in output.split("\n"):
    167       if not line: continue
    168       name, uid, gid, mode = line.split()
    169       i = cls.ITEMS.get(name, None)
    170       if i is not None:
    171         i.uid = int(uid)
    172         i.gid = int(gid)
    173         i.mode = int(mode, 8)
    174         if i.dir:
    175           i.children.sort(key=lambda i: i.name)
    176 
    177     # set metadata for the files generated by this script.
    178     i = cls.ITEMS.get("system/recovery-from-boot.p", None)
    179     if i: i.uid, i.gid, i.mode = 0, 0, 0644
    180     i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
    181     if i: i.uid, i.gid, i.mode = 0, 0, 0544
    182 
    183   def CountChildMetadata(self):
    184     """Count up the (uid, gid, mode) tuples for all children and
    185     determine the best strategy for using set_perm_recursive and
    186     set_perm to correctly chown/chmod all the files to their desired
    187     values.  Recursively calls itself for all descendants.
    188 
    189     Returns a dict of {(uid, gid, dmode, fmode): count} counting up
    190     all descendants of this node.  (dmode or fmode may be None.)  Also
    191     sets the best_subtree of each directory Item to the (uid, gid,
    192     dmode, fmode) tuple that will match the most descendants of that
    193     Item.
    194     """
    195 
    196     assert self.dir
    197     d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
    198     for i in self.children:
    199       if i.dir:
    200         for k, v in i.CountChildMetadata().iteritems():
    201           d[k] = d.get(k, 0) + v
    202       else:
    203         k = (i.uid, i.gid, None, i.mode)
    204         d[k] = d.get(k, 0) + 1
    205 
    206     # Find the (uid, gid, dmode, fmode) tuple that matches the most
    207     # descendants.
    208 
    209     # First, find the (uid, gid) pair that matches the most
    210     # descendants.
    211     ug = {}
    212     for (uid, gid, _, _), count in d.iteritems():
    213       ug[(uid, gid)] = ug.get((uid, gid), 0) + count
    214     ug = MostPopularKey(ug, (0, 0))
    215 
    216     # Now find the dmode and fmode that match the most descendants
    217     # with that (uid, gid), and choose those.
    218     best_dmode = (0, 0755)
    219     best_fmode = (0, 0644)
    220     for k, count in d.iteritems():
    221       if k[:2] != ug: continue
    222       if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
    223       if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
    224     self.best_subtree = ug + (best_dmode[1], best_fmode[1])
    225 
    226     return d
    227 
    228   def SetPermissions(self, script):
    229     """Append set_perm/set_perm_recursive commands to 'script' to
    230     set all permissions, users, and groups for the tree of files
    231     rooted at 'self'."""
    232 
    233     self.CountChildMetadata()
    234 
    235     def recurse(item, current):
    236       # current is the (uid, gid, dmode, fmode) tuple that the current
    237       # item (and all its children) have already been set to.  We only
    238       # need to issue set_perm/set_perm_recursive commands if we're
    239       # supposed to be something different.
    240       if item.dir:
    241         if current != item.best_subtree:
    242           script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
    243           current = item.best_subtree
    244 
    245         if item.uid != current[0] or item.gid != current[1] or \
    246            item.mode != current[2]:
    247           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
    248 
    249         for i in item.children:
    250           recurse(i, current)
    251       else:
    252         if item.uid != current[0] or item.gid != current[1] or \
    253                item.mode != current[3]:
    254           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
    255 
    256     recurse(self, (-1, -1, -1, -1))
    257 
    258 
    259 def CopySystemFiles(input_zip, output_zip=None,
    260                     substitute=None):
    261   """Copies files underneath system/ in the input zip to the output
    262   zip.  Populates the Item class with their metadata, and returns a
    263   list of symlinks as well as a list of files that will be retouched.
    264   output_zip may be None, in which case the copy is skipped (but the
    265   other side effects still happen).  substitute is an optional dict
    266   of {output filename: contents} to be output instead of certain input
    267   files.
    268   """
    269 
    270   symlinks = []
    271   retouch_files = []
    272 
    273   for info in input_zip.infolist():
    274     if info.filename.startswith("SYSTEM/"):
    275       basefilename = info.filename[7:]
    276       if IsSymlink(info):
    277         symlinks.append((input_zip.read(info.filename),
    278                          "/system/" + basefilename))
    279       else:
    280         info2 = copy.copy(info)
    281         fn = info2.filename = "system/" + basefilename
    282         if substitute and fn in substitute and substitute[fn] is None:
    283           continue
    284         if output_zip is not None:
    285           if substitute and fn in substitute:
    286             data = substitute[fn]
    287           else:
    288             data = input_zip.read(info.filename)
    289           if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
    290             retouch_files.append(("/system/" + basefilename,
    291                                   common.sha1(data).hexdigest()))
    292           output_zip.writestr(info2, data)
    293         if fn.endswith("/"):
    294           Item.Get(fn[:-1], dir=True)
    295         else:
    296           Item.Get(fn, dir=False)
    297 
    298   symlinks.sort()
    299   return (symlinks, retouch_files)
    300 
    301 
    302 def SignOutput(temp_zip_name, output_zip_name):
    303   key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
    304   pw = key_passwords[OPTIONS.package_key]
    305 
    306   common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
    307                   whole_file=True)
    308 
    309 
    310 def AppendAssertions(script, input_zip):
    311   device = GetBuildProp("ro.product.device", input_zip)
    312   script.AssertDevice(device)
    313 
    314 
    315 def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
    316   """Generate a binary patch that creates the recovery image starting
    317   with the boot image.  (Most of the space in these images is just the
    318   kernel, which is identical for the two, so the resulting patch
    319   should be efficient.)  Add it to the output zip, along with a shell
    320   script that is run from init.rc on first boot to actually do the
    321   patching and install the new recovery image.
    322 
    323   recovery_img and boot_img should be File objects for the
    324   corresponding images.  info should be the dictionary returned by
    325   common.LoadInfoDict() on the input target_files.
    326 
    327   Returns an Item for the shell script, which must be made
    328   executable.
    329   """
    330 
    331   d = common.Difference(recovery_img, boot_img)
    332   _, _, patch = d.ComputePatch()
    333   common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
    334   Item.Get("system/recovery-from-boot.p", dir=False)
    335 
    336   boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
    337   recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
    338 
    339   sh = """#!/system/bin/sh
    340 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
    341   log -t recovery "Installing new recovery image"
    342   applypatch %(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
    343 else
    344   log -t recovery "Recovery image already installed"
    345 fi
    346 """ % { 'boot_size': boot_img.size,
    347         'boot_sha1': boot_img.sha1,
    348         'recovery_size': recovery_img.size,
    349         'recovery_sha1': recovery_img.sha1,
    350         'boot_type': boot_type,
    351         'boot_device': boot_device,
    352         'recovery_type': recovery_type,
    353         'recovery_device': recovery_device,
    354         }
    355   common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
    356   return Item.Get("system/etc/install-recovery.sh", dir=False)
    357 
    358 
    359 def WriteFullOTAPackage(input_zip, output_zip):
    360   # TODO: how to determine this?  We don't know what version it will
    361   # be installed on top of.  For now, we expect the API just won't
    362   # change very often.
    363   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
    364 
    365   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
    366               "pre-device": GetBuildProp("ro.product.device", input_zip),
    367               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
    368               }
    369 
    370   device_specific = common.DeviceSpecificParams(
    371       input_zip=input_zip,
    372       input_version=OPTIONS.info_dict["recovery_api_version"],
    373       output_zip=output_zip,
    374       script=script,
    375       input_tmp=OPTIONS.input_tmp,
    376       metadata=metadata,
    377       info_dict=OPTIONS.info_dict)
    378 
    379   if not OPTIONS.omit_prereq:
    380     ts = GetBuildProp("ro.build.date.utc", input_zip)
    381     script.AssertOlderBuild(ts)
    382 
    383   AppendAssertions(script, input_zip)
    384   device_specific.FullOTA_Assertions()
    385 
    386   script.ShowProgress(0.5, 0)
    387 
    388   if OPTIONS.wipe_user_data:
    389     script.FormatPartition("/data")
    390 
    391   script.FormatPartition("/system")
    392   script.Mount("/system")
    393   script.UnpackPackageDir("recovery", "/system")
    394   script.UnpackPackageDir("system", "/system")
    395 
    396   (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
    397   script.MakeSymlinks(symlinks)
    398   if OPTIONS.aslr_mode:
    399     script.RetouchBinaries(retouch_files)
    400   else:
    401     script.UndoRetouchBinaries(retouch_files)
    402 
    403   boot_img = common.GetBootableImage("boot.img", "boot.img",
    404                                      OPTIONS.input_tmp, "BOOT")
    405   recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
    406                                          OPTIONS.input_tmp, "RECOVERY")
    407   MakeRecoveryPatch(output_zip, recovery_img, boot_img)
    408 
    409   Item.GetMetadata(input_zip)
    410   Item.Get("system").SetPermissions(script)
    411 
    412   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
    413   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
    414   script.ShowProgress(0.2, 0)
    415 
    416   script.ShowProgress(0.2, 10)
    417   script.WriteRawImage("/boot", "boot.img")
    418 
    419   script.ShowProgress(0.1, 0)
    420   device_specific.FullOTA_InstallEnd()
    421 
    422   if OPTIONS.extra_script is not None:
    423     script.AppendExtra(OPTIONS.extra_script)
    424 
    425   script.UnmountAll()
    426   script.AddToZip(input_zip, output_zip)
    427   WriteMetadata(metadata, output_zip)
    428 
    429 
    430 def WriteMetadata(metadata, output_zip):
    431   common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
    432                      "".join(["%s=%s\n" % kv
    433                               for kv in sorted(metadata.iteritems())]))
    434 
    435 
    436 
    437 
    438 def LoadSystemFiles(z):
    439   """Load all the files from SYSTEM/... in a given target-files
    440   ZipFile, and return a dict of {filename: File object}."""
    441   out = {}
    442   retouch_files = []
    443   for info in z.infolist():
    444     if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
    445       basefilename = info.filename[7:]
    446       fn = "system/" + basefilename
    447       data = z.read(info.filename)
    448       out[fn] = common.File(fn, data)
    449       if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
    450         retouch_files.append(("/system/" + basefilename,
    451                               out[fn].sha1))
    452   return (out, retouch_files)
    453 
    454 
    455 def GetBuildProp(property, z):
    456   """Return the fingerprint of the build of a given target-files
    457   ZipFile object."""
    458   bp = z.read("SYSTEM/build.prop")
    459   if not property:
    460     return bp
    461   m = re.search(re.escape(property) + r"=(.*)\n", bp)
    462   if not m:
    463     raise common.ExternalError("couldn't find %s in build.prop" % (property,))
    464   return m.group(1).strip()
    465 
    466 
    467 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
    468   source_version = OPTIONS.source_info_dict["recovery_api_version"]
    469   target_version = OPTIONS.target_info_dict["recovery_api_version"]
    470 
    471   if source_version == 0:
    472     print ("WARNING: generating edify script for a source that "
    473            "can't install it.")
    474   script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict)
    475 
    476   metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
    477               "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
    478               }
    479 
    480   device_specific = common.DeviceSpecificParams(
    481       source_zip=source_zip,
    482       source_version=source_version,
    483       target_zip=target_zip,
    484       target_version=target_version,
    485       output_zip=output_zip,
    486       script=script,
    487       metadata=metadata,
    488       info_dict=OPTIONS.info_dict)
    489 
    490   print "Loading target..."
    491   (target_data, target_retouch_files) = LoadSystemFiles(target_zip)
    492   print "Loading source..."
    493   (source_data, source_retouch_files) = LoadSystemFiles(source_zip)
    494 
    495   verbatim_targets = []
    496   patch_list = []
    497   diffs = []
    498   largest_source_size = 0
    499   for fn in sorted(target_data.keys()):
    500     tf = target_data[fn]
    501     assert fn == tf.name
    502     sf = source_data.get(fn, None)
    503 
    504     if sf is None or fn in OPTIONS.require_verbatim:
    505       # This file should be included verbatim
    506       if fn in OPTIONS.prohibit_verbatim:
    507         raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
    508       print "send", fn, "verbatim"
    509       tf.AddToZip(output_zip)
    510       verbatim_targets.append((fn, tf.size))
    511     elif tf.sha1 != sf.sha1:
    512       # File is different; consider sending as a patch
    513       diffs.append(common.Difference(tf, sf))
    514     else:
    515       # Target file identical to source.
    516       pass
    517 
    518   common.ComputeDifferences(diffs)
    519 
    520   for diff in diffs:
    521     tf, sf, d = diff.GetPatch()
    522     if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
    523       # patch is almost as big as the file; don't bother patching
    524       tf.AddToZip(output_zip)
    525       verbatim_targets.append((tf.name, tf.size))
    526     else:
    527       common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
    528       patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest()))
    529       largest_source_size = max(largest_source_size, sf.size)
    530 
    531   source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
    532   target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
    533   metadata["pre-build"] = source_fp
    534   metadata["post-build"] = target_fp
    535 
    536   script.Mount("/system")
    537   script.AssertSomeFingerprint(source_fp, target_fp)
    538 
    539   source_boot = common.GetBootableImage(
    540       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT")
    541   target_boot = common.GetBootableImage(
    542       "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
    543   updating_boot = (source_boot.data != target_boot.data)
    544 
    545   source_recovery = common.GetBootableImage(
    546       "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY")
    547   target_recovery = common.GetBootableImage(
    548       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
    549   updating_recovery = (source_recovery.data != target_recovery.data)
    550 
    551   # Here's how we divide up the progress bar:
    552   #  0.1 for verifying the start state (PatchCheck calls)
    553   #  0.8 for applying patches (ApplyPatch calls)
    554   #  0.1 for unpacking verbatim files, symlinking, and doing the
    555   #      device-specific commands.
    556 
    557   AppendAssertions(script, target_zip)
    558   device_specific.IncrementalOTA_Assertions()
    559 
    560   script.Print("Verifying current system...")
    561 
    562   script.ShowProgress(0.1, 0)
    563   total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
    564   if updating_boot:
    565     total_verify_size += source_boot.size
    566   so_far = 0
    567 
    568   for fn, tf, sf, size, patch_sha in patch_list:
    569     script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
    570     so_far += sf.size
    571     script.SetProgress(so_far / total_verify_size)
    572 
    573   if updating_boot:
    574     d = common.Difference(target_boot, source_boot)
    575     _, _, d = d.ComputePatch()
    576     print "boot      target: %d  source: %d  diff: %d" % (
    577         target_boot.size, source_boot.size, len(d))
    578 
    579     common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
    580 
    581     boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
    582 
    583     script.PatchCheck("%s:%s:%d:%s:%d:%s" %
    584                       (boot_type, boot_device,
    585                        source_boot.size, source_boot.sha1,
    586                        target_boot.size, target_boot.sha1))
    587     so_far += source_boot.size
    588     script.SetProgress(so_far / total_verify_size)
    589 
    590   if patch_list or updating_recovery or updating_boot:
    591     script.CacheFreeSpaceCheck(largest_source_size)
    592 
    593   device_specific.IncrementalOTA_VerifyEnd()
    594 
    595   script.Comment("---- start making changes here ----")
    596 
    597   if OPTIONS.wipe_user_data:
    598     script.Print("Erasing user data...")
    599     script.FormatPartition("/data")
    600 
    601   script.Print("Removing unneeded files...")
    602   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
    603                      ["/"+i for i in sorted(source_data)
    604                             if i not in target_data] +
    605                      ["/system/recovery.img"])
    606 
    607   script.ShowProgress(0.8, 0)
    608   total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
    609   if updating_boot:
    610     total_patch_size += target_boot.size
    611   so_far = 0
    612 
    613   script.Print("Patching system files...")
    614   deferred_patch_list = []
    615   for item in patch_list:
    616     fn, tf, sf, size, _ = item
    617     if tf.name == "system/build.prop":
    618       deferred_patch_list.append(item)
    619       continue
    620     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
    621     so_far += tf.size
    622     script.SetProgress(so_far / total_patch_size)
    623 
    624   if updating_boot:
    625     # Produce the boot image by applying a patch to the current
    626     # contents of the boot partition, and write it back to the
    627     # partition.
    628     script.Print("Patching boot image...")
    629     script.ApplyPatch("%s:%s:%d:%s:%d:%s"
    630                       % (boot_type, boot_device,
    631                          source_boot.size, source_boot.sha1,
    632                          target_boot.size, target_boot.sha1),
    633                       "-",
    634                       target_boot.size, target_boot.sha1,
    635                       source_boot.sha1, "patch/boot.img.p")
    636     so_far += target_boot.size
    637     script.SetProgress(so_far / total_patch_size)
    638     print "boot image changed; including."
    639   else:
    640     print "boot image unchanged; skipping."
    641 
    642   if updating_recovery:
    643     # Is it better to generate recovery as a patch from the current
    644     # boot image, or from the previous recovery image?  For large
    645     # updates with significant kernel changes, probably the former.
    646     # For small updates where the kernel hasn't changed, almost
    647     # certainly the latter.  We pick the first option.  Future
    648     # complicated schemes may let us effectively use both.
    649     #
    650     # A wacky possibility: as long as there is room in the boot
    651     # partition, include the binaries and image files from recovery in
    652     # the boot image (though not in the ramdisk) so they can be used
    653     # as fodder for constructing the recovery image.
    654     MakeRecoveryPatch(output_zip, target_recovery, target_boot)
    655     script.DeleteFiles(["/system/recovery-from-boot.p",
    656                         "/system/etc/install-recovery.sh"])
    657     print "recovery image changed; including as patch from boot."
    658   else:
    659     print "recovery image unchanged; skipping."
    660 
    661   script.ShowProgress(0.1, 10)
    662 
    663   (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None)
    664 
    665   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
    666   temp_script = script.MakeTemporary()
    667   Item.GetMetadata(target_zip)
    668   Item.Get("system").SetPermissions(temp_script)
    669 
    670   # Note that this call will mess up the tree of Items, so make sure
    671   # we're done with it.
    672   (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None)
    673   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
    674 
    675   # Delete all the symlinks in source that aren't in target.  This
    676   # needs to happen before verbatim files are unpacked, in case a
    677   # symlink in the source is replaced by a real file in the target.
    678   to_delete = []
    679   for dest, link in source_symlinks:
    680     if link not in target_symlinks_d:
    681       to_delete.append(link)
    682   script.DeleteFiles(to_delete)
    683 
    684   if verbatim_targets:
    685     script.Print("Unpacking new files...")
    686     script.UnpackPackageDir("system", "/system")
    687 
    688   if updating_recovery:
    689     script.Print("Unpacking new recovery...")
    690     script.UnpackPackageDir("recovery", "/system")
    691 
    692   script.Print("Symlinks and permissions...")
    693 
    694   # Create all the symlinks that don't already exist, or point to
    695   # somewhere different than what we want.  Delete each symlink before
    696   # creating it, since the 'symlink' command won't overwrite.
    697   to_create = []
    698   for dest, link in target_symlinks:
    699     if link in source_symlinks_d:
    700       if dest != source_symlinks_d[link]:
    701         to_create.append((dest, link))
    702     else:
    703       to_create.append((dest, link))
    704   script.DeleteFiles([i[1] for i in to_create])
    705   script.MakeSymlinks(to_create)
    706   if OPTIONS.aslr_mode:
    707     script.RetouchBinaries(target_retouch_files)
    708   else:
    709     script.UndoRetouchBinaries(target_retouch_files)
    710 
    711   # Now that the symlinks are created, we can set all the
    712   # permissions.
    713   script.AppendScript(temp_script)
    714 
    715   # Do device-specific installation (eg, write radio image).
    716   device_specific.IncrementalOTA_InstallEnd()
    717 
    718   if OPTIONS.extra_script is not None:
    719     script.AppendExtra(OPTIONS.extra_script)
    720 
    721   # Patch the build.prop file last, so if something fails but the
    722   # device can still come up, it appears to be the old build and will
    723   # get set the OTA package again to retry.
    724   script.Print("Patching remaining system files...")
    725   for item in deferred_patch_list:
    726     fn, tf, sf, size, _ = item
    727     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
    728   script.SetPermissions("/system/build.prop", 0, 0, 0644)
    729 
    730   script.AddToZip(target_zip, output_zip)
    731   WriteMetadata(metadata, output_zip)
    732 
    733 
    734 def main(argv):
    735 
    736   def option_handler(o, a):
    737     if o in ("-b", "--board_config"):
    738       pass   # deprecated
    739     elif o in ("-k", "--package_key"):
    740       OPTIONS.package_key = a
    741     elif o in ("-i", "--incremental_from"):
    742       OPTIONS.incremental_source = a
    743     elif o in ("-w", "--wipe_user_data"):
    744       OPTIONS.wipe_user_data = True
    745     elif o in ("-n", "--no_prereq"):
    746       OPTIONS.omit_prereq = True
    747     elif o in ("-e", "--extra_script"):
    748       OPTIONS.extra_script = a
    749     elif o in ("-a", "--aslr_mode"):
    750       if a in ("on", "On", "true", "True", "yes", "Yes"):
    751         OPTIONS.aslr_mode = True
    752       else:
    753         OPTIONS.aslr_mode = False
    754     elif o in ("--worker_threads"):
    755       OPTIONS.worker_threads = int(a)
    756     else:
    757       return False
    758     return True
    759 
    760   args = common.ParseOptions(argv, __doc__,
    761                              extra_opts="b:k:i:d:wne:a:",
    762                              extra_long_opts=["board_config=",
    763                                               "package_key=",
    764                                               "incremental_from=",
    765                                               "wipe_user_data",
    766                                               "no_prereq",
    767                                               "extra_script=",
    768                                               "worker_threads=",
    769                                               "aslr_mode=",
    770                                               ],
    771                              extra_option_handler=option_handler)
    772 
    773   if len(args) != 2:
    774     common.Usage(__doc__)
    775     sys.exit(1)
    776 
    777   if OPTIONS.extra_script is not None:
    778     OPTIONS.extra_script = open(OPTIONS.extra_script).read()
    779 
    780   print "unzipping target target-files..."
    781   OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
    782 
    783   OPTIONS.target_tmp = OPTIONS.input_tmp
    784   OPTIONS.info_dict = common.LoadInfoDict(input_zip)
    785   if OPTIONS.verbose:
    786     print "--- target info ---"
    787     common.DumpInfoDict(OPTIONS.info_dict)
    788 
    789   if OPTIONS.device_specific is None:
    790     OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
    791   if OPTIONS.device_specific is not None:
    792     OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
    793     print "using device-specific extensions in", OPTIONS.device_specific
    794 
    795   temp_zip_file = tempfile.NamedTemporaryFile()
    796   output_zip = zipfile.ZipFile(temp_zip_file, "w",
    797                                compression=zipfile.ZIP_DEFLATED)
    798 
    799   if OPTIONS.incremental_source is None:
    800     WriteFullOTAPackage(input_zip, output_zip)
    801     if OPTIONS.package_key is None:
    802       OPTIONS.package_key = OPTIONS.info_dict.get(
    803           "default_system_dev_certificate",
    804           "build/target/product/security/testkey")
    805   else:
    806     print "unzipping source target-files..."
    807     OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
    808     OPTIONS.target_info_dict = OPTIONS.info_dict
    809     OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
    810     if OPTIONS.package_key is None:
    811       OPTIONS.package_key = OPTIONS.source_info_dict.get(
    812           "default_system_dev_certificate",
    813           "build/target/product/security/testkey")
    814     if OPTIONS.verbose:
    815       print "--- source info ---"
    816       common.DumpInfoDict(OPTIONS.source_info_dict)
    817     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
    818 
    819   output_zip.close()
    820 
    821   SignOutput(temp_zip_file.name, args[1])
    822   temp_zip_file.close()
    823 
    824   common.Cleanup()
    825 
    826   print "done."
    827 
    828 
    829 if __name__ == '__main__':
    830   try:
    831     common.CloseInheritedPipes()
    832     main(sys.argv[1:])
    833   except common.ExternalError, e:
    834     print
    835     print "   ERROR: %s" % (e,)
    836     print
    837     sys.exit(1)
    838