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