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