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