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