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