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   --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   --full_radio
     41       When generating an incremental OTA, always include a full copy of
     42       radio image. This option is only meaningful when -i is specified,
     43       because a full radio is always included in a full OTA if applicable.
     44 
     45   -v  (--verify)
     46       Remount and verify the checksums of the files written to the
     47       system and vendor (if used) partitions.  Incremental builds only.
     48 
     49   -o  (--oem_settings)  <file>
     50       Use the file to specify the expected OEM-specific properties
     51       on the OEM partition of the intended device.
     52 
     53   -w  (--wipe_user_data)
     54       Generate an OTA package that will wipe the user data partition
     55       when installed.
     56 
     57   -n  (--no_prereq)
     58       Omit the timestamp prereq check normally included at the top of
     59       the build scripts (used for developer OTA packages which
     60       legitimately need to go back and forth).
     61 
     62   -e  (--extra_script)  <file>
     63       Insert the contents of file at the end of the update script.
     64 
     65   -a  (--aslr_mode)  <on|off>
     66       Specify whether to turn on ASLR for the package (on by default).
     67 
     68   -2  (--two_step)
     69       Generate a 'two-step' OTA package, where recovery is updated
     70       first, so that any changes made to the system partition are done
     71       using the new recovery (new kernel, etc.).
     72 
     73   --block
     74       Generate a block-based OTA if possible.  Will fall back to a
     75       file-based OTA if the target_files is older and doesn't support
     76       block-based OTAs.
     77 
     78   -b  (--binary)  <file>
     79       Use the given binary as the update-binary in the output package,
     80       instead of the binary in the build's target_files.  Use for
     81       development only.
     82 
     83   -t  (--worker_threads) <int>
     84       Specifies the number of worker-threads that will be used when
     85       generating patches for incremental updates (defaults to 3).
     86 
     87 """
     88 
     89 import sys
     90 
     91 if sys.hexversion < 0x02070000:
     92   print >> sys.stderr, "Python 2.7 or newer is required."
     93   sys.exit(1)
     94 
     95 import multiprocessing
     96 import os
     97 import tempfile
     98 import zipfile
     99 
    100 import common
    101 import edify_generator
    102 import sparse_img
    103 
    104 OPTIONS = common.OPTIONS
    105 OPTIONS.package_key = None
    106 OPTIONS.incremental_source = None
    107 OPTIONS.verify = False
    108 OPTIONS.require_verbatim = set()
    109 OPTIONS.prohibit_verbatim = set(("system/build.prop",))
    110 OPTIONS.patch_threshold = 0.95
    111 OPTIONS.wipe_user_data = False
    112 OPTIONS.omit_prereq = False
    113 OPTIONS.extra_script = None
    114 OPTIONS.aslr_mode = True
    115 OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
    116 if OPTIONS.worker_threads == 0:
    117   OPTIONS.worker_threads = 1
    118 OPTIONS.two_step = False
    119 OPTIONS.no_signing = False
    120 OPTIONS.block_based = False
    121 OPTIONS.updater_binary = None
    122 OPTIONS.oem_source = None
    123 OPTIONS.fallback_to_full = True
    124 OPTIONS.full_radio = False
    125 
    126 def MostPopularKey(d, default):
    127   """Given a dict, return the key corresponding to the largest
    128   value.  Returns 'default' if the dict is empty."""
    129   x = [(v, k) for (k, v) in d.iteritems()]
    130   if not x:
    131     return default
    132   x.sort()
    133   return x[-1][1]
    134 
    135 
    136 def IsSymlink(info):
    137   """Return true if the zipfile.ZipInfo object passed in represents a
    138   symlink."""
    139   return (info.external_attr >> 16) == 0o120777
    140 
    141 def IsRegular(info):
    142   """Return true if the zipfile.ZipInfo object passed in represents a
    143   symlink."""
    144   return (info.external_attr >> 28) == 0o10
    145 
    146 def ClosestFileMatch(src, tgtfiles, existing):
    147   """Returns the closest file match between a source file and list
    148      of potential matches.  The exact filename match is preferred,
    149      then the sha1 is searched for, and finally a file with the same
    150      basename is evaluated.  Rename support in the updater-binary is
    151      required for the latter checks to be used."""
    152 
    153   result = tgtfiles.get("path:" + src.name)
    154   if result is not None:
    155     return result
    156 
    157   if not OPTIONS.target_info_dict.get("update_rename_support", False):
    158     return None
    159 
    160   if src.size < 1000:
    161     return None
    162 
    163   result = tgtfiles.get("sha1:" + src.sha1)
    164   if result is not None and existing.get(result.name) is None:
    165     return result
    166   result = tgtfiles.get("file:" + src.name.split("/")[-1])
    167   if result is not None and existing.get(result.name) is None:
    168     return result
    169   return None
    170 
    171 class ItemSet(object):
    172   def __init__(self, partition, fs_config):
    173     self.partition = partition
    174     self.fs_config = fs_config
    175     self.ITEMS = {}
    176 
    177   def Get(self, name, is_dir=False):
    178     if name not in self.ITEMS:
    179       self.ITEMS[name] = Item(self, name, is_dir=is_dir)
    180     return self.ITEMS[name]
    181 
    182   def GetMetadata(self, input_zip):
    183     # The target_files contains a record of what the uid,
    184     # gid, and mode are supposed to be.
    185     output = input_zip.read(self.fs_config)
    186 
    187     for line in output.split("\n"):
    188       if not line:
    189         continue
    190       columns = line.split()
    191       name, uid, gid, mode = columns[:4]
    192       selabel = None
    193       capabilities = None
    194 
    195       # After the first 4 columns, there are a series of key=value
    196       # pairs. Extract out the fields we care about.
    197       for element in columns[4:]:
    198         key, value = element.split("=")
    199         if key == "selabel":
    200           selabel = value
    201         if key == "capabilities":
    202           capabilities = value
    203 
    204       i = self.ITEMS.get(name, None)
    205       if i is not None:
    206         i.uid = int(uid)
    207         i.gid = int(gid)
    208         i.mode = int(mode, 8)
    209         i.selabel = selabel
    210         i.capabilities = capabilities
    211         if i.is_dir:
    212           i.children.sort(key=lambda i: i.name)
    213 
    214     # set metadata for the files generated by this script.
    215     i = self.ITEMS.get("system/recovery-from-boot.p", None)
    216     if i:
    217       i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o644, None, None
    218     i = self.ITEMS.get("system/etc/install-recovery.sh", None)
    219     if i:
    220       i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o544, None, None
    221 
    222 
    223 class Item(object):
    224   """Items represent the metadata (user, group, mode) of files and
    225   directories in the system image."""
    226   def __init__(self, itemset, name, is_dir=False):
    227     self.itemset = itemset
    228     self.name = name
    229     self.uid = None
    230     self.gid = None
    231     self.mode = None
    232     self.selabel = None
    233     self.capabilities = None
    234     self.is_dir = is_dir
    235     self.descendants = None
    236     self.best_subtree = None
    237 
    238     if name:
    239       self.parent = itemset.Get(os.path.dirname(name), is_dir=True)
    240       self.parent.children.append(self)
    241     else:
    242       self.parent = None
    243     if self.is_dir:
    244       self.children = []
    245 
    246   def Dump(self, indent=0):
    247     if self.uid is not None:
    248       print "%s%s %d %d %o" % (
    249           "  " * indent, self.name, self.uid, self.gid, self.mode)
    250     else:
    251       print "%s%s %s %s %s" % (
    252           "  " * indent, self.name, self.uid, self.gid, self.mode)
    253     if self.is_dir:
    254       print "%s%s" % ("  "*indent, self.descendants)
    255       print "%s%s" % ("  "*indent, self.best_subtree)
    256       for i in self.children:
    257         i.Dump(indent=indent+1)
    258 
    259   def CountChildMetadata(self):
    260     """Count up the (uid, gid, mode, selabel, capabilities) tuples for
    261     all children and determine the best strategy for using set_perm_recursive
    262     and set_perm to correctly chown/chmod all the files to their desired
    263     values.  Recursively calls itself for all descendants.
    264 
    265     Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count}
    266     counting up all descendants of this node.  (dmode or fmode may be None.)
    267     Also sets the best_subtree of each directory Item to the (uid, gid, dmode,
    268     fmode, selabel, capabilities) tuple that will match the most descendants of
    269     that Item.
    270     """
    271 
    272     assert self.is_dir
    273     key = (self.uid, self.gid, self.mode, None, self.selabel,
    274            self.capabilities)
    275     self.descendants = {key: 1}
    276     d = self.descendants
    277     for i in self.children:
    278       if i.is_dir:
    279         for k, v in i.CountChildMetadata().iteritems():
    280           d[k] = d.get(k, 0) + v
    281       else:
    282         k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities)
    283         d[k] = d.get(k, 0) + 1
    284 
    285     # Find the (uid, gid, dmode, fmode, selabel, capabilities)
    286     # tuple that matches the most descendants.
    287 
    288     # First, find the (uid, gid) pair that matches the most
    289     # descendants.
    290     ug = {}
    291     for (uid, gid, _, _, _, _), count in d.iteritems():
    292       ug[(uid, gid)] = ug.get((uid, gid), 0) + count
    293     ug = MostPopularKey(ug, (0, 0))
    294 
    295     # Now find the dmode, fmode, selabel, and capabilities that match
    296     # the most descendants with that (uid, gid), and choose those.
    297     best_dmode = (0, 0o755)
    298     best_fmode = (0, 0o644)
    299     best_selabel = (0, None)
    300     best_capabilities = (0, None)
    301     for k, count in d.iteritems():
    302       if k[:2] != ug:
    303         continue
    304       if k[2] is not None and count >= best_dmode[0]:
    305         best_dmode = (count, k[2])
    306       if k[3] is not None and count >= best_fmode[0]:
    307         best_fmode = (count, k[3])
    308       if k[4] is not None and count >= best_selabel[0]:
    309         best_selabel = (count, k[4])
    310       if k[5] is not None and count >= best_capabilities[0]:
    311         best_capabilities = (count, k[5])
    312     self.best_subtree = ug + (
    313         best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1])
    314 
    315     return d
    316 
    317   def SetPermissions(self, script):
    318     """Append set_perm/set_perm_recursive commands to 'script' to
    319     set all permissions, users, and groups for the tree of files
    320     rooted at 'self'."""
    321 
    322     self.CountChildMetadata()
    323 
    324     def recurse(item, current):
    325       # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple
    326       # that the current item (and all its children) have already been set to.
    327       # We only need to issue set_perm/set_perm_recursive commands if we're
    328       # supposed to be something different.
    329       if item.is_dir:
    330         if current != item.best_subtree:
    331           script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
    332           current = item.best_subtree
    333 
    334         if item.uid != current[0] or item.gid != current[1] or \
    335            item.mode != current[2] or item.selabel != current[4] or \
    336            item.capabilities != current[5]:
    337           script.SetPermissions("/"+item.name, item.uid, item.gid,
    338                                 item.mode, item.selabel, item.capabilities)
    339 
    340         for i in item.children:
    341           recurse(i, current)
    342       else:
    343         if item.uid != current[0] or item.gid != current[1] or \
    344                item.mode != current[3] or item.selabel != current[4] or \
    345                item.capabilities != current[5]:
    346           script.SetPermissions("/"+item.name, item.uid, item.gid,
    347                                 item.mode, item.selabel, item.capabilities)
    348 
    349     recurse(self, (-1, -1, -1, -1, None, None))
    350 
    351 
    352 def CopyPartitionFiles(itemset, input_zip, output_zip=None, substitute=None):
    353   """Copies files for the partition in the input zip to the output
    354   zip.  Populates the Item class with their metadata, and returns a
    355   list of symlinks.  output_zip may be None, in which case the copy is
    356   skipped (but the other side effects still happen).  substitute is an
    357   optional dict of {output filename: contents} to be output instead of
    358   certain input files.
    359   """
    360 
    361   symlinks = []
    362 
    363   partition = itemset.partition
    364 
    365   for info in input_zip.infolist():
    366     prefix = partition.upper() + "/"
    367     if info.filename.startswith(prefix):
    368       basefilename = info.filename[len(prefix):]
    369       if IsSymlink(info):
    370         symlinks.append((input_zip.read(info.filename),
    371                          "/" + partition + "/" + basefilename))
    372       else:
    373         import copy
    374         info2 = copy.copy(info)
    375         fn = info2.filename = partition + "/" + basefilename
    376         if substitute and fn in substitute and substitute[fn] is None:
    377           continue
    378         if output_zip is not None:
    379           if substitute and fn in substitute:
    380             data = substitute[fn]
    381           else:
    382             data = input_zip.read(info.filename)
    383           common.ZipWriteStr(output_zip, info2, data)
    384         if fn.endswith("/"):
    385           itemset.Get(fn[:-1], is_dir=True)
    386         else:
    387           itemset.Get(fn)
    388 
    389   symlinks.sort()
    390   return symlinks
    391 
    392 
    393 def SignOutput(temp_zip_name, output_zip_name):
    394   key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
    395   pw = key_passwords[OPTIONS.package_key]
    396 
    397   common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
    398                   whole_file=True)
    399 
    400 
    401 def AppendAssertions(script, info_dict, oem_dict=None):
    402   oem_props = info_dict.get("oem_fingerprint_properties")
    403   if oem_props is None or len(oem_props) == 0:
    404     device = GetBuildProp("ro.product.device", info_dict)
    405     script.AssertDevice(device)
    406   else:
    407     if oem_dict is None:
    408       raise common.ExternalError(
    409           "No OEM file provided to answer expected assertions")
    410     for prop in oem_props.split():
    411       if oem_dict.get(prop) is None:
    412         raise common.ExternalError(
    413             "The OEM file is missing the property %s" % prop)
    414       script.AssertOemProperty(prop, oem_dict.get(prop))
    415 
    416 
    417 def HasRecoveryPatch(target_files_zip):
    418   try:
    419     target_files_zip.getinfo("SYSTEM/recovery-from-boot.p")
    420     return True
    421   except KeyError:
    422     return False
    423 
    424 def HasVendorPartition(target_files_zip):
    425   try:
    426     target_files_zip.getinfo("VENDOR/")
    427     return True
    428   except KeyError:
    429     return False
    430 
    431 def GetOemProperty(name, oem_props, oem_dict, info_dict):
    432   if oem_props is not None and name in oem_props:
    433     return oem_dict[name]
    434   return GetBuildProp(name, info_dict)
    435 
    436 
    437 def CalculateFingerprint(oem_props, oem_dict, info_dict):
    438   if oem_props is None:
    439     return GetBuildProp("ro.build.fingerprint", info_dict)
    440   return "%s/%s/%s:%s" % (
    441       GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
    442       GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
    443       GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
    444       GetBuildProp("ro.build.thumbprint", info_dict))
    445 
    446 
    447 def GetImage(which, tmpdir, info_dict):
    448   # Return an image object (suitable for passing to BlockImageDiff)
    449   # for the 'which' partition (most be "system" or "vendor").  If a
    450   # prebuilt image and file map are found in tmpdir they are used,
    451   # otherwise they are reconstructed from the individual files.
    452 
    453   assert which in ("system", "vendor")
    454 
    455   path = os.path.join(tmpdir, "IMAGES", which + ".img")
    456   mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
    457   if os.path.exists(path) and os.path.exists(mappath):
    458     print "using %s.img from target-files" % (which,)
    459     # This is a 'new' target-files, which already has the image in it.
    460 
    461   else:
    462     print "building %s.img from target-files" % (which,)
    463 
    464     # This is an 'old' target-files, which does not contain images
    465     # already built.  Build them.
    466 
    467     mappath = tempfile.mkstemp()[1]
    468     OPTIONS.tempfiles.append(mappath)
    469 
    470     import add_img_to_target_files
    471     if which == "system":
    472       path = add_img_to_target_files.BuildSystem(
    473           tmpdir, info_dict, block_list=mappath)
    474     elif which == "vendor":
    475       path = add_img_to_target_files.BuildVendor(
    476           tmpdir, info_dict, block_list=mappath)
    477 
    478   # Bug: http://b/20939131
    479   # In ext4 filesystems, block 0 might be changed even being mounted
    480   # R/O. We add it to clobbered_blocks so that it will be written to the
    481   # target unconditionally. Note that they are still part of care_map.
    482   clobbered_blocks = "0"
    483 
    484   return sparse_img.SparseImage(path, mappath, clobbered_blocks)
    485 
    486 
    487 def WriteFullOTAPackage(input_zip, output_zip):
    488   # TODO: how to determine this?  We don't know what version it will
    489   # be installed on top of. For now, we expect the API just won't
    490   # change very often. Similarly for fstab, it might have changed
    491   # in the target build.
    492   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
    493 
    494   oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
    495   recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
    496   oem_dict = None
    497   if oem_props is not None and len(oem_props) > 0:
    498     if OPTIONS.oem_source is None:
    499       raise common.ExternalError("OEM source required for this build")
    500     script.Mount("/oem", recovery_mount_options)
    501     oem_dict = common.LoadDictionaryFromLines(
    502         open(OPTIONS.oem_source).readlines())
    503 
    504   metadata = {
    505       "post-build": CalculateFingerprint(oem_props, oem_dict,
    506                                          OPTIONS.info_dict),
    507       "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
    508                                    OPTIONS.info_dict),
    509       "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
    510   }
    511 
    512   device_specific = common.DeviceSpecificParams(
    513       input_zip=input_zip,
    514       input_version=OPTIONS.info_dict["recovery_api_version"],
    515       output_zip=output_zip,
    516       script=script,
    517       input_tmp=OPTIONS.input_tmp,
    518       metadata=metadata,
    519       info_dict=OPTIONS.info_dict)
    520 
    521   has_recovery_patch = HasRecoveryPatch(input_zip)
    522   block_based = OPTIONS.block_based and has_recovery_patch
    523 
    524   if not OPTIONS.omit_prereq:
    525     ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
    526     ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
    527     script.AssertOlderBuild(ts, ts_text)
    528 
    529   AppendAssertions(script, OPTIONS.info_dict, oem_dict)
    530   device_specific.FullOTA_Assertions()
    531 
    532   # Two-step package strategy (in chronological order, which is *not*
    533   # the order in which the generated script has things):
    534   #
    535   # if stage is not "2/3" or "3/3":
    536   #    write recovery image to boot partition
    537   #    set stage to "2/3"
    538   #    reboot to boot partition and restart recovery
    539   # else if stage is "2/3":
    540   #    write recovery image to recovery partition
    541   #    set stage to "3/3"
    542   #    reboot to recovery partition and restart recovery
    543   # else:
    544   #    (stage must be "3/3")
    545   #    set stage to ""
    546   #    do normal full package installation:
    547   #       wipe and install system, boot image, etc.
    548   #       set up system to update recovery partition on first boot
    549   #    complete script normally
    550   #    (allow recovery to mark itself finished and reboot)
    551 
    552   recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
    553                                          OPTIONS.input_tmp, "RECOVERY")
    554   if OPTIONS.two_step:
    555     if not OPTIONS.info_dict.get("multistage_support", None):
    556       assert False, "two-step packages not supported by this build"
    557     fs = OPTIONS.info_dict["fstab"]["/misc"]
    558     assert fs.fs_type.upper() == "EMMC", \
    559         "two-step packages only supported on devices with EMMC /misc partitions"
    560     bcb_dev = {"bcb_dev": fs.device}
    561     common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
    562     script.AppendExtra("""
    563 if get_stage("%(bcb_dev)s") == "2/3" then
    564 """ % bcb_dev)
    565     script.WriteRawImage("/recovery", "recovery.img")
    566     script.AppendExtra("""
    567 set_stage("%(bcb_dev)s", "3/3");
    568 reboot_now("%(bcb_dev)s", "recovery");
    569 else if get_stage("%(bcb_dev)s") == "3/3" then
    570 """ % bcb_dev)
    571 
    572   # Dump fingerprints
    573   script.Print("Target: %s" % CalculateFingerprint(
    574       oem_props, oem_dict, OPTIONS.info_dict))
    575 
    576   device_specific.FullOTA_InstallBegin()
    577 
    578   system_progress = 0.75
    579 
    580   if OPTIONS.wipe_user_data:
    581     system_progress -= 0.1
    582   if HasVendorPartition(input_zip):
    583     system_progress -= 0.1
    584 
    585   if "selinux_fc" in OPTIONS.info_dict:
    586     WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
    587 
    588   recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
    589 
    590   system_items = ItemSet("system", "META/filesystem_config.txt")
    591   script.ShowProgress(system_progress, 0)
    592 
    593   if block_based:
    594     # Full OTA is done as an "incremental" against an empty source
    595     # image.  This has the effect of writing new data from the package
    596     # to the entire partition, but lets us reuse the updater code that
    597     # writes incrementals to do it.
    598     system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
    599     system_tgt.ResetFileMap()
    600     system_diff = common.BlockDifference("system", system_tgt, src=None)
    601     system_diff.WriteScript(script, output_zip)
    602   else:
    603     script.FormatPartition("/system")
    604     script.Mount("/system", recovery_mount_options)
    605     if not has_recovery_patch:
    606       script.UnpackPackageDir("recovery", "/system")
    607     script.UnpackPackageDir("system", "/system")
    608 
    609     symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
    610     script.MakeSymlinks(symlinks)
    611 
    612   boot_img = common.GetBootableImage("boot.img", "boot.img",
    613                                      OPTIONS.input_tmp, "BOOT")
    614 
    615   if not block_based:
    616     def output_sink(fn, data):
    617       common.ZipWriteStr(output_zip, "recovery/" + fn, data)
    618       system_items.Get("system/" + fn)
    619 
    620     common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
    621                              recovery_img, boot_img)
    622 
    623     system_items.GetMetadata(input_zip)
    624     system_items.Get("system").SetPermissions(script)
    625 
    626   if HasVendorPartition(input_zip):
    627     vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
    628     script.ShowProgress(0.1, 0)
    629 
    630     if block_based:
    631       vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
    632       vendor_tgt.ResetFileMap()
    633       vendor_diff = common.BlockDifference("vendor", vendor_tgt)
    634       vendor_diff.WriteScript(script, output_zip)
    635     else:
    636       script.FormatPartition("/vendor")
    637       script.Mount("/vendor", recovery_mount_options)
    638       script.UnpackPackageDir("vendor", "/vendor")
    639 
    640       symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
    641       script.MakeSymlinks(symlinks)
    642 
    643       vendor_items.GetMetadata(input_zip)
    644       vendor_items.Get("vendor").SetPermissions(script)
    645 
    646   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
    647   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
    648 
    649   script.ShowProgress(0.05, 5)
    650   script.WriteRawImage("/boot", "boot.img")
    651 
    652   script.ShowProgress(0.2, 10)
    653   device_specific.FullOTA_InstallEnd()
    654 
    655   if OPTIONS.extra_script is not None:
    656     script.AppendExtra(OPTIONS.extra_script)
    657 
    658   script.UnmountAll()
    659 
    660   if OPTIONS.wipe_user_data:
    661     script.ShowProgress(0.1, 10)
    662     script.FormatPartition("/data")
    663 
    664   if OPTIONS.two_step:
    665     script.AppendExtra("""
    666 set_stage("%(bcb_dev)s", "");
    667 """ % bcb_dev)
    668     script.AppendExtra("else\n")
    669     script.WriteRawImage("/boot", "recovery.img")
    670     script.AppendExtra("""
    671 set_stage("%(bcb_dev)s", "2/3");
    672 reboot_now("%(bcb_dev)s", "");
    673 endif;
    674 endif;
    675 """ % bcb_dev)
    676   script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
    677   WriteMetadata(metadata, output_zip)
    678 
    679 
    680 def WritePolicyConfig(file_name, output_zip):
    681   common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
    682 
    683 
    684 def WriteMetadata(metadata, output_zip):
    685   common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
    686                      "".join(["%s=%s\n" % kv
    687                               for kv in sorted(metadata.iteritems())]))
    688 
    689 
    690 def LoadPartitionFiles(z, partition):
    691   """Load all the files from the given partition in a given target-files
    692   ZipFile, and return a dict of {filename: File object}."""
    693   out = {}
    694   prefix = partition.upper() + "/"
    695   for info in z.infolist():
    696     if info.filename.startswith(prefix) and not IsSymlink(info):
    697       basefilename = info.filename[len(prefix):]
    698       fn = partition + "/" + basefilename
    699       data = z.read(info.filename)
    700       out[fn] = common.File(fn, data)
    701   return out
    702 
    703 
    704 def GetBuildProp(prop, info_dict):
    705   """Return the fingerprint of the build of a given target-files info_dict."""
    706   try:
    707     return info_dict.get("build.prop", {})[prop]
    708   except KeyError:
    709     raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
    710 
    711 
    712 def AddToKnownPaths(filename, known_paths):
    713   if filename[-1] == "/":
    714     return
    715   dirs = filename.split("/")[:-1]
    716   while len(dirs) > 0:
    717     path = "/".join(dirs)
    718     if path in known_paths:
    719       break
    720     known_paths.add(path)
    721     dirs.pop()
    722 
    723 
    724 def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
    725   source_version = OPTIONS.source_info_dict["recovery_api_version"]
    726   target_version = OPTIONS.target_info_dict["recovery_api_version"]
    727 
    728   if source_version == 0:
    729     print ("WARNING: generating edify script for a source that "
    730            "can't install it.")
    731   script = edify_generator.EdifyGenerator(
    732       source_version, OPTIONS.target_info_dict,
    733       fstab=OPTIONS.source_info_dict["fstab"])
    734 
    735   metadata = {
    736       "pre-device": GetBuildProp("ro.product.device",
    737                                  OPTIONS.source_info_dict),
    738       "post-timestamp": GetBuildProp("ro.build.date.utc",
    739                                      OPTIONS.target_info_dict),
    740   }
    741 
    742   device_specific = common.DeviceSpecificParams(
    743       source_zip=source_zip,
    744       source_version=source_version,
    745       target_zip=target_zip,
    746       target_version=target_version,
    747       output_zip=output_zip,
    748       script=script,
    749       metadata=metadata,
    750       info_dict=OPTIONS.info_dict)
    751 
    752   # TODO: Currently this works differently from WriteIncrementalOTAPackage().
    753   # This function doesn't consider thumbprints when writing
    754   # metadata["pre/post-build"]. One possible reason is that the current
    755   # devices with thumbprints are all using file-based OTAs. Long term we
    756   # should factor out the common parts into a shared one to avoid further
    757   # divergence.
    758   source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
    759   target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
    760   metadata["pre-build"] = source_fp
    761   metadata["post-build"] = target_fp
    762 
    763   source_boot = common.GetBootableImage(
    764       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
    765       OPTIONS.source_info_dict)
    766   target_boot = common.GetBootableImage(
    767       "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
    768   updating_boot = (not OPTIONS.two_step and
    769                    (source_boot.data != target_boot.data))
    770 
    771   target_recovery = common.GetBootableImage(
    772       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
    773 
    774   system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
    775   system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
    776 
    777   blockimgdiff_version = 1
    778   if OPTIONS.info_dict:
    779     blockimgdiff_version = max(
    780         int(i) for i in
    781         OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
    782 
    783   system_diff = common.BlockDifference("system", system_tgt, system_src,
    784                                        version=blockimgdiff_version)
    785 
    786   if HasVendorPartition(target_zip):
    787     if not HasVendorPartition(source_zip):
    788       raise RuntimeError("can't generate incremental that adds /vendor")
    789     vendor_src = GetImage("vendor", OPTIONS.source_tmp,
    790                           OPTIONS.source_info_dict)
    791     vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
    792                           OPTIONS.target_info_dict)
    793     vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
    794                                          version=blockimgdiff_version)
    795   else:
    796     vendor_diff = None
    797 
    798   oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
    799   recovery_mount_options = OPTIONS.source_info_dict.get(
    800       "recovery_mount_options")
    801   oem_dict = None
    802   if oem_props is not None and len(oem_props) > 0:
    803     if OPTIONS.oem_source is None:
    804       raise common.ExternalError("OEM source required for this build")
    805     script.Mount("/oem", recovery_mount_options)
    806     oem_dict = common.LoadDictionaryFromLines(
    807         open(OPTIONS.oem_source).readlines())
    808 
    809   AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
    810   device_specific.IncrementalOTA_Assertions()
    811 
    812   # Two-step incremental package strategy (in chronological order,
    813   # which is *not* the order in which the generated script has
    814   # things):
    815   #
    816   # if stage is not "2/3" or "3/3":
    817   #    do verification on current system
    818   #    write recovery image to boot partition
    819   #    set stage to "2/3"
    820   #    reboot to boot partition and restart recovery
    821   # else if stage is "2/3":
    822   #    write recovery image to recovery partition
    823   #    set stage to "3/3"
    824   #    reboot to recovery partition and restart recovery
    825   # else:
    826   #    (stage must be "3/3")
    827   #    perform update:
    828   #       patch system files, etc.
    829   #       force full install of new boot image
    830   #       set up system to update recovery partition on first boot
    831   #    complete script normally
    832   #    (allow recovery to mark itself finished and reboot)
    833 
    834   if OPTIONS.two_step:
    835     if not OPTIONS.source_info_dict.get("multistage_support", None):
    836       assert False, "two-step packages not supported by this build"
    837     fs = OPTIONS.source_info_dict["fstab"]["/misc"]
    838     assert fs.fs_type.upper() == "EMMC", \
    839         "two-step packages only supported on devices with EMMC /misc partitions"
    840     bcb_dev = {"bcb_dev": fs.device}
    841     common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
    842     script.AppendExtra("""
    843 if get_stage("%(bcb_dev)s") == "2/3" then
    844 """ % bcb_dev)
    845     script.AppendExtra("sleep(20);\n")
    846     script.WriteRawImage("/recovery", "recovery.img")
    847     script.AppendExtra("""
    848 set_stage("%(bcb_dev)s", "3/3");
    849 reboot_now("%(bcb_dev)s", "recovery");
    850 else if get_stage("%(bcb_dev)s") != "3/3" then
    851 """ % bcb_dev)
    852 
    853   # Dump fingerprints
    854   script.Print("Source: %s" % CalculateFingerprint(
    855       oem_props, oem_dict, OPTIONS.source_info_dict))
    856   script.Print("Target: %s" % CalculateFingerprint(
    857       oem_props, oem_dict, OPTIONS.target_info_dict))
    858 
    859   script.Print("Verifying current system...")
    860 
    861   device_specific.IncrementalOTA_VerifyBegin()
    862 
    863   if oem_props is None:
    864     # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
    865     # patching on a device that's already on the target build will damage the
    866     # system. Because operations like move don't check the block state, they
    867     # always apply the changes unconditionally.
    868     if blockimgdiff_version <= 2:
    869       script.AssertSomeFingerprint(source_fp)
    870     else:
    871       script.AssertSomeFingerprint(source_fp, target_fp)
    872   else:
    873     if blockimgdiff_version <= 2:
    874       script.AssertSomeThumbprint(
    875           GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
    876     else:
    877       script.AssertSomeThumbprint(
    878           GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
    879           GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
    880 
    881   if updating_boot:
    882     boot_type, boot_device = common.GetTypeAndDevice(
    883         "/boot", OPTIONS.source_info_dict)
    884     d = common.Difference(target_boot, source_boot)
    885     _, _, d = d.ComputePatch()
    886     if d is None:
    887       include_full_boot = True
    888       common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    889     else:
    890       include_full_boot = False
    891 
    892       print "boot      target: %d  source: %d  diff: %d" % (
    893           target_boot.size, source_boot.size, len(d))
    894 
    895       common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
    896 
    897       script.PatchCheck("%s:%s:%d:%s:%d:%s" %
    898                         (boot_type, boot_device,
    899                          source_boot.size, source_boot.sha1,
    900                          target_boot.size, target_boot.sha1))
    901 
    902   device_specific.IncrementalOTA_VerifyEnd()
    903 
    904   if OPTIONS.two_step:
    905     script.WriteRawImage("/boot", "recovery.img")
    906     script.AppendExtra("""
    907 set_stage("%(bcb_dev)s", "2/3");
    908 reboot_now("%(bcb_dev)s", "");
    909 else
    910 """ % bcb_dev)
    911 
    912   # Verify the existing partitions.
    913   system_diff.WriteVerifyScript(script)
    914   if vendor_diff:
    915     vendor_diff.WriteVerifyScript(script)
    916 
    917   script.Comment("---- start making changes here ----")
    918 
    919   device_specific.IncrementalOTA_InstallBegin()
    920 
    921   system_diff.WriteScript(script, output_zip,
    922                           progress=0.8 if vendor_diff else 0.9)
    923   if vendor_diff:
    924     vendor_diff.WriteScript(script, output_zip, progress=0.1)
    925 
    926   if OPTIONS.two_step:
    927     common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    928     script.WriteRawImage("/boot", "boot.img")
    929     print "writing full boot image (forced by two-step mode)"
    930 
    931   if not OPTIONS.two_step:
    932     if updating_boot:
    933       if include_full_boot:
    934         print "boot image changed; including full."
    935         script.Print("Installing boot image...")
    936         script.WriteRawImage("/boot", "boot.img")
    937       else:
    938         # Produce the boot image by applying a patch to the current
    939         # contents of the boot partition, and write it back to the
    940         # partition.
    941         print "boot image changed; including patch."
    942         script.Print("Patching boot image...")
    943         script.ShowProgress(0.1, 10)
    944         script.ApplyPatch("%s:%s:%d:%s:%d:%s"
    945                           % (boot_type, boot_device,
    946                              source_boot.size, source_boot.sha1,
    947                              target_boot.size, target_boot.sha1),
    948                           "-",
    949                           target_boot.size, target_boot.sha1,
    950                           source_boot.sha1, "patch/boot.img.p")
    951     else:
    952       print "boot image unchanged; skipping."
    953 
    954   # Do device-specific installation (eg, write radio image).
    955   device_specific.IncrementalOTA_InstallEnd()
    956 
    957   if OPTIONS.extra_script is not None:
    958     script.AppendExtra(OPTIONS.extra_script)
    959 
    960   if OPTIONS.wipe_user_data:
    961     script.Print("Erasing user data...")
    962     script.FormatPartition("/data")
    963 
    964   if OPTIONS.two_step:
    965     script.AppendExtra("""
    966 set_stage("%(bcb_dev)s", "");
    967 endif;
    968 endif;
    969 """ % bcb_dev)
    970 
    971   script.SetProgress(1)
    972   script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
    973   WriteMetadata(metadata, output_zip)
    974 
    975 
    976 class FileDifference(object):
    977   def __init__(self, partition, source_zip, target_zip, output_zip):
    978     self.deferred_patch_list = None
    979     print "Loading target..."
    980     self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
    981     print "Loading source..."
    982     self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
    983 
    984     self.verbatim_targets = verbatim_targets = []
    985     self.patch_list = patch_list = []
    986     diffs = []
    987     self.renames = renames = {}
    988     known_paths = set()
    989     largest_source_size = 0
    990 
    991     matching_file_cache = {}
    992     for fn, sf in source_data.items():
    993       assert fn == sf.name
    994       matching_file_cache["path:" + fn] = sf
    995       if fn in target_data.keys():
    996         AddToKnownPaths(fn, known_paths)
    997       # Only allow eligibility for filename/sha matching
    998       # if there isn't a perfect path match.
    999       if target_data.get(sf.name) is None:
   1000         matching_file_cache["file:" + fn.split("/")[-1]] = sf
   1001         matching_file_cache["sha:" + sf.sha1] = sf
   1002 
   1003     for fn in sorted(target_data.keys()):
   1004       tf = target_data[fn]
   1005       assert fn == tf.name
   1006       sf = ClosestFileMatch(tf, matching_file_cache, renames)
   1007       if sf is not None and sf.name != tf.name:
   1008         print "File has moved from " + sf.name + " to " + tf.name
   1009         renames[sf.name] = tf
   1010 
   1011       if sf is None or fn in OPTIONS.require_verbatim:
   1012         # This file should be included verbatim
   1013         if fn in OPTIONS.prohibit_verbatim:
   1014           raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
   1015         print "send", fn, "verbatim"
   1016         tf.AddToZip(output_zip)
   1017         verbatim_targets.append((fn, tf.size, tf.sha1))
   1018         if fn in target_data.keys():
   1019           AddToKnownPaths(fn, known_paths)
   1020       elif tf.sha1 != sf.sha1:
   1021         # File is different; consider sending as a patch
   1022         diffs.append(common.Difference(tf, sf))
   1023       else:
   1024         # Target file data identical to source (may still be renamed)
   1025         pass
   1026 
   1027     common.ComputeDifferences(diffs)
   1028 
   1029     for diff in diffs:
   1030       tf, sf, d = diff.GetPatch()
   1031       path = "/".join(tf.name.split("/")[:-1])
   1032       if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
   1033           path not in known_paths:
   1034         # patch is almost as big as the file; don't bother patching
   1035         # or a patch + rename cannot take place due to the target
   1036         # directory not existing
   1037         tf.AddToZip(output_zip)
   1038         verbatim_targets.append((tf.name, tf.size, tf.sha1))
   1039         if sf.name in renames:
   1040           del renames[sf.name]
   1041         AddToKnownPaths(tf.name, known_paths)
   1042       else:
   1043         common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
   1044         patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
   1045         largest_source_size = max(largest_source_size, sf.size)
   1046 
   1047     self.largest_source_size = largest_source_size
   1048 
   1049   def EmitVerification(self, script):
   1050     so_far = 0
   1051     for tf, sf, _, _ in self.patch_list:
   1052       if tf.name != sf.name:
   1053         script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
   1054       script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
   1055       so_far += sf.size
   1056     return so_far
   1057 
   1058   def EmitExplicitTargetVerification(self, script):
   1059     for fn, _, sha1 in self.verbatim_targets:
   1060       if fn[-1] != "/":
   1061         script.FileCheck("/"+fn, sha1)
   1062     for tf, _, _, _ in self.patch_list:
   1063       script.FileCheck(tf.name, tf.sha1)
   1064 
   1065   def RemoveUnneededFiles(self, script, extras=()):
   1066     script.DeleteFiles(
   1067         ["/" + i[0] for i in self.verbatim_targets] +
   1068         ["/" + i for i in sorted(self.source_data)
   1069          if i not in self.target_data and i not in self.renames] +
   1070         list(extras))
   1071 
   1072   def TotalPatchSize(self):
   1073     return sum(i[1].size for i in self.patch_list)
   1074 
   1075   def EmitPatches(self, script, total_patch_size, so_far):
   1076     self.deferred_patch_list = deferred_patch_list = []
   1077     for item in self.patch_list:
   1078       tf, sf, _, _ = item
   1079       if tf.name == "system/build.prop":
   1080         deferred_patch_list.append(item)
   1081         continue
   1082       if sf.name != tf.name:
   1083         script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
   1084       script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
   1085                         "patch/" + sf.name + ".p")
   1086       so_far += tf.size
   1087       script.SetProgress(so_far / total_patch_size)
   1088     return so_far
   1089 
   1090   def EmitDeferredPatches(self, script):
   1091     for item in self.deferred_patch_list:
   1092       tf, sf, _, _ = item
   1093       script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
   1094                         "patch/" + sf.name + ".p")
   1095     script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
   1096 
   1097   def EmitRenames(self, script):
   1098     if len(self.renames) > 0:
   1099       script.Print("Renaming files...")
   1100       for src, tgt in self.renames.iteritems():
   1101         print "Renaming " + src + " to " + tgt.name
   1102         script.RenameFile(src, tgt.name)
   1103 
   1104 
   1105 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
   1106   target_has_recovery_patch = HasRecoveryPatch(target_zip)
   1107   source_has_recovery_patch = HasRecoveryPatch(source_zip)
   1108 
   1109   if (OPTIONS.block_based and
   1110       target_has_recovery_patch and
   1111       source_has_recovery_patch):
   1112     return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
   1113 
   1114   source_version = OPTIONS.source_info_dict["recovery_api_version"]
   1115   target_version = OPTIONS.target_info_dict["recovery_api_version"]
   1116 
   1117   if source_version == 0:
   1118     print ("WARNING: generating edify script for a source that "
   1119            "can't install it.")
   1120   script = edify_generator.EdifyGenerator(
   1121       source_version, OPTIONS.target_info_dict,
   1122       fstab=OPTIONS.source_info_dict["fstab"])
   1123 
   1124   oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
   1125   recovery_mount_options = OPTIONS.source_info_dict.get(
   1126       "recovery_mount_options")
   1127   oem_dict = None
   1128   if oem_props is not None and len(oem_props) > 0:
   1129     if OPTIONS.oem_source is None:
   1130       raise common.ExternalError("OEM source required for this build")
   1131     script.Mount("/oem", recovery_mount_options)
   1132     oem_dict = common.LoadDictionaryFromLines(
   1133         open(OPTIONS.oem_source).readlines())
   1134 
   1135   metadata = {
   1136       "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
   1137                                    OPTIONS.source_info_dict),
   1138       "post-timestamp": GetBuildProp("ro.build.date.utc",
   1139                                      OPTIONS.target_info_dict),
   1140   }
   1141 
   1142   device_specific = common.DeviceSpecificParams(
   1143       source_zip=source_zip,
   1144       source_version=source_version,
   1145       target_zip=target_zip,
   1146       target_version=target_version,
   1147       output_zip=output_zip,
   1148       script=script,
   1149       metadata=metadata,
   1150       info_dict=OPTIONS.info_dict)
   1151 
   1152   system_diff = FileDifference("system", source_zip, target_zip, output_zip)
   1153   script.Mount("/system", recovery_mount_options)
   1154   if HasVendorPartition(target_zip):
   1155     vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
   1156     script.Mount("/vendor", recovery_mount_options)
   1157   else:
   1158     vendor_diff = None
   1159 
   1160   target_fp = CalculateFingerprint(oem_props, oem_dict,
   1161                                    OPTIONS.target_info_dict)
   1162   source_fp = CalculateFingerprint(oem_props, oem_dict,
   1163                                    OPTIONS.source_info_dict)
   1164 
   1165   if oem_props is None:
   1166     script.AssertSomeFingerprint(source_fp, target_fp)
   1167   else:
   1168     script.AssertSomeThumbprint(
   1169         GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
   1170         GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
   1171 
   1172   metadata["pre-build"] = source_fp
   1173   metadata["post-build"] = target_fp
   1174 
   1175   source_boot = common.GetBootableImage(
   1176       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
   1177       OPTIONS.source_info_dict)
   1178   target_boot = common.GetBootableImage(
   1179       "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
   1180   updating_boot = (not OPTIONS.two_step and
   1181                    (source_boot.data != target_boot.data))
   1182 
   1183   source_recovery = common.GetBootableImage(
   1184       "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
   1185       OPTIONS.source_info_dict)
   1186   target_recovery = common.GetBootableImage(
   1187       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
   1188   updating_recovery = (source_recovery.data != target_recovery.data)
   1189 
   1190   # Here's how we divide up the progress bar:
   1191   #  0.1 for verifying the start state (PatchCheck calls)
   1192   #  0.8 for applying patches (ApplyPatch calls)
   1193   #  0.1 for unpacking verbatim files, symlinking, and doing the
   1194   #      device-specific commands.
   1195 
   1196   AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
   1197   device_specific.IncrementalOTA_Assertions()
   1198 
   1199   # Two-step incremental package strategy (in chronological order,
   1200   # which is *not* the order in which the generated script has
   1201   # things):
   1202   #
   1203   # if stage is not "2/3" or "3/3":
   1204   #    do verification on current system
   1205   #    write recovery image to boot partition
   1206   #    set stage to "2/3"
   1207   #    reboot to boot partition and restart recovery
   1208   # else if stage is "2/3":
   1209   #    write recovery image to recovery partition
   1210   #    set stage to "3/3"
   1211   #    reboot to recovery partition and restart recovery
   1212   # else:
   1213   #    (stage must be "3/3")
   1214   #    perform update:
   1215   #       patch system files, etc.
   1216   #       force full install of new boot image
   1217   #       set up system to update recovery partition on first boot
   1218   #    complete script normally
   1219   #    (allow recovery to mark itself finished and reboot)
   1220 
   1221   if OPTIONS.two_step:
   1222     if not OPTIONS.source_info_dict.get("multistage_support", None):
   1223       assert False, "two-step packages not supported by this build"
   1224     fs = OPTIONS.source_info_dict["fstab"]["/misc"]
   1225     assert fs.fs_type.upper() == "EMMC", \
   1226         "two-step packages only supported on devices with EMMC /misc partitions"
   1227     bcb_dev = {"bcb_dev": fs.device}
   1228     common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
   1229     script.AppendExtra("""
   1230 if get_stage("%(bcb_dev)s") == "2/3" then
   1231 """ % bcb_dev)
   1232     script.AppendExtra("sleep(20);\n")
   1233     script.WriteRawImage("/recovery", "recovery.img")
   1234     script.AppendExtra("""
   1235 set_stage("%(bcb_dev)s", "3/3");
   1236 reboot_now("%(bcb_dev)s", "recovery");
   1237 else if get_stage("%(bcb_dev)s") != "3/3" then
   1238 """ % bcb_dev)
   1239 
   1240   # Dump fingerprints
   1241   script.Print("Source: %s" % (source_fp,))
   1242   script.Print("Target: %s" % (target_fp,))
   1243 
   1244   script.Print("Verifying current system...")
   1245 
   1246   device_specific.IncrementalOTA_VerifyBegin()
   1247 
   1248   script.ShowProgress(0.1, 0)
   1249   so_far = system_diff.EmitVerification(script)
   1250   if vendor_diff:
   1251     so_far += vendor_diff.EmitVerification(script)
   1252 
   1253   if updating_boot:
   1254     d = common.Difference(target_boot, source_boot)
   1255     _, _, d = d.ComputePatch()
   1256     print "boot      target: %d  source: %d  diff: %d" % (
   1257         target_boot.size, source_boot.size, len(d))
   1258 
   1259     common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
   1260 
   1261     boot_type, boot_device = common.GetTypeAndDevice(
   1262         "/boot", OPTIONS.source_info_dict)
   1263 
   1264     script.PatchCheck("%s:%s:%d:%s:%d:%s" %
   1265                       (boot_type, boot_device,
   1266                        source_boot.size, source_boot.sha1,
   1267                        target_boot.size, target_boot.sha1))
   1268     so_far += source_boot.size
   1269 
   1270   size = []
   1271   if system_diff.patch_list:
   1272     size.append(system_diff.largest_source_size)
   1273   if vendor_diff:
   1274     if vendor_diff.patch_list:
   1275       size.append(vendor_diff.largest_source_size)
   1276   if size or updating_recovery or updating_boot:
   1277     script.CacheFreeSpaceCheck(max(size))
   1278 
   1279   device_specific.IncrementalOTA_VerifyEnd()
   1280 
   1281   if OPTIONS.two_step:
   1282     script.WriteRawImage("/boot", "recovery.img")
   1283     script.AppendExtra("""
   1284 set_stage("%(bcb_dev)s", "2/3");
   1285 reboot_now("%(bcb_dev)s", "");
   1286 else
   1287 """ % bcb_dev)
   1288 
   1289   script.Comment("---- start making changes here ----")
   1290 
   1291   device_specific.IncrementalOTA_InstallBegin()
   1292 
   1293   if OPTIONS.two_step:
   1294     common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
   1295     script.WriteRawImage("/boot", "boot.img")
   1296     print "writing full boot image (forced by two-step mode)"
   1297 
   1298   script.Print("Removing unneeded files...")
   1299   system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
   1300   if vendor_diff:
   1301     vendor_diff.RemoveUnneededFiles(script)
   1302 
   1303   script.ShowProgress(0.8, 0)
   1304   total_patch_size = 1.0 + system_diff.TotalPatchSize()
   1305   if vendor_diff:
   1306     total_patch_size += vendor_diff.TotalPatchSize()
   1307   if updating_boot:
   1308     total_patch_size += target_boot.size
   1309 
   1310   script.Print("Patching system files...")
   1311   so_far = system_diff.EmitPatches(script, total_patch_size, 0)
   1312   if vendor_diff:
   1313     script.Print("Patching vendor files...")
   1314     so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
   1315 
   1316   if not OPTIONS.two_step:
   1317     if updating_boot:
   1318       # Produce the boot image by applying a patch to the current
   1319       # contents of the boot partition, and write it back to the
   1320       # partition.
   1321       script.Print("Patching boot image...")
   1322       script.ApplyPatch("%s:%s:%d:%s:%d:%s"
   1323                         % (boot_type, boot_device,
   1324                            source_boot.size, source_boot.sha1,
   1325                            target_boot.size, target_boot.sha1),
   1326                         "-",
   1327                         target_boot.size, target_boot.sha1,
   1328                         source_boot.sha1, "patch/boot.img.p")
   1329       so_far += target_boot.size
   1330       script.SetProgress(so_far / total_patch_size)
   1331       print "boot image changed; including."
   1332     else:
   1333       print "boot image unchanged; skipping."
   1334 
   1335   system_items = ItemSet("system", "META/filesystem_config.txt")
   1336   if vendor_diff:
   1337     vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
   1338 
   1339   if updating_recovery:
   1340     # Recovery is generated as a patch using both the boot image
   1341     # (which contains the same linux kernel as recovery) and the file
   1342     # /system/etc/recovery-resource.dat (which contains all the images
   1343     # used in the recovery UI) as sources.  This lets us minimize the
   1344     # size of the patch, which must be included in every OTA package.
   1345     #
   1346     # For older builds where recovery-resource.dat is not present, we
   1347     # use only the boot image as the source.
   1348 
   1349     if not target_has_recovery_patch:
   1350       def output_sink(fn, data):
   1351         common.ZipWriteStr(output_zip, "recovery/" + fn, data)
   1352         system_items.Get("system/" + fn)
   1353 
   1354       common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
   1355                                target_recovery, target_boot)
   1356       script.DeleteFiles(["/system/recovery-from-boot.p",
   1357                           "/system/etc/install-recovery.sh"])
   1358     print "recovery image changed; including as patch from boot."
   1359   else:
   1360     print "recovery image unchanged; skipping."
   1361 
   1362   script.ShowProgress(0.1, 10)
   1363 
   1364   target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
   1365   if vendor_diff:
   1366     target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
   1367 
   1368   temp_script = script.MakeTemporary()
   1369   system_items.GetMetadata(target_zip)
   1370   system_items.Get("system").SetPermissions(temp_script)
   1371   if vendor_diff:
   1372     vendor_items.GetMetadata(target_zip)
   1373     vendor_items.Get("vendor").SetPermissions(temp_script)
   1374 
   1375   # Note that this call will mess up the trees of Items, so make sure
   1376   # we're done with them.
   1377   source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
   1378   if vendor_diff:
   1379     source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
   1380 
   1381   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
   1382   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
   1383 
   1384   # Delete all the symlinks in source that aren't in target.  This
   1385   # needs to happen before verbatim files are unpacked, in case a
   1386   # symlink in the source is replaced by a real file in the target.
   1387 
   1388   # If a symlink in the source will be replaced by a regular file, we cannot
   1389   # delete the symlink/file in case the package gets applied again. For such
   1390   # a symlink, we prepend a sha1_check() to detect if it has been updated.
   1391   # (Bug: 23646151)
   1392   replaced_symlinks = dict()
   1393   if system_diff:
   1394     for i in system_diff.verbatim_targets:
   1395       replaced_symlinks["/%s" % (i[0],)] = i[2]
   1396   if vendor_diff:
   1397     for i in vendor_diff.verbatim_targets:
   1398       replaced_symlinks["/%s" % (i[0],)] = i[2]
   1399 
   1400   if system_diff:
   1401     for tf in system_diff.renames.values():
   1402       replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
   1403   if vendor_diff:
   1404     for tf in vendor_diff.renames.values():
   1405       replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
   1406 
   1407   always_delete = []
   1408   may_delete = []
   1409   for dest, link in source_symlinks:
   1410     if link not in target_symlinks_d:
   1411       if link in replaced_symlinks:
   1412         may_delete.append((link, replaced_symlinks[link]))
   1413       else:
   1414         always_delete.append(link)
   1415   script.DeleteFiles(always_delete)
   1416   script.DeleteFilesIfNotMatching(may_delete)
   1417 
   1418   if system_diff.verbatim_targets:
   1419     script.Print("Unpacking new system files...")
   1420     script.UnpackPackageDir("system", "/system")
   1421   if vendor_diff and vendor_diff.verbatim_targets:
   1422     script.Print("Unpacking new vendor files...")
   1423     script.UnpackPackageDir("vendor", "/vendor")
   1424 
   1425   if updating_recovery and not target_has_recovery_patch:
   1426     script.Print("Unpacking new recovery...")
   1427     script.UnpackPackageDir("recovery", "/system")
   1428 
   1429   system_diff.EmitRenames(script)
   1430   if vendor_diff:
   1431     vendor_diff.EmitRenames(script)
   1432 
   1433   script.Print("Symlinks and permissions...")
   1434 
   1435   # Create all the symlinks that don't already exist, or point to
   1436   # somewhere different than what we want.  Delete each symlink before
   1437   # creating it, since the 'symlink' command won't overwrite.
   1438   to_create = []
   1439   for dest, link in target_symlinks:
   1440     if link in source_symlinks_d:
   1441       if dest != source_symlinks_d[link]:
   1442         to_create.append((dest, link))
   1443     else:
   1444       to_create.append((dest, link))
   1445   script.DeleteFiles([i[1] for i in to_create])
   1446   script.MakeSymlinks(to_create)
   1447 
   1448   # Now that the symlinks are created, we can set all the
   1449   # permissions.
   1450   script.AppendScript(temp_script)
   1451 
   1452   # Do device-specific installation (eg, write radio image).
   1453   device_specific.IncrementalOTA_InstallEnd()
   1454 
   1455   if OPTIONS.extra_script is not None:
   1456     script.AppendExtra(OPTIONS.extra_script)
   1457 
   1458   # Patch the build.prop file last, so if something fails but the
   1459   # device can still come up, it appears to be the old build and will
   1460   # get set the OTA package again to retry.
   1461   script.Print("Patching remaining system files...")
   1462   system_diff.EmitDeferredPatches(script)
   1463 
   1464   if OPTIONS.wipe_user_data:
   1465     script.Print("Erasing user data...")
   1466     script.FormatPartition("/data")
   1467 
   1468   if OPTIONS.two_step:
   1469     script.AppendExtra("""
   1470 set_stage("%(bcb_dev)s", "");
   1471 endif;
   1472 endif;
   1473 """ % bcb_dev)
   1474 
   1475   if OPTIONS.verify and system_diff:
   1476     script.Print("Remounting and verifying system partition files...")
   1477     script.Unmount("/system")
   1478     script.Mount("/system")
   1479     system_diff.EmitExplicitTargetVerification(script)
   1480 
   1481   if OPTIONS.verify and vendor_diff:
   1482     script.Print("Remounting and verifying vendor partition files...")
   1483     script.Unmount("/vendor")
   1484     script.Mount("/vendor")
   1485     vendor_diff.EmitExplicitTargetVerification(script)
   1486   script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
   1487 
   1488   WriteMetadata(metadata, output_zip)
   1489 
   1490 
   1491 def main(argv):
   1492 
   1493   def option_handler(o, a):
   1494     if o == "--board_config":
   1495       pass   # deprecated
   1496     elif o in ("-k", "--package_key"):
   1497       OPTIONS.package_key = a
   1498     elif o in ("-i", "--incremental_from"):
   1499       OPTIONS.incremental_source = a
   1500     elif o == "--full_radio":
   1501       OPTIONS.full_radio = True
   1502     elif o in ("-w", "--wipe_user_data"):
   1503       OPTIONS.wipe_user_data = True
   1504     elif o in ("-n", "--no_prereq"):
   1505       OPTIONS.omit_prereq = True
   1506     elif o in ("-o", "--oem_settings"):
   1507       OPTIONS.oem_source = a
   1508     elif o in ("-e", "--extra_script"):
   1509       OPTIONS.extra_script = a
   1510     elif o in ("-a", "--aslr_mode"):
   1511       if a in ("on", "On", "true", "True", "yes", "Yes"):
   1512         OPTIONS.aslr_mode = True
   1513       else:
   1514         OPTIONS.aslr_mode = False
   1515     elif o in ("-t", "--worker_threads"):
   1516       if a.isdigit():
   1517         OPTIONS.worker_threads = int(a)
   1518       else:
   1519         raise ValueError("Cannot parse value %r for option %r - only "
   1520                          "integers are allowed." % (a, o))
   1521     elif o in ("-2", "--two_step"):
   1522       OPTIONS.two_step = True
   1523     elif o == "--no_signing":
   1524       OPTIONS.no_signing = True
   1525     elif o == "--verify":
   1526       OPTIONS.verify = True
   1527     elif o == "--block":
   1528       OPTIONS.block_based = True
   1529     elif o in ("-b", "--binary"):
   1530       OPTIONS.updater_binary = a
   1531     elif o in ("--no_fallback_to_full",):
   1532       OPTIONS.fallback_to_full = False
   1533     else:
   1534       return False
   1535     return True
   1536 
   1537   args = common.ParseOptions(argv, __doc__,
   1538                              extra_opts="b:k:i:d:wne:t:a:2o:",
   1539                              extra_long_opts=[
   1540                                  "board_config=",
   1541                                  "package_key=",
   1542                                  "incremental_from=",
   1543                                  "full_radio",
   1544                                  "wipe_user_data",
   1545                                  "no_prereq",
   1546                                  "extra_script=",
   1547                                  "worker_threads=",
   1548                                  "aslr_mode=",
   1549                                  "two_step",
   1550                                  "no_signing",
   1551                                  "block",
   1552                                  "binary=",
   1553                                  "oem_settings=",
   1554                                  "verify",
   1555                                  "no_fallback_to_full",
   1556                              ], extra_option_handler=option_handler)
   1557 
   1558   if len(args) != 2:
   1559     common.Usage(__doc__)
   1560     sys.exit(1)
   1561 
   1562   if OPTIONS.extra_script is not None:
   1563     OPTIONS.extra_script = open(OPTIONS.extra_script).read()
   1564 
   1565   print "unzipping target target-files..."
   1566   OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
   1567 
   1568   OPTIONS.target_tmp = OPTIONS.input_tmp
   1569   OPTIONS.info_dict = common.LoadInfoDict(input_zip)
   1570 
   1571   # If this image was originally labelled with SELinux contexts, make sure we
   1572   # also apply the labels in our new image. During building, the "file_contexts"
   1573   # is in the out/ directory tree, but for repacking from target-files.zip it's
   1574   # in the root directory of the ramdisk.
   1575   if "selinux_fc" in OPTIONS.info_dict:
   1576     OPTIONS.info_dict["selinux_fc"] = os.path.join(
   1577         OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
   1578 
   1579   if OPTIONS.verbose:
   1580     print "--- target info ---"
   1581     common.DumpInfoDict(OPTIONS.info_dict)
   1582 
   1583   # If the caller explicitly specified the device-specific extensions
   1584   # path via -s/--device_specific, use that.  Otherwise, use
   1585   # META/releasetools.py if it is present in the target target_files.
   1586   # Otherwise, take the path of the file from 'tool_extensions' in the
   1587   # info dict and look for that in the local filesystem, relative to
   1588   # the current directory.
   1589 
   1590   if OPTIONS.device_specific is None:
   1591     from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
   1592     if os.path.exists(from_input):
   1593       print "(using device-specific extensions from target_files)"
   1594       OPTIONS.device_specific = from_input
   1595     else:
   1596       OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
   1597 
   1598   if OPTIONS.device_specific is not None:
   1599     OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
   1600 
   1601   while True:
   1602 
   1603     if OPTIONS.no_signing:
   1604       if os.path.exists(args[1]):
   1605         os.unlink(args[1])
   1606       output_zip = zipfile.ZipFile(args[1], "w",
   1607                                    compression=zipfile.ZIP_DEFLATED)
   1608     else:
   1609       temp_zip_file = tempfile.NamedTemporaryFile()
   1610       output_zip = zipfile.ZipFile(temp_zip_file, "w",
   1611                                    compression=zipfile.ZIP_DEFLATED)
   1612 
   1613     if OPTIONS.incremental_source is None:
   1614       WriteFullOTAPackage(input_zip, output_zip)
   1615       if OPTIONS.package_key is None:
   1616         OPTIONS.package_key = OPTIONS.info_dict.get(
   1617             "default_system_dev_certificate",
   1618             "build/target/product/security/testkey")
   1619       common.ZipClose(output_zip)
   1620       break
   1621 
   1622     else:
   1623       print "unzipping source target-files..."
   1624       OPTIONS.source_tmp, source_zip = common.UnzipTemp(
   1625           OPTIONS.incremental_source)
   1626       OPTIONS.target_info_dict = OPTIONS.info_dict
   1627       OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
   1628       if "selinux_fc" in OPTIONS.source_info_dict:
   1629         OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
   1630             OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
   1631       if OPTIONS.package_key is None:
   1632         OPTIONS.package_key = OPTIONS.source_info_dict.get(
   1633             "default_system_dev_certificate",
   1634             "build/target/product/security/testkey")
   1635       if OPTIONS.verbose:
   1636         print "--- source info ---"
   1637         common.DumpInfoDict(OPTIONS.source_info_dict)
   1638       try:
   1639         WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
   1640         common.ZipClose(output_zip)
   1641         break
   1642       except ValueError:
   1643         if not OPTIONS.fallback_to_full:
   1644           raise
   1645         print "--- failed to build incremental; falling back to full ---"
   1646         OPTIONS.incremental_source = None
   1647         common.ZipClose(output_zip)
   1648 
   1649   if not OPTIONS.no_signing:
   1650     SignOutput(temp_zip_file.name, args[1])
   1651     temp_zip_file.close()
   1652 
   1653   print "done."
   1654 
   1655 
   1656 if __name__ == '__main__':
   1657   try:
   1658     common.CloseInheritedPipes()
   1659     main(sys.argv[1:])
   1660   except common.ExternalError as e:
   1661     print
   1662     print "   ERROR: %s" % (e,)
   1663     print
   1664     sys.exit(1)
   1665   finally:
   1666     common.Cleanup()
   1667