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