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   # Images with different content will have a different first page, so
    340   # we check to see if this recovery has already been installed by
    341   # testing just the first 2k.
    342   HEADER_SIZE = 2048
    343   header_sha1 = common.sha1(recovery_img.data[:HEADER_SIZE]).hexdigest()
    344   sh = """#!/system/bin/sh
    345 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then
    346   log -t recovery "Installing new recovery image"
    347   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
    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         'header_size': HEADER_SIZE,
    354         'header_sha1': header_sha1,
    355         'recovery_size': recovery_img.size,
    356         'recovery_sha1': recovery_img.sha1,
    357         'boot_type': boot_type,
    358         'boot_device': boot_device,
    359         'recovery_type': recovery_type,
    360         'recovery_device': recovery_device,
    361         }
    362   common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
    363   return Item.Get("system/etc/install-recovery.sh", dir=False)
    364 
    365 
    366 def WriteFullOTAPackage(input_zip, output_zip):
    367   # TODO: how to determine this?  We don't know what version it will
    368   # be installed on top of.  For now, we expect the API just won't
    369   # change very often.
    370   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
    371 
    372   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
    373               "pre-device": GetBuildProp("ro.product.device", input_zip),
    374               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
    375               }
    376 
    377   device_specific = common.DeviceSpecificParams(
    378       input_zip=input_zip,
    379       input_version=OPTIONS.info_dict["recovery_api_version"],
    380       output_zip=output_zip,
    381       script=script,
    382       input_tmp=OPTIONS.input_tmp,
    383       metadata=metadata,
    384       info_dict=OPTIONS.info_dict)
    385 
    386   if not OPTIONS.omit_prereq:
    387     ts = GetBuildProp("ro.build.date.utc", input_zip)
    388     script.AssertOlderBuild(ts)
    389 
    390   AppendAssertions(script, input_zip)
    391   device_specific.FullOTA_Assertions()
    392 
    393   script.ShowProgress(0.5, 0)
    394 
    395   if OPTIONS.wipe_user_data:
    396     script.FormatPartition("/data")
    397 
    398   script.FormatPartition("/system")
    399   script.Mount("/system")
    400   script.UnpackPackageDir("recovery", "/system")
    401   script.UnpackPackageDir("system", "/system")
    402 
    403   (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
    404   script.MakeSymlinks(symlinks)
    405   if OPTIONS.aslr_mode:
    406     script.RetouchBinaries(retouch_files)
    407   else:
    408     script.UndoRetouchBinaries(retouch_files)
    409 
    410   boot_img = common.GetBootableImage("boot.img", "boot.img",
    411                                      OPTIONS.input_tmp, "BOOT")
    412   recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
    413                                          OPTIONS.input_tmp, "RECOVERY")
    414   MakeRecoveryPatch(output_zip, recovery_img, boot_img)
    415 
    416   Item.GetMetadata(input_zip)
    417   Item.Get("system").SetPermissions(script)
    418 
    419   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
    420   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
    421   script.ShowProgress(0.2, 0)
    422 
    423   script.ShowProgress(0.2, 10)
    424   script.WriteRawImage("/boot", "boot.img")
    425 
    426   script.ShowProgress(0.1, 0)
    427   device_specific.FullOTA_InstallEnd()
    428 
    429   if OPTIONS.extra_script is not None:
    430     script.AppendExtra(OPTIONS.extra_script)
    431 
    432   script.UnmountAll()
    433   script.AddToZip(input_zip, output_zip)
    434   WriteMetadata(metadata, output_zip)
    435 
    436 
    437 def WriteMetadata(metadata, output_zip):
    438   common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
    439                      "".join(["%s=%s\n" % kv
    440                               for kv in sorted(metadata.iteritems())]))
    441 
    442 
    443 
    444 
    445 def LoadSystemFiles(z):
    446   """Load all the files from SYSTEM/... in a given target-files
    447   ZipFile, and return a dict of {filename: File object}."""
    448   out = {}
    449   retouch_files = []
    450   for info in z.infolist():
    451     if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
    452       basefilename = info.filename[7:]
    453       fn = "system/" + basefilename
    454       data = z.read(info.filename)
    455       out[fn] = common.File(fn, data)
    456       if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
    457         retouch_files.append(("/system/" + basefilename,
    458                               out[fn].sha1))
    459   return (out, retouch_files)
    460 
    461 
    462 def GetBuildProp(property, z):
    463   """Return the fingerprint of the build of a given target-files
    464   ZipFile object."""
    465   bp = z.read("SYSTEM/build.prop")
    466   if not property:
    467     return bp
    468   m = re.search(re.escape(property) + r"=(.*)\n", bp)
    469   if not m:
    470     raise common.ExternalError("couldn't find %s in build.prop" % (property,))
    471   return m.group(1).strip()
    472 
    473 
    474 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
    475   source_version = OPTIONS.source_info_dict["recovery_api_version"]
    476   target_version = OPTIONS.target_info_dict["recovery_api_version"]
    477 
    478   if source_version == 0:
    479     print ("WARNING: generating edify script for a source that "
    480            "can't install it.")
    481   script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict)
    482 
    483   metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
    484               "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
    485               }
    486 
    487   device_specific = common.DeviceSpecificParams(
    488       source_zip=source_zip,
    489       source_version=source_version,
    490       target_zip=target_zip,
    491       target_version=target_version,
    492       output_zip=output_zip,
    493       script=script,
    494       metadata=metadata,
    495       info_dict=OPTIONS.info_dict)
    496 
    497   print "Loading target..."
    498   (target_data, target_retouch_files) = LoadSystemFiles(target_zip)
    499   print "Loading source..."
    500   (source_data, source_retouch_files) = LoadSystemFiles(source_zip)
    501 
    502   verbatim_targets = []
    503   patch_list = []
    504   diffs = []
    505   largest_source_size = 0
    506   for fn in sorted(target_data.keys()):
    507     tf = target_data[fn]
    508     assert fn == tf.name
    509     sf = source_data.get(fn, None)
    510 
    511     if sf is None or fn in OPTIONS.require_verbatim:
    512       # This file should be included verbatim
    513       if fn in OPTIONS.prohibit_verbatim:
    514         raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
    515       print "send", fn, "verbatim"
    516       tf.AddToZip(output_zip)
    517       verbatim_targets.append((fn, tf.size))
    518     elif tf.sha1 != sf.sha1:
    519       # File is different; consider sending as a patch
    520       diffs.append(common.Difference(tf, sf))
    521     else:
    522       # Target file identical to source.
    523       pass
    524 
    525   common.ComputeDifferences(diffs)
    526 
    527   for diff in diffs:
    528     tf, sf, d = diff.GetPatch()
    529     if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
    530       # patch is almost as big as the file; don't bother patching
    531       tf.AddToZip(output_zip)
    532       verbatim_targets.append((tf.name, tf.size))
    533     else:
    534       common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
    535       patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest()))
    536       largest_source_size = max(largest_source_size, sf.size)
    537 
    538   source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
    539   target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
    540   metadata["pre-build"] = source_fp
    541   metadata["post-build"] = target_fp
    542 
    543   script.Mount("/system")
    544   script.AssertSomeFingerprint(source_fp, target_fp)
    545 
    546   source_boot = common.GetBootableImage(
    547       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT")
    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   target_recovery = common.GetBootableImage(
    555       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
    556   updating_recovery = (source_recovery.data != target_recovery.data)
    557 
    558   # Here's how we divide up the progress bar:
    559   #  0.1 for verifying the start state (PatchCheck calls)
    560   #  0.8 for applying patches (ApplyPatch calls)
    561   #  0.1 for unpacking verbatim files, symlinking, and doing the
    562   #      device-specific commands.
    563 
    564   AppendAssertions(script, target_zip)
    565   device_specific.IncrementalOTA_Assertions()
    566 
    567   script.Print("Verifying current system...")
    568 
    569   script.ShowProgress(0.1, 0)
    570   total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
    571   if updating_boot:
    572     total_verify_size += source_boot.size
    573   so_far = 0
    574 
    575   for fn, tf, sf, size, patch_sha in patch_list:
    576     script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
    577     so_far += sf.size
    578     script.SetProgress(so_far / total_verify_size)
    579 
    580   if updating_boot:
    581     d = common.Difference(target_boot, source_boot)
    582     _, _, d = d.ComputePatch()
    583     print "boot      target: %d  source: %d  diff: %d" % (
    584         target_boot.size, source_boot.size, len(d))
    585 
    586     common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
    587 
    588     boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
    589 
    590     script.PatchCheck("%s:%s:%d:%s:%d:%s" %
    591                       (boot_type, boot_device,
    592                        source_boot.size, source_boot.sha1,
    593                        target_boot.size, target_boot.sha1))
    594     so_far += source_boot.size
    595     script.SetProgress(so_far / total_verify_size)
    596 
    597   if patch_list or updating_recovery or updating_boot:
    598     script.CacheFreeSpaceCheck(largest_source_size)
    599 
    600   device_specific.IncrementalOTA_VerifyEnd()
    601 
    602   script.Comment("---- start making changes here ----")
    603 
    604   if OPTIONS.wipe_user_data:
    605     script.Print("Erasing user data...")
    606     script.FormatPartition("/data")
    607 
    608   script.Print("Removing unneeded files...")
    609   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
    610                      ["/"+i for i in sorted(source_data)
    611                             if i not in target_data] +
    612                      ["/system/recovery.img"])
    613 
    614   script.ShowProgress(0.8, 0)
    615   total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
    616   if updating_boot:
    617     total_patch_size += target_boot.size
    618   so_far = 0
    619 
    620   script.Print("Patching system files...")
    621   deferred_patch_list = []
    622   for item in patch_list:
    623     fn, tf, sf, size, _ = item
    624     if tf.name == "system/build.prop":
    625       deferred_patch_list.append(item)
    626       continue
    627     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
    628     so_far += tf.size
    629     script.SetProgress(so_far / total_patch_size)
    630 
    631   if updating_boot:
    632     # Produce the boot image by applying a patch to the current
    633     # contents of the boot partition, and write it back to the
    634     # partition.
    635     script.Print("Patching boot image...")
    636     script.ApplyPatch("%s:%s:%d:%s:%d:%s"
    637                       % (boot_type, boot_device,
    638                          source_boot.size, source_boot.sha1,
    639                          target_boot.size, target_boot.sha1),
    640                       "-",
    641                       target_boot.size, target_boot.sha1,
    642                       source_boot.sha1, "patch/boot.img.p")
    643     so_far += target_boot.size
    644     script.SetProgress(so_far / total_patch_size)
    645     print "boot image changed; including."
    646   else:
    647     print "boot image unchanged; skipping."
    648 
    649   if updating_recovery:
    650     # Is it better to generate recovery as a patch from the current
    651     # boot image, or from the previous recovery image?  For large
    652     # updates with significant kernel changes, probably the former.
    653     # For small updates where the kernel hasn't changed, almost
    654     # certainly the latter.  We pick the first option.  Future
    655     # complicated schemes may let us effectively use both.
    656     #
    657     # A wacky possibility: as long as there is room in the boot
    658     # partition, include the binaries and image files from recovery in
    659     # the boot image (though not in the ramdisk) so they can be used
    660     # as fodder for constructing the recovery image.
    661     MakeRecoveryPatch(output_zip, target_recovery, target_boot)
    662     script.DeleteFiles(["/system/recovery-from-boot.p",
    663                         "/system/etc/install-recovery.sh"])
    664     print "recovery image changed; including as patch from boot."
    665   else:
    666     print "recovery image unchanged; skipping."
    667 
    668   script.ShowProgress(0.1, 10)
    669 
    670   (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None)
    671 
    672   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
    673   temp_script = script.MakeTemporary()
    674   Item.GetMetadata(target_zip)
    675   Item.Get("system").SetPermissions(temp_script)
    676 
    677   # Note that this call will mess up the tree of Items, so make sure
    678   # we're done with it.
    679   (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None)
    680   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
    681 
    682   # Delete all the symlinks in source that aren't in target.  This
    683   # needs to happen before verbatim files are unpacked, in case a
    684   # symlink in the source is replaced by a real file in the target.
    685   to_delete = []
    686   for dest, link in source_symlinks:
    687     if link not in target_symlinks_d:
    688       to_delete.append(link)
    689   script.DeleteFiles(to_delete)
    690 
    691   if verbatim_targets:
    692     script.Print("Unpacking new files...")
    693     script.UnpackPackageDir("system", "/system")
    694 
    695   if updating_recovery:
    696     script.Print("Unpacking new recovery...")
    697     script.UnpackPackageDir("recovery", "/system")
    698 
    699   script.Print("Symlinks and permissions...")
    700 
    701   # Create all the symlinks that don't already exist, or point to
    702   # somewhere different than what we want.  Delete each symlink before
    703   # creating it, since the 'symlink' command won't overwrite.
    704   to_create = []
    705   for dest, link in target_symlinks:
    706     if link in source_symlinks_d:
    707       if dest != source_symlinks_d[link]:
    708         to_create.append((dest, link))
    709     else:
    710       to_create.append((dest, link))
    711   script.DeleteFiles([i[1] for i in to_create])
    712   script.MakeSymlinks(to_create)
    713   if OPTIONS.aslr_mode:
    714     script.RetouchBinaries(target_retouch_files)
    715   else:
    716     script.UndoRetouchBinaries(target_retouch_files)
    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   if OPTIONS.verbose:
    793     print "--- target info ---"
    794     common.DumpInfoDict(OPTIONS.info_dict)
    795 
    796   if OPTIONS.device_specific is None:
    797     OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
    798   if OPTIONS.device_specific is not None:
    799     OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
    800     print "using device-specific extensions in", OPTIONS.device_specific
    801 
    802   temp_zip_file = tempfile.NamedTemporaryFile()
    803   output_zip = zipfile.ZipFile(temp_zip_file, "w",
    804                                compression=zipfile.ZIP_DEFLATED)
    805 
    806   if OPTIONS.incremental_source is None:
    807     WriteFullOTAPackage(input_zip, output_zip)
    808     if OPTIONS.package_key is None:
    809       OPTIONS.package_key = OPTIONS.info_dict.get(
    810           "default_system_dev_certificate",
    811           "build/target/product/security/testkey")
    812   else:
    813     print "unzipping source target-files..."
    814     OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
    815     OPTIONS.target_info_dict = OPTIONS.info_dict
    816     OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
    817     if OPTIONS.package_key is None:
    818       OPTIONS.package_key = OPTIONS.source_info_dict.get(
    819           "default_system_dev_certificate",
    820           "build/target/product/security/testkey")
    821     if OPTIONS.verbose:
    822       print "--- source info ---"
    823       common.DumpInfoDict(OPTIONS.source_info_dict)
    824     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
    825 
    826   output_zip.close()
    827 
    828   SignOutput(temp_zip_file.name, args[1])
    829   temp_zip_file.close()
    830 
    831   common.Cleanup()
    832 
    833   print "done."
    834 
    835 
    836 if __name__ == '__main__':
    837   try:
    838     common.CloseInheritedPipes()
    839     main(sys.argv[1:])
    840   except common.ExternalError, e:
    841     print
    842     print "   ERROR: %s" % (e,)
    843     print
    844     sys.exit(1)
    845