ota_from_target_files.py revision 19241c11bd1cf039bcd74859426ad22282849912
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      Similar to --full_radio. When generating an incremental OTA, always
47      include a full copy of bootloader image.
48
49  -v  (--verify)
50      Remount and verify the checksums of the files written to the
51      system and vendor (if used) partitions.  Incremental builds only.
52
53  -o  (--oem_settings)  <file>
54      Use the file to specify the expected OEM-specific properties
55      on the OEM partition of the intended device.
56
57  -w  (--wipe_user_data)
58      Generate an OTA package that will wipe the user data partition
59      when installed.
60
61  -n  (--no_prereq)
62      Omit the timestamp prereq check normally included at the top of
63      the build scripts (used for developer OTA packages which
64      legitimately need to go back and forth).
65
66  -e  (--extra_script)  <file>
67      Insert the contents of file at the end of the update script.
68
69  -a  (--aslr_mode)  <on|off>
70      Specify whether to turn on ASLR for the package (on by default).
71
72  -2  (--two_step)
73      Generate a 'two-step' OTA package, where recovery is updated
74      first, so that any changes made to the system partition are done
75      using the new recovery (new kernel, etc.).
76
77  --block
78      Generate a block-based OTA if possible.  Will fall back to a
79      file-based OTA if the target_files is older and doesn't support
80      block-based OTAs.
81
82  -b  (--binary)  <file>
83      Use the given binary as the update-binary in the output package,
84      instead of the binary in the build's target_files.  Use for
85      development only.
86
87  -t  (--worker_threads) <int>
88      Specifies the number of worker-threads that will be used when
89      generating patches for incremental updates (defaults to 3).
90
91  --stash_threshold <float>
92      Specifies the threshold that will be used to compute the maximum
93      allowed stash size (defaults to 0.8).
94
95  --gen_verify
96      Generate an OTA package that verifies the partitions.
97
98  --log_diff <file>
99      Generate a log file that shows the differences in the source and target
100      builds for an incremental package. This option is only meaningful when
101      -i is specified.
102"""
103
104import sys
105
106if sys.hexversion < 0x02070000:
107  print >> sys.stderr, "Python 2.7 or newer is required."
108  sys.exit(1)
109
110import multiprocessing
111import os
112import subprocess
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.extra_script = None
130OPTIONS.aslr_mode = True
131OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
132if OPTIONS.worker_threads == 0:
133  OPTIONS.worker_threads = 1
134OPTIONS.two_step = False
135OPTIONS.no_signing = False
136OPTIONS.block_based = False
137OPTIONS.updater_binary = None
138OPTIONS.oem_source = None
139OPTIONS.fallback_to_full = True
140OPTIONS.full_radio = False
141OPTIONS.full_bootloader = False
142# Stash size cannot exceed cache_size * threshold.
143OPTIONS.cache_size = None
144OPTIONS.stash_threshold = 0.8
145OPTIONS.gen_verify = False
146OPTIONS.log_diff = None
147
148def MostPopularKey(d, default):
149  """Given a dict, return the key corresponding to the largest
150  value.  Returns 'default' if the dict is empty."""
151  x = [(v, k) for (k, v) in d.iteritems()]
152  if not x:
153    return default
154  x.sort()
155  return x[-1][1]
156
157
158def IsSymlink(info):
159  """Return true if the zipfile.ZipInfo object passed in represents a
160  symlink."""
161  return (info.external_attr >> 16) & 0o770000 == 0o120000
162
163def IsRegular(info):
164  """Return true if the zipfile.ZipInfo object passed in represents a
165  regular file."""
166  return (info.external_attr >> 16) & 0o770000 == 0o100000
167
168def ClosestFileMatch(src, tgtfiles, existing):
169  """Returns the closest file match between a source file and list
170     of potential matches.  The exact filename match is preferred,
171     then the sha1 is searched for, and finally a file with the same
172     basename is evaluated.  Rename support in the updater-binary is
173     required for the latter checks to be used."""
174
175  result = tgtfiles.get("path:" + src.name)
176  if result is not None:
177    return result
178
179  if not OPTIONS.target_info_dict.get("update_rename_support", False):
180    return None
181
182  if src.size < 1000:
183    return None
184
185  result = tgtfiles.get("sha1:" + src.sha1)
186  if result is not None and existing.get(result.name) is None:
187    return result
188  result = tgtfiles.get("file:" + src.name.split("/")[-1])
189  if result is not None and existing.get(result.name) is None:
190    return result
191  return None
192
193class ItemSet(object):
194  def __init__(self, partition, fs_config):
195    self.partition = partition
196    self.fs_config = fs_config
197    self.ITEMS = {}
198
199  def Get(self, name, is_dir=False):
200    if name not in self.ITEMS:
201      self.ITEMS[name] = Item(self, name, is_dir=is_dir)
202    return self.ITEMS[name]
203
204  def GetMetadata(self, input_zip):
205    # The target_files contains a record of what the uid,
206    # gid, and mode are supposed to be.
207    output = input_zip.read(self.fs_config)
208
209    for line in output.split("\n"):
210      if not line:
211        continue
212      columns = line.split()
213      name, uid, gid, mode = columns[:4]
214      selabel = None
215      capabilities = None
216
217      # After the first 4 columns, there are a series of key=value
218      # pairs. Extract out the fields we care about.
219      for element in columns[4:]:
220        key, value = element.split("=")
221        if key == "selabel":
222          selabel = value
223        if key == "capabilities":
224          capabilities = value
225
226      i = self.ITEMS.get(name, None)
227      if i is not None:
228        i.uid = int(uid)
229        i.gid = int(gid)
230        i.mode = int(mode, 8)
231        i.selabel = selabel
232        i.capabilities = capabilities
233        if i.is_dir:
234          i.children.sort(key=lambda i: i.name)
235
236    # Set metadata for the files generated by this script. For full recovery
237    # image at system/etc/recovery.img, it will be taken care by fs_config.
238    i = self.ITEMS.get("system/recovery-from-boot.p", None)
239    if i:
240      i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o644, None, None
241    i = self.ITEMS.get("system/etc/install-recovery.sh", None)
242    if i:
243      i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o544, None, None
244
245
246class Item(object):
247  """Items represent the metadata (user, group, mode) of files and
248  directories in the system image."""
249  def __init__(self, itemset, name, is_dir=False):
250    self.itemset = itemset
251    self.name = name
252    self.uid = None
253    self.gid = None
254    self.mode = None
255    self.selabel = None
256    self.capabilities = None
257    self.is_dir = is_dir
258    self.descendants = None
259    self.best_subtree = None
260
261    if name:
262      self.parent = itemset.Get(os.path.dirname(name), is_dir=True)
263      self.parent.children.append(self)
264    else:
265      self.parent = None
266    if self.is_dir:
267      self.children = []
268
269  def Dump(self, indent=0):
270    if self.uid is not None:
271      print "%s%s %d %d %o" % (
272          "  " * indent, self.name, self.uid, self.gid, self.mode)
273    else:
274      print "%s%s %s %s %s" % (
275          "  " * indent, self.name, self.uid, self.gid, self.mode)
276    if self.is_dir:
277      print "%s%s" % ("  "*indent, self.descendants)
278      print "%s%s" % ("  "*indent, self.best_subtree)
279      for i in self.children:
280        i.Dump(indent=indent+1)
281
282  def CountChildMetadata(self):
283    """Count up the (uid, gid, mode, selabel, capabilities) tuples for
284    all children and determine the best strategy for using set_perm_recursive
285    and set_perm to correctly chown/chmod all the files to their desired
286    values.  Recursively calls itself for all descendants.
287
288    Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count}
289    counting up all descendants of this node.  (dmode or fmode may be None.)
290    Also sets the best_subtree of each directory Item to the (uid, gid, dmode,
291    fmode, selabel, capabilities) tuple that will match the most descendants of
292    that Item.
293    """
294
295    assert self.is_dir
296    key = (self.uid, self.gid, self.mode, None, self.selabel,
297           self.capabilities)
298    self.descendants = {key: 1}
299    d = self.descendants
300    for i in self.children:
301      if i.is_dir:
302        for k, v in i.CountChildMetadata().iteritems():
303          d[k] = d.get(k, 0) + v
304      else:
305        k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities)
306        d[k] = d.get(k, 0) + 1
307
308    # Find the (uid, gid, dmode, fmode, selabel, capabilities)
309    # tuple that matches the most descendants.
310
311    # First, find the (uid, gid) pair that matches the most
312    # descendants.
313    ug = {}
314    for (uid, gid, _, _, _, _), count in d.iteritems():
315      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
316    ug = MostPopularKey(ug, (0, 0))
317
318    # Now find the dmode, fmode, selabel, and capabilities that match
319    # the most descendants with that (uid, gid), and choose those.
320    best_dmode = (0, 0o755)
321    best_fmode = (0, 0o644)
322    best_selabel = (0, None)
323    best_capabilities = (0, None)
324    for k, count in d.iteritems():
325      if k[:2] != ug:
326        continue
327      if k[2] is not None and count >= best_dmode[0]:
328        best_dmode = (count, k[2])
329      if k[3] is not None and count >= best_fmode[0]:
330        best_fmode = (count, k[3])
331      if k[4] is not None and count >= best_selabel[0]:
332        best_selabel = (count, k[4])
333      if k[5] is not None and count >= best_capabilities[0]:
334        best_capabilities = (count, k[5])
335    self.best_subtree = ug + (
336        best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1])
337
338    return d
339
340  def SetPermissions(self, script):
341    """Append set_perm/set_perm_recursive commands to 'script' to
342    set all permissions, users, and groups for the tree of files
343    rooted at 'self'."""
344
345    self.CountChildMetadata()
346
347    def recurse(item, current):
348      # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple
349      # that the current item (and all its children) have already been set to.
350      # We only need to issue set_perm/set_perm_recursive commands if we're
351      # supposed to be something different.
352      if item.is_dir:
353        if current != item.best_subtree:
354          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
355          current = item.best_subtree
356
357        if item.uid != current[0] or item.gid != current[1] or \
358           item.mode != current[2] or item.selabel != current[4] or \
359           item.capabilities != current[5]:
360          script.SetPermissions("/"+item.name, item.uid, item.gid,
361                                item.mode, item.selabel, item.capabilities)
362
363        for i in item.children:
364          recurse(i, current)
365      else:
366        if item.uid != current[0] or item.gid != current[1] or \
367               item.mode != current[3] or item.selabel != current[4] or \
368               item.capabilities != current[5]:
369          script.SetPermissions("/"+item.name, item.uid, item.gid,
370                                item.mode, item.selabel, item.capabilities)
371
372    recurse(self, (-1, -1, -1, -1, None, None))
373
374
375def CopyPartitionFiles(itemset, input_zip, output_zip=None, substitute=None):
376  """Copies files for the partition in the input zip to the output
377  zip.  Populates the Item class with their metadata, and returns a
378  list of symlinks.  output_zip may be None, in which case the copy is
379  skipped (but the other side effects still happen).  substitute is an
380  optional dict of {output filename: contents} to be output instead of
381  certain input files.
382  """
383
384  symlinks = []
385
386  partition = itemset.partition
387
388  for info in input_zip.infolist():
389    prefix = partition.upper() + "/"
390    if info.filename.startswith(prefix):
391      basefilename = info.filename[len(prefix):]
392      if IsSymlink(info):
393        symlinks.append((input_zip.read(info.filename),
394                         "/" + partition + "/" + basefilename))
395      else:
396        import copy
397        info2 = copy.copy(info)
398        fn = info2.filename = partition + "/" + basefilename
399        if substitute and fn in substitute and substitute[fn] is None:
400          continue
401        if output_zip is not None:
402          if substitute and fn in substitute:
403            data = substitute[fn]
404          else:
405            data = input_zip.read(info.filename)
406          common.ZipWriteStr(output_zip, info2, data)
407        if fn.endswith("/"):
408          itemset.Get(fn[:-1], is_dir=True)
409        else:
410          itemset.Get(fn)
411
412  symlinks.sort()
413  return symlinks
414
415
416def SignOutput(temp_zip_name, output_zip_name):
417  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
418  pw = key_passwords[OPTIONS.package_key]
419
420  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
421                  whole_file=True)
422
423
424def AppendAssertions(script, info_dict, oem_dict=None):
425  oem_props = info_dict.get("oem_fingerprint_properties")
426  if oem_props is None or len(oem_props) == 0:
427    device = GetBuildProp("ro.product.device", info_dict)
428    script.AssertDevice(device)
429  else:
430    if oem_dict is None:
431      raise common.ExternalError(
432          "No OEM file provided to answer expected assertions")
433    for prop in oem_props.split():
434      if oem_dict.get(prop) is None:
435        raise common.ExternalError(
436            "The OEM file is missing the property %s" % prop)
437      script.AssertOemProperty(prop, oem_dict.get(prop))
438
439
440def HasRecoveryPatch(target_files_zip):
441  namelist = [name for name in target_files_zip.namelist()]
442  return ("SYSTEM/recovery-from-boot.p" in namelist or
443          "SYSTEM/etc/recovery.img" in namelist)
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  # Place a copy of file_contexts.bin into the OTA package which will be used
607  # by the recovery program.
608  if "selinux_fc" in OPTIONS.info_dict:
609    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
610
611  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
612
613  system_items = ItemSet("system", "META/filesystem_config.txt")
614  script.ShowProgress(system_progress, 0)
615
616  if block_based:
617    # Full OTA is done as an "incremental" against an empty source
618    # image.  This has the effect of writing new data from the package
619    # to the entire partition, but lets us reuse the updater code that
620    # writes incrementals to do it.
621    system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
622    system_tgt.ResetFileMap()
623    system_diff = common.BlockDifference("system", system_tgt, src=None)
624    system_diff.WriteScript(script, output_zip)
625  else:
626    script.FormatPartition("/system")
627    script.Mount("/system", recovery_mount_options)
628    if not has_recovery_patch:
629      script.UnpackPackageDir("recovery", "/system")
630    script.UnpackPackageDir("system", "/system")
631
632    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
633    script.MakeSymlinks(symlinks)
634
635  boot_img = common.GetBootableImage(
636      "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
637
638  if not block_based:
639    def output_sink(fn, data):
640      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
641      system_items.Get("system/" + fn)
642
643    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
644                             recovery_img, boot_img)
645
646    system_items.GetMetadata(input_zip)
647    system_items.Get("system").SetPermissions(script)
648
649  if HasVendorPartition(input_zip):
650    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
651    script.ShowProgress(0.1, 0)
652
653    if block_based:
654      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
655      vendor_tgt.ResetFileMap()
656      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
657      vendor_diff.WriteScript(script, output_zip)
658    else:
659      script.FormatPartition("/vendor")
660      script.Mount("/vendor", recovery_mount_options)
661      script.UnpackPackageDir("vendor", "/vendor")
662
663      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
664      script.MakeSymlinks(symlinks)
665
666      vendor_items.GetMetadata(input_zip)
667      vendor_items.Get("vendor").SetPermissions(script)
668
669  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
670  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
671
672  script.ShowProgress(0.05, 5)
673  script.WriteRawImage("/boot", "boot.img")
674
675  script.ShowProgress(0.2, 10)
676  device_specific.FullOTA_InstallEnd()
677
678  if OPTIONS.extra_script is not None:
679    script.AppendExtra(OPTIONS.extra_script)
680
681  script.UnmountAll()
682
683  if OPTIONS.wipe_user_data:
684    script.ShowProgress(0.1, 10)
685    script.FormatPartition("/data")
686
687  if OPTIONS.two_step:
688    script.AppendExtra("""
689set_stage("%(bcb_dev)s", "");
690""" % bcb_dev)
691    script.AppendExtra("else\n")
692    script.WriteRawImage("/boot", "recovery.img")
693    script.AppendExtra("""
694set_stage("%(bcb_dev)s", "2/3");
695reboot_now("%(bcb_dev)s", "");
696endif;
697endif;
698""" % bcb_dev)
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  # TODO(tbao): We should factor out the common parts between
749  # WriteBlockIncrementalOTAPackage() and WriteIncrementalOTAPackage().
750  source_version = OPTIONS.source_info_dict["recovery_api_version"]
751  target_version = OPTIONS.target_info_dict["recovery_api_version"]
752
753  if source_version == 0:
754    print ("WARNING: generating edify script for a source that "
755           "can't install it.")
756  script = edify_generator.EdifyGenerator(
757      source_version, OPTIONS.target_info_dict,
758      fstab=OPTIONS.source_info_dict["fstab"])
759
760  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
761  recovery_mount_options = OPTIONS.source_info_dict.get(
762      "recovery_mount_options")
763  oem_dict = None
764  if oem_props is not None and len(oem_props) > 0:
765    if OPTIONS.oem_source is None:
766      raise common.ExternalError("OEM source required for this build")
767    script.Mount("/oem", recovery_mount_options)
768    oem_dict = common.LoadDictionaryFromLines(
769        open(OPTIONS.oem_source).readlines())
770
771  metadata = {
772      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
773                                   OPTIONS.source_info_dict),
774      "post-timestamp": GetBuildProp("ro.build.date.utc",
775                                     OPTIONS.target_info_dict),
776  }
777
778  device_specific = common.DeviceSpecificParams(
779      source_zip=source_zip,
780      source_version=source_version,
781      target_zip=target_zip,
782      target_version=target_version,
783      output_zip=output_zip,
784      script=script,
785      metadata=metadata,
786      info_dict=OPTIONS.source_info_dict)
787
788  source_fp = CalculateFingerprint(oem_props, oem_dict,
789                                   OPTIONS.source_info_dict)
790  target_fp = CalculateFingerprint(oem_props, oem_dict,
791                                   OPTIONS.target_info_dict)
792  metadata["pre-build"] = source_fp
793  metadata["post-build"] = target_fp
794
795  source_boot = common.GetBootableImage(
796      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
797      OPTIONS.source_info_dict)
798  target_boot = common.GetBootableImage(
799      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
800  updating_boot = (not OPTIONS.two_step and
801                   (source_boot.data != target_boot.data))
802
803  target_recovery = common.GetBootableImage(
804      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
805
806  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
807  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
808
809  blockimgdiff_version = 1
810  if OPTIONS.info_dict:
811    blockimgdiff_version = max(
812        int(i) for i in
813        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
814
815  # Check first block of system partition for remount R/W only if
816  # disk type is ext4
817  system_partition = OPTIONS.source_info_dict["fstab"]["/system"]
818  check_first_block = system_partition.fs_type=="ext4"
819  system_diff = common.BlockDifference("system", system_tgt, system_src,
820                                       check_first_block,
821                                       version=blockimgdiff_version)
822
823  if HasVendorPartition(target_zip):
824    if not HasVendorPartition(source_zip):
825      raise RuntimeError("can't generate incremental that adds /vendor")
826    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
827                          OPTIONS.source_info_dict)
828    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
829                          OPTIONS.target_info_dict)
830
831    # Check first block of vendor partition for remount R/W only if
832    # disk type is ext4
833    vendor_partition = OPTIONS.source_info_dict["fstab"]["/vendor"]
834    check_first_block = vendor_partition.fs_type=="ext4"
835    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
836                                         check_first_block,
837                                         version=blockimgdiff_version)
838  else:
839    vendor_diff = None
840
841  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
842  device_specific.IncrementalOTA_Assertions()
843
844  # Two-step incremental package strategy (in chronological order,
845  # which is *not* the order in which the generated script has
846  # things):
847  #
848  # if stage is not "2/3" or "3/3":
849  #    do verification on current system
850  #    write recovery image to boot partition
851  #    set stage to "2/3"
852  #    reboot to boot partition and restart recovery
853  # else if stage is "2/3":
854  #    write recovery image to recovery partition
855  #    set stage to "3/3"
856  #    reboot to recovery partition and restart recovery
857  # else:
858  #    (stage must be "3/3")
859  #    perform update:
860  #       patch system files, etc.
861  #       force full install of new boot image
862  #       set up system to update recovery partition on first boot
863  #    complete script normally
864  #    (allow recovery to mark itself finished and reboot)
865
866  if OPTIONS.two_step:
867    if not OPTIONS.source_info_dict.get("multistage_support", None):
868      assert False, "two-step packages not supported by this build"
869    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
870    assert fs.fs_type.upper() == "EMMC", \
871        "two-step packages only supported on devices with EMMC /misc partitions"
872    bcb_dev = {"bcb_dev": fs.device}
873    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
874    script.AppendExtra("""
875if get_stage("%(bcb_dev)s") == "2/3" then
876""" % bcb_dev)
877    script.AppendExtra("sleep(20);\n")
878    script.WriteRawImage("/recovery", "recovery.img")
879    script.AppendExtra("""
880set_stage("%(bcb_dev)s", "3/3");
881reboot_now("%(bcb_dev)s", "recovery");
882else if get_stage("%(bcb_dev)s") != "3/3" then
883""" % bcb_dev)
884
885  # Dump fingerprints
886  script.Print("Source: %s" % CalculateFingerprint(
887      oem_props, oem_dict, OPTIONS.source_info_dict))
888  script.Print("Target: %s" % CalculateFingerprint(
889      oem_props, oem_dict, OPTIONS.target_info_dict))
890
891  script.Print("Verifying current system...")
892
893  device_specific.IncrementalOTA_VerifyBegin()
894
895  if oem_props is None:
896    # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
897    # patching on a device that's already on the target build will damage the
898    # system. Because operations like move don't check the block state, they
899    # always apply the changes unconditionally.
900    if blockimgdiff_version <= 2:
901      script.AssertSomeFingerprint(source_fp)
902    else:
903      script.AssertSomeFingerprint(source_fp, target_fp)
904  else:
905    if blockimgdiff_version <= 2:
906      script.AssertSomeThumbprint(
907          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
908    else:
909      script.AssertSomeThumbprint(
910          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
911          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
912
913  if updating_boot:
914    boot_type, boot_device = common.GetTypeAndDevice(
915        "/boot", OPTIONS.source_info_dict)
916    d = common.Difference(target_boot, source_boot)
917    _, _, d = d.ComputePatch()
918    if d is None:
919      include_full_boot = True
920      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
921    else:
922      include_full_boot = False
923
924      print "boot      target: %d  source: %d  diff: %d" % (
925          target_boot.size, source_boot.size, len(d))
926
927      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
928
929      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
930                        (boot_type, boot_device,
931                         source_boot.size, source_boot.sha1,
932                         target_boot.size, target_boot.sha1))
933
934  device_specific.IncrementalOTA_VerifyEnd()
935
936  if OPTIONS.two_step:
937    script.WriteRawImage("/boot", "recovery.img")
938    script.AppendExtra("""
939set_stage("%(bcb_dev)s", "2/3");
940reboot_now("%(bcb_dev)s", "");
941else
942""" % bcb_dev)
943
944  # Verify the existing partitions.
945  system_diff.WriteVerifyScript(script)
946  if vendor_diff:
947    vendor_diff.WriteVerifyScript(script)
948
949  script.Comment("---- start making changes here ----")
950
951  device_specific.IncrementalOTA_InstallBegin()
952
953  system_diff.WriteScript(script, output_zip,
954                          progress=0.8 if vendor_diff else 0.9)
955
956  if vendor_diff:
957    vendor_diff.WriteScript(script, output_zip, progress=0.1)
958
959  if OPTIONS.two_step:
960    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
961    script.WriteRawImage("/boot", "boot.img")
962    print "writing full boot image (forced by two-step mode)"
963
964  if not OPTIONS.two_step:
965    if updating_boot:
966      if include_full_boot:
967        print "boot image changed; including full."
968        script.Print("Installing boot image...")
969        script.WriteRawImage("/boot", "boot.img")
970      else:
971        # Produce the boot image by applying a patch to the current
972        # contents of the boot partition, and write it back to the
973        # partition.
974        print "boot image changed; including patch."
975        script.Print("Patching boot image...")
976        script.ShowProgress(0.1, 10)
977        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
978                          % (boot_type, boot_device,
979                             source_boot.size, source_boot.sha1,
980                             target_boot.size, target_boot.sha1),
981                          "-",
982                          target_boot.size, target_boot.sha1,
983                          source_boot.sha1, "patch/boot.img.p")
984    else:
985      print "boot image unchanged; skipping."
986
987  # Do device-specific installation (eg, write radio image).
988  device_specific.IncrementalOTA_InstallEnd()
989
990  if OPTIONS.extra_script is not None:
991    script.AppendExtra(OPTIONS.extra_script)
992
993  if OPTIONS.wipe_user_data:
994    script.Print("Erasing user data...")
995    script.FormatPartition("/data")
996
997  if OPTIONS.two_step:
998    script.AppendExtra("""
999set_stage("%(bcb_dev)s", "");
1000endif;
1001endif;
1002""" % bcb_dev)
1003
1004  script.SetProgress(1)
1005  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1006  WriteMetadata(metadata, output_zip)
1007
1008
1009def WriteVerifyPackage(input_zip, output_zip):
1010  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
1011
1012  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1013  recovery_mount_options = OPTIONS.info_dict.get(
1014      "recovery_mount_options")
1015  oem_dict = None
1016  if oem_props is not None and len(oem_props) > 0:
1017    if OPTIONS.oem_source is None:
1018      raise common.ExternalError("OEM source required for this build")
1019    script.Mount("/oem", recovery_mount_options)
1020    oem_dict = common.LoadDictionaryFromLines(
1021        open(OPTIONS.oem_source).readlines())
1022
1023  target_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.info_dict)
1024  metadata = {
1025      "post-build": target_fp,
1026      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1027                                   OPTIONS.info_dict),
1028      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
1029  }
1030
1031  device_specific = common.DeviceSpecificParams(
1032      input_zip=input_zip,
1033      input_version=OPTIONS.info_dict["recovery_api_version"],
1034      output_zip=output_zip,
1035      script=script,
1036      input_tmp=OPTIONS.input_tmp,
1037      metadata=metadata,
1038      info_dict=OPTIONS.info_dict)
1039
1040  AppendAssertions(script, OPTIONS.info_dict, oem_dict)
1041
1042  script.Print("Verifying device images against %s..." % target_fp)
1043  script.AppendExtra("")
1044
1045  script.Print("Verifying boot...")
1046  boot_img = common.GetBootableImage(
1047      "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
1048  boot_type, boot_device = common.GetTypeAndDevice(
1049      "/boot", OPTIONS.info_dict)
1050  script.Verify("%s:%s:%d:%s" % (
1051      boot_type, boot_device, boot_img.size, boot_img.sha1))
1052  script.AppendExtra("")
1053
1054  script.Print("Verifying recovery...")
1055  recovery_img = common.GetBootableImage(
1056      "recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
1057  recovery_type, recovery_device = common.GetTypeAndDevice(
1058      "/recovery", OPTIONS.info_dict)
1059  script.Verify("%s:%s:%d:%s" % (
1060      recovery_type, recovery_device, recovery_img.size, recovery_img.sha1))
1061  script.AppendExtra("")
1062
1063  system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
1064  system_tgt.ResetFileMap()
1065  system_diff = common.BlockDifference("system", system_tgt, src=None)
1066  system_diff.WriteStrictVerifyScript(script)
1067
1068  if HasVendorPartition(input_zip):
1069    vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
1070    vendor_tgt.ResetFileMap()
1071    vendor_diff = common.BlockDifference("vendor", vendor_tgt, src=None)
1072    vendor_diff.WriteStrictVerifyScript(script)
1073
1074  # Device specific partitions, such as radio, bootloader and etc.
1075  device_specific.VerifyOTA_Assertions()
1076
1077  script.SetProgress(1.0)
1078  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
1079  WriteMetadata(metadata, output_zip)
1080
1081
1082def WriteABOTAPackageWithBrilloScript(target_file, output_file,
1083                                      source_file=None):
1084  """Generate an Android OTA package that has A/B update payload."""
1085
1086  # Setup signing keys.
1087  if OPTIONS.package_key is None:
1088    OPTIONS.package_key = OPTIONS.info_dict.get(
1089        "default_system_dev_certificate",
1090        "build/target/product/security/testkey")
1091
1092  # A/B updater expects key in RSA format.
1093  cmd = ["openssl", "pkcs8",
1094         "-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
1095         "-inform", "DER", "-nocrypt"]
1096  rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
1097  cmd.extend(["-out", rsa_key])
1098  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1099  p1.wait()
1100  assert p1.returncode == 0, "openssl pkcs8 failed"
1101
1102  # Stage the output zip package for signing.
1103  temp_zip_file = tempfile.NamedTemporaryFile()
1104  output_zip = zipfile.ZipFile(temp_zip_file, "w",
1105                               compression=zipfile.ZIP_DEFLATED)
1106
1107  # Metadata to comply with Android OTA package format.
1108  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
1109  oem_dict = None
1110  if oem_props:
1111    if OPTIONS.oem_source is None:
1112      raise common.ExternalError("OEM source required for this build")
1113    oem_dict = common.LoadDictionaryFromLines(
1114        open(OPTIONS.oem_source).readlines())
1115
1116  metadata = {
1117      "post-build": CalculateFingerprint(oem_props, oem_dict,
1118                                         OPTIONS.info_dict),
1119      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1120                                   OPTIONS.info_dict),
1121      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
1122  }
1123
1124  if source_file is not None:
1125    metadata["pre-build"] = CalculateFingerprint(oem_props, oem_dict,
1126                                                 OPTIONS.source_info_dict)
1127
1128  # 1. Generate payload.
1129  payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
1130  cmd = ["brillo_update_payload", "generate",
1131         "--payload", payload_file,
1132         "--target_image", target_file]
1133  if source_file is not None:
1134    cmd.extend(["--source_image", source_file])
1135  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1136  p1.wait()
1137  assert p1.returncode == 0, "brillo_update_payload generate failed"
1138
1139  # 2. Generate hashes of the payload and metadata files.
1140  payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1141  metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1142  cmd = ["brillo_update_payload", "hash",
1143         "--unsigned_payload", payload_file,
1144         "--signature_size", "256",
1145         "--metadata_hash_file", metadata_sig_file,
1146         "--payload_hash_file", payload_sig_file]
1147  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1148  p1.wait()
1149  assert p1.returncode == 0, "brillo_update_payload hash failed"
1150
1151  # 3. Sign the hashes and insert them back into the payload file.
1152  signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-",
1153                                                suffix=".bin")
1154  signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-",
1155                                                 suffix=".bin")
1156  # 3a. Sign the payload hash.
1157  cmd = ["openssl", "pkeyutl", "-sign",
1158         "-inkey", rsa_key,
1159         "-pkeyopt", "digest:sha256",
1160         "-in", payload_sig_file,
1161         "-out", signed_payload_sig_file]
1162  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1163  p1.wait()
1164  assert p1.returncode == 0, "openssl sign payload failed"
1165
1166  # 3b. Sign the metadata hash.
1167  cmd = ["openssl", "pkeyutl", "-sign",
1168         "-inkey", rsa_key,
1169         "-pkeyopt", "digest:sha256",
1170         "-in", metadata_sig_file,
1171         "-out", signed_metadata_sig_file]
1172  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1173  p1.wait()
1174  assert p1.returncode == 0, "openssl sign metadata failed"
1175
1176  # 3c. Insert the signatures back into the payload file.
1177  signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
1178                                            suffix=".bin")
1179  cmd = ["brillo_update_payload", "sign",
1180         "--unsigned_payload", payload_file,
1181         "--payload", signed_payload_file,
1182         "--signature_size", "256",
1183         "--metadata_signature_file", signed_metadata_sig_file,
1184         "--payload_signature_file", signed_payload_sig_file]
1185  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1186  p1.wait()
1187  assert p1.returncode == 0, "brillo_update_payload sign failed"
1188
1189  # 4. Dump the signed payload properties.
1190  properties_file = common.MakeTempFile(prefix="payload-properties-",
1191                                        suffix=".txt")
1192  cmd = ["brillo_update_payload", "properties",
1193         "--payload", signed_payload_file,
1194         "--properties_file", properties_file]
1195  p1 = common.Run(cmd, stdout=subprocess.PIPE)
1196  p1.wait()
1197  assert p1.returncode == 0, "brillo_update_payload properties failed"
1198
1199  # Add the signed payload file and properties into the zip.
1200  common.ZipWrite(output_zip, properties_file, arcname="payload_properties.txt")
1201  common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
1202                  compress_type=zipfile.ZIP_STORED)
1203  WriteMetadata(metadata, output_zip)
1204
1205  # Sign the whole package to comply with the Android OTA package format.
1206  common.ZipClose(output_zip)
1207  SignOutput(temp_zip_file.name, output_file)
1208  temp_zip_file.close()
1209
1210
1211class FileDifference(object):
1212  def __init__(self, partition, source_zip, target_zip, output_zip):
1213    self.deferred_patch_list = None
1214    print "Loading target..."
1215    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
1216    print "Loading source..."
1217    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
1218
1219    self.verbatim_targets = verbatim_targets = []
1220    self.patch_list = patch_list = []
1221    diffs = []
1222    self.renames = renames = {}
1223    known_paths = set()
1224    largest_source_size = 0
1225
1226    matching_file_cache = {}
1227    for fn, sf in source_data.items():
1228      assert fn == sf.name
1229      matching_file_cache["path:" + fn] = sf
1230      if fn in target_data.keys():
1231        AddToKnownPaths(fn, known_paths)
1232      # Only allow eligibility for filename/sha matching
1233      # if there isn't a perfect path match.
1234      if target_data.get(sf.name) is None:
1235        matching_file_cache["file:" + fn.split("/")[-1]] = sf
1236        matching_file_cache["sha:" + sf.sha1] = sf
1237
1238    for fn in sorted(target_data.keys()):
1239      tf = target_data[fn]
1240      assert fn == tf.name
1241      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1242      if sf is not None and sf.name != tf.name:
1243        print "File has moved from " + sf.name + " to " + tf.name
1244        renames[sf.name] = tf
1245
1246      if sf is None or fn in OPTIONS.require_verbatim:
1247        # This file should be included verbatim
1248        if fn in OPTIONS.prohibit_verbatim:
1249          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1250        print "send", fn, "verbatim"
1251        tf.AddToZip(output_zip)
1252        verbatim_targets.append((fn, tf.size, tf.sha1))
1253        if fn in target_data.keys():
1254          AddToKnownPaths(fn, known_paths)
1255      elif tf.sha1 != sf.sha1:
1256        # File is different; consider sending as a patch
1257        diffs.append(common.Difference(tf, sf))
1258      else:
1259        # Target file data identical to source (may still be renamed)
1260        pass
1261
1262    common.ComputeDifferences(diffs)
1263
1264    for diff in diffs:
1265      tf, sf, d = diff.GetPatch()
1266      path = "/".join(tf.name.split("/")[:-1])
1267      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1268          path not in known_paths:
1269        # patch is almost as big as the file; don't bother patching
1270        # or a patch + rename cannot take place due to the target
1271        # directory not existing
1272        tf.AddToZip(output_zip)
1273        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1274        if sf.name in renames:
1275          del renames[sf.name]
1276        AddToKnownPaths(tf.name, known_paths)
1277      else:
1278        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1279        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1280        largest_source_size = max(largest_source_size, sf.size)
1281
1282    self.largest_source_size = largest_source_size
1283
1284  def EmitVerification(self, script):
1285    so_far = 0
1286    for tf, sf, _, _ in self.patch_list:
1287      if tf.name != sf.name:
1288        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1289      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1290      so_far += sf.size
1291    return so_far
1292
1293  def EmitExplicitTargetVerification(self, script):
1294    for fn, _, sha1 in self.verbatim_targets:
1295      if fn[-1] != "/":
1296        script.FileCheck("/"+fn, sha1)
1297    for tf, _, _, _ in self.patch_list:
1298      script.FileCheck(tf.name, tf.sha1)
1299
1300  def RemoveUnneededFiles(self, script, extras=()):
1301    file_list = ["/" + i[0] for i in self.verbatim_targets]
1302    file_list += ["/" + i for i in self.source_data
1303                  if i not in self.target_data and i not in self.renames]
1304    file_list += list(extras)
1305    # Sort the list in descending order, which removes all the files first
1306    # before attempting to remove the folder. (Bug: 22960996)
1307    script.DeleteFiles(sorted(file_list, reverse=True))
1308
1309  def TotalPatchSize(self):
1310    return sum(i[1].size for i in self.patch_list)
1311
1312  def EmitPatches(self, script, total_patch_size, so_far):
1313    self.deferred_patch_list = deferred_patch_list = []
1314    for item in self.patch_list:
1315      tf, sf, _, _ = item
1316      if tf.name == "system/build.prop":
1317        deferred_patch_list.append(item)
1318        continue
1319      if sf.name != tf.name:
1320        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1321      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1322                        "patch/" + sf.name + ".p")
1323      so_far += tf.size
1324      script.SetProgress(so_far / total_patch_size)
1325    return so_far
1326
1327  def EmitDeferredPatches(self, script):
1328    for item in self.deferred_patch_list:
1329      tf, sf, _, _ = item
1330      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1331                        "patch/" + sf.name + ".p")
1332    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1333
1334  def EmitRenames(self, script):
1335    if len(self.renames) > 0:
1336      script.Print("Renaming files...")
1337      for src, tgt in self.renames.iteritems():
1338        print "Renaming " + src + " to " + tgt.name
1339        script.RenameFile(src, tgt.name)
1340
1341
1342def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1343  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1344  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1345
1346  if (OPTIONS.block_based and
1347      target_has_recovery_patch and
1348      source_has_recovery_patch):
1349    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1350
1351  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1352  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1353
1354  if source_version == 0:
1355    print ("WARNING: generating edify script for a source that "
1356           "can't install it.")
1357  script = edify_generator.EdifyGenerator(
1358      source_version, OPTIONS.target_info_dict,
1359      fstab=OPTIONS.source_info_dict["fstab"])
1360
1361  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1362  recovery_mount_options = OPTIONS.source_info_dict.get(
1363      "recovery_mount_options")
1364  oem_dict = None
1365  if oem_props is not None and len(oem_props) > 0:
1366    if OPTIONS.oem_source is None:
1367      raise common.ExternalError("OEM source required for this build")
1368    script.Mount("/oem", recovery_mount_options)
1369    oem_dict = common.LoadDictionaryFromLines(
1370        open(OPTIONS.oem_source).readlines())
1371
1372  metadata = {
1373      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1374                                   OPTIONS.source_info_dict),
1375      "post-timestamp": GetBuildProp("ro.build.date.utc",
1376                                     OPTIONS.target_info_dict),
1377  }
1378
1379  device_specific = common.DeviceSpecificParams(
1380      source_zip=source_zip,
1381      source_version=source_version,
1382      target_zip=target_zip,
1383      target_version=target_version,
1384      output_zip=output_zip,
1385      script=script,
1386      metadata=metadata,
1387      info_dict=OPTIONS.source_info_dict)
1388
1389  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1390  script.Mount("/system", recovery_mount_options)
1391  if HasVendorPartition(target_zip):
1392    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1393    script.Mount("/vendor", recovery_mount_options)
1394  else:
1395    vendor_diff = None
1396
1397  target_fp = CalculateFingerprint(oem_props, oem_dict,
1398                                   OPTIONS.target_info_dict)
1399  source_fp = CalculateFingerprint(oem_props, oem_dict,
1400                                   OPTIONS.source_info_dict)
1401
1402  if oem_props is None:
1403    script.AssertSomeFingerprint(source_fp, target_fp)
1404  else:
1405    script.AssertSomeThumbprint(
1406        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1407        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1408
1409  metadata["pre-build"] = source_fp
1410  metadata["post-build"] = target_fp
1411
1412  source_boot = common.GetBootableImage(
1413      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1414      OPTIONS.source_info_dict)
1415  target_boot = common.GetBootableImage(
1416      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1417  updating_boot = (not OPTIONS.two_step and
1418                   (source_boot.data != target_boot.data))
1419
1420  source_recovery = common.GetBootableImage(
1421      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1422      OPTIONS.source_info_dict)
1423  target_recovery = common.GetBootableImage(
1424      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1425  updating_recovery = (source_recovery.data != target_recovery.data)
1426
1427  # Here's how we divide up the progress bar:
1428  #  0.1 for verifying the start state (PatchCheck calls)
1429  #  0.8 for applying patches (ApplyPatch calls)
1430  #  0.1 for unpacking verbatim files, symlinking, and doing the
1431  #      device-specific commands.
1432
1433  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1434  device_specific.IncrementalOTA_Assertions()
1435
1436  # Two-step incremental package strategy (in chronological order,
1437  # which is *not* the order in which the generated script has
1438  # things):
1439  #
1440  # if stage is not "2/3" or "3/3":
1441  #    do verification on current system
1442  #    write recovery image to boot partition
1443  #    set stage to "2/3"
1444  #    reboot to boot partition and restart recovery
1445  # else if stage is "2/3":
1446  #    write recovery image to recovery partition
1447  #    set stage to "3/3"
1448  #    reboot to recovery partition and restart recovery
1449  # else:
1450  #    (stage must be "3/3")
1451  #    perform update:
1452  #       patch system files, etc.
1453  #       force full install of new boot image
1454  #       set up system to update recovery partition on first boot
1455  #    complete script normally
1456  #    (allow recovery to mark itself finished and reboot)
1457
1458  if OPTIONS.two_step:
1459    if not OPTIONS.source_info_dict.get("multistage_support", None):
1460      assert False, "two-step packages not supported by this build"
1461    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
1462    assert fs.fs_type.upper() == "EMMC", \
1463        "two-step packages only supported on devices with EMMC /misc partitions"
1464    bcb_dev = {"bcb_dev": fs.device}
1465    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1466    script.AppendExtra("""
1467if get_stage("%(bcb_dev)s") == "2/3" then
1468""" % bcb_dev)
1469    script.AppendExtra("sleep(20);\n")
1470    script.WriteRawImage("/recovery", "recovery.img")
1471    script.AppendExtra("""
1472set_stage("%(bcb_dev)s", "3/3");
1473reboot_now("%(bcb_dev)s", "recovery");
1474else if get_stage("%(bcb_dev)s") != "3/3" then
1475""" % bcb_dev)
1476
1477  # Dump fingerprints
1478  script.Print("Source: %s" % (source_fp,))
1479  script.Print("Target: %s" % (target_fp,))
1480
1481  script.Print("Verifying current system...")
1482
1483  device_specific.IncrementalOTA_VerifyBegin()
1484
1485  script.ShowProgress(0.1, 0)
1486  so_far = system_diff.EmitVerification(script)
1487  if vendor_diff:
1488    so_far += vendor_diff.EmitVerification(script)
1489
1490  if updating_boot:
1491    d = common.Difference(target_boot, source_boot)
1492    _, _, d = d.ComputePatch()
1493    print "boot      target: %d  source: %d  diff: %d" % (
1494        target_boot.size, source_boot.size, len(d))
1495
1496    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1497
1498    boot_type, boot_device = common.GetTypeAndDevice(
1499        "/boot", OPTIONS.source_info_dict)
1500
1501    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1502                      (boot_type, boot_device,
1503                       source_boot.size, source_boot.sha1,
1504                       target_boot.size, target_boot.sha1))
1505    so_far += source_boot.size
1506
1507  size = []
1508  if system_diff.patch_list:
1509    size.append(system_diff.largest_source_size)
1510  if vendor_diff:
1511    if vendor_diff.patch_list:
1512      size.append(vendor_diff.largest_source_size)
1513  if size or updating_recovery or updating_boot:
1514    script.CacheFreeSpaceCheck(max(size))
1515
1516  device_specific.IncrementalOTA_VerifyEnd()
1517
1518  if OPTIONS.two_step:
1519    script.WriteRawImage("/boot", "recovery.img")
1520    script.AppendExtra("""
1521set_stage("%(bcb_dev)s", "2/3");
1522reboot_now("%(bcb_dev)s", "");
1523else
1524""" % bcb_dev)
1525
1526  script.Comment("---- start making changes here ----")
1527
1528  device_specific.IncrementalOTA_InstallBegin()
1529
1530  if OPTIONS.two_step:
1531    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1532    script.WriteRawImage("/boot", "boot.img")
1533    print "writing full boot image (forced by two-step mode)"
1534
1535  script.Print("Removing unneeded files...")
1536  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1537  if vendor_diff:
1538    vendor_diff.RemoveUnneededFiles(script)
1539
1540  script.ShowProgress(0.8, 0)
1541  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1542  if vendor_diff:
1543    total_patch_size += vendor_diff.TotalPatchSize()
1544  if updating_boot:
1545    total_patch_size += target_boot.size
1546
1547  script.Print("Patching system files...")
1548  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1549  if vendor_diff:
1550    script.Print("Patching vendor files...")
1551    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1552
1553  if not OPTIONS.two_step:
1554    if updating_boot:
1555      # Produce the boot image by applying a patch to the current
1556      # contents of the boot partition, and write it back to the
1557      # partition.
1558      script.Print("Patching boot image...")
1559      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1560                        % (boot_type, boot_device,
1561                           source_boot.size, source_boot.sha1,
1562                           target_boot.size, target_boot.sha1),
1563                        "-",
1564                        target_boot.size, target_boot.sha1,
1565                        source_boot.sha1, "patch/boot.img.p")
1566      so_far += target_boot.size
1567      script.SetProgress(so_far / total_patch_size)
1568      print "boot image changed; including."
1569    else:
1570      print "boot image unchanged; skipping."
1571
1572  system_items = ItemSet("system", "META/filesystem_config.txt")
1573  if vendor_diff:
1574    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1575
1576  if updating_recovery:
1577    # Recovery is generated as a patch using both the boot image
1578    # (which contains the same linux kernel as recovery) and the file
1579    # /system/etc/recovery-resource.dat (which contains all the images
1580    # used in the recovery UI) as sources.  This lets us minimize the
1581    # size of the patch, which must be included in every OTA package.
1582    #
1583    # For older builds where recovery-resource.dat is not present, we
1584    # use only the boot image as the source.
1585
1586    if not target_has_recovery_patch:
1587      def output_sink(fn, data):
1588        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1589        system_items.Get("system/" + fn)
1590
1591      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1592                               target_recovery, target_boot)
1593      script.DeleteFiles(["/system/recovery-from-boot.p",
1594                          "/system/etc/recovery.img",
1595                          "/system/etc/install-recovery.sh"])
1596    print "recovery image changed; including as patch from boot."
1597  else:
1598    print "recovery image unchanged; skipping."
1599
1600  script.ShowProgress(0.1, 10)
1601
1602  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1603  if vendor_diff:
1604    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1605
1606  temp_script = script.MakeTemporary()
1607  system_items.GetMetadata(target_zip)
1608  system_items.Get("system").SetPermissions(temp_script)
1609  if vendor_diff:
1610    vendor_items.GetMetadata(target_zip)
1611    vendor_items.Get("vendor").SetPermissions(temp_script)
1612
1613  # Note that this call will mess up the trees of Items, so make sure
1614  # we're done with them.
1615  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1616  if vendor_diff:
1617    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1618
1619  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1620  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1621
1622  # Delete all the symlinks in source that aren't in target.  This
1623  # needs to happen before verbatim files are unpacked, in case a
1624  # symlink in the source is replaced by a real file in the target.
1625
1626  # If a symlink in the source will be replaced by a regular file, we cannot
1627  # delete the symlink/file in case the package gets applied again. For such
1628  # a symlink, we prepend a sha1_check() to detect if it has been updated.
1629  # (Bug: 23646151)
1630  replaced_symlinks = dict()
1631  if system_diff:
1632    for i in system_diff.verbatim_targets:
1633      replaced_symlinks["/%s" % (i[0],)] = i[2]
1634  if vendor_diff:
1635    for i in vendor_diff.verbatim_targets:
1636      replaced_symlinks["/%s" % (i[0],)] = i[2]
1637
1638  if system_diff:
1639    for tf in system_diff.renames.values():
1640      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1641  if vendor_diff:
1642    for tf in vendor_diff.renames.values():
1643      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1644
1645  always_delete = []
1646  may_delete = []
1647  for dest, link in source_symlinks:
1648    if link not in target_symlinks_d:
1649      if link in replaced_symlinks:
1650        may_delete.append((link, replaced_symlinks[link]))
1651      else:
1652        always_delete.append(link)
1653  script.DeleteFiles(always_delete)
1654  script.DeleteFilesIfNotMatching(may_delete)
1655
1656  if system_diff.verbatim_targets:
1657    script.Print("Unpacking new system files...")
1658    script.UnpackPackageDir("system", "/system")
1659  if vendor_diff and vendor_diff.verbatim_targets:
1660    script.Print("Unpacking new vendor files...")
1661    script.UnpackPackageDir("vendor", "/vendor")
1662
1663  if updating_recovery and not target_has_recovery_patch:
1664    script.Print("Unpacking new recovery...")
1665    script.UnpackPackageDir("recovery", "/system")
1666
1667  system_diff.EmitRenames(script)
1668  if vendor_diff:
1669    vendor_diff.EmitRenames(script)
1670
1671  script.Print("Symlinks and permissions...")
1672
1673  # Create all the symlinks that don't already exist, or point to
1674  # somewhere different than what we want.  Delete each symlink before
1675  # creating it, since the 'symlink' command won't overwrite.
1676  to_create = []
1677  for dest, link in target_symlinks:
1678    if link in source_symlinks_d:
1679      if dest != source_symlinks_d[link]:
1680        to_create.append((dest, link))
1681    else:
1682      to_create.append((dest, link))
1683  script.DeleteFiles([i[1] for i in to_create])
1684  script.MakeSymlinks(to_create)
1685
1686  # Now that the symlinks are created, we can set all the
1687  # permissions.
1688  script.AppendScript(temp_script)
1689
1690  # Do device-specific installation (eg, write radio image).
1691  device_specific.IncrementalOTA_InstallEnd()
1692
1693  if OPTIONS.extra_script is not None:
1694    script.AppendExtra(OPTIONS.extra_script)
1695
1696  # Patch the build.prop file last, so if something fails but the
1697  # device can still come up, it appears to be the old build and will
1698  # get set the OTA package again to retry.
1699  script.Print("Patching remaining system files...")
1700  system_diff.EmitDeferredPatches(script)
1701
1702  if OPTIONS.wipe_user_data:
1703    script.Print("Erasing user data...")
1704    script.FormatPartition("/data")
1705
1706  if OPTIONS.two_step:
1707    script.AppendExtra("""
1708set_stage("%(bcb_dev)s", "");
1709endif;
1710endif;
1711""" % bcb_dev)
1712
1713  if OPTIONS.verify and system_diff:
1714    script.Print("Remounting and verifying system partition files...")
1715    script.Unmount("/system")
1716    script.Mount("/system", recovery_mount_options)
1717    system_diff.EmitExplicitTargetVerification(script)
1718
1719  if OPTIONS.verify and vendor_diff:
1720    script.Print("Remounting and verifying vendor partition files...")
1721    script.Unmount("/vendor")
1722    script.Mount("/vendor", recovery_mount_options)
1723    vendor_diff.EmitExplicitTargetVerification(script)
1724  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1725
1726  WriteMetadata(metadata, output_zip)
1727
1728
1729def main(argv):
1730
1731  def option_handler(o, a):
1732    if o == "--board_config":
1733      pass   # deprecated
1734    elif o in ("-k", "--package_key"):
1735      OPTIONS.package_key = a
1736    elif o in ("-i", "--incremental_from"):
1737      OPTIONS.incremental_source = a
1738    elif o == "--full_radio":
1739      OPTIONS.full_radio = True
1740    elif o == "--full_bootloader":
1741      OPTIONS.full_bootloader = True
1742    elif o in ("-w", "--wipe_user_data"):
1743      OPTIONS.wipe_user_data = True
1744    elif o in ("-n", "--no_prereq"):
1745      OPTIONS.omit_prereq = True
1746    elif o in ("-o", "--oem_settings"):
1747      OPTIONS.oem_source = a
1748    elif o in ("-e", "--extra_script"):
1749      OPTIONS.extra_script = a
1750    elif o in ("-a", "--aslr_mode"):
1751      if a in ("on", "On", "true", "True", "yes", "Yes"):
1752        OPTIONS.aslr_mode = True
1753      else:
1754        OPTIONS.aslr_mode = False
1755    elif o in ("-t", "--worker_threads"):
1756      if a.isdigit():
1757        OPTIONS.worker_threads = int(a)
1758      else:
1759        raise ValueError("Cannot parse value %r for option %r - only "
1760                         "integers are allowed." % (a, o))
1761    elif o in ("-2", "--two_step"):
1762      OPTIONS.two_step = True
1763    elif o == "--no_signing":
1764      OPTIONS.no_signing = True
1765    elif o == "--verify":
1766      OPTIONS.verify = True
1767    elif o == "--block":
1768      OPTIONS.block_based = True
1769    elif o in ("-b", "--binary"):
1770      OPTIONS.updater_binary = a
1771    elif o in ("--no_fallback_to_full",):
1772      OPTIONS.fallback_to_full = False
1773    elif o == "--stash_threshold":
1774      try:
1775        OPTIONS.stash_threshold = float(a)
1776      except ValueError:
1777        raise ValueError("Cannot parse value %r for option %r - expecting "
1778                         "a float" % (a, o))
1779    elif o == "--gen_verify":
1780      OPTIONS.gen_verify = True
1781    elif o == "--log_diff":
1782      OPTIONS.log_diff = a
1783    else:
1784      return False
1785    return True
1786
1787  args = common.ParseOptions(argv, __doc__,
1788                             extra_opts="b:k:i:d:wne:t:a:2o:",
1789                             extra_long_opts=[
1790                                 "board_config=",
1791                                 "package_key=",
1792                                 "incremental_from=",
1793                                 "full_radio",
1794                                 "full_bootloader",
1795                                 "wipe_user_data",
1796                                 "no_prereq",
1797                                 "extra_script=",
1798                                 "worker_threads=",
1799                                 "aslr_mode=",
1800                                 "two_step",
1801                                 "no_signing",
1802                                 "block",
1803                                 "binary=",
1804                                 "oem_settings=",
1805                                 "verify",
1806                                 "no_fallback_to_full",
1807                                 "stash_threshold=",
1808                                 "gen_verify",
1809                                 "log_diff=",
1810                             ], extra_option_handler=option_handler)
1811
1812  if len(args) != 2:
1813    common.Usage(__doc__)
1814    sys.exit(1)
1815
1816  # Load the dict file from the zip directly to have a peek at the OTA type.
1817  # For packages using A/B update, unzipping is not needed.
1818  input_zip = zipfile.ZipFile(args[0], "r")
1819  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1820  common.ZipClose(input_zip)
1821
1822  ab_update = OPTIONS.info_dict.get("ab_update") == "true"
1823
1824  if ab_update:
1825    if OPTIONS.incremental_source is not None:
1826      OPTIONS.target_info_dict = OPTIONS.info_dict
1827      source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
1828      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1829      common.ZipClose(source_zip)
1830
1831    if OPTIONS.verbose:
1832      print "--- target info ---"
1833      common.DumpInfoDict(OPTIONS.info_dict)
1834
1835      if OPTIONS.incremental_source is not None:
1836        print "--- source info ---"
1837        common.DumpInfoDict(OPTIONS.source_info_dict)
1838
1839    WriteABOTAPackageWithBrilloScript(
1840        target_file=args[0],
1841        output_file=args[1],
1842        source_file=OPTIONS.incremental_source)
1843
1844    print "done."
1845    return
1846
1847  if OPTIONS.extra_script is not None:
1848    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1849
1850  print "unzipping target target-files..."
1851  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1852
1853  OPTIONS.target_tmp = OPTIONS.input_tmp
1854  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
1855
1856  if OPTIONS.verbose:
1857    print "--- target info ---"
1858    common.DumpInfoDict(OPTIONS.info_dict)
1859
1860  # If the caller explicitly specified the device-specific extensions
1861  # path via -s/--device_specific, use that.  Otherwise, use
1862  # META/releasetools.py if it is present in the target target_files.
1863  # Otherwise, take the path of the file from 'tool_extensions' in the
1864  # info dict and look for that in the local filesystem, relative to
1865  # the current directory.
1866
1867  if OPTIONS.device_specific is None:
1868    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1869    if os.path.exists(from_input):
1870      print "(using device-specific extensions from target_files)"
1871      OPTIONS.device_specific = from_input
1872    else:
1873      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1874
1875  if OPTIONS.device_specific is not None:
1876    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1877
1878  if OPTIONS.info_dict.get("no_recovery") == "true":
1879    raise common.ExternalError(
1880        "--- target build has specified no recovery ---")
1881
1882  # Use the default key to sign the package if not specified with package_key.
1883  if not OPTIONS.no_signing:
1884    if OPTIONS.package_key is None:
1885      OPTIONS.package_key = OPTIONS.info_dict.get(
1886          "default_system_dev_certificate",
1887          "build/target/product/security/testkey")
1888
1889  # Set up the output zip. Create a temporary zip file if signing is needed.
1890  if OPTIONS.no_signing:
1891    if os.path.exists(args[1]):
1892      os.unlink(args[1])
1893    output_zip = zipfile.ZipFile(args[1], "w",
1894                                 compression=zipfile.ZIP_DEFLATED)
1895  else:
1896    temp_zip_file = tempfile.NamedTemporaryFile()
1897    output_zip = zipfile.ZipFile(temp_zip_file, "w",
1898                                 compression=zipfile.ZIP_DEFLATED)
1899
1900  # Non A/B OTAs rely on /cache partition to store temporary files.
1901  cache_size = OPTIONS.info_dict.get("cache_size", None)
1902  if cache_size is None:
1903    print "--- can't determine the cache partition size ---"
1904  OPTIONS.cache_size = cache_size
1905
1906  # Generate a verify package.
1907  if OPTIONS.gen_verify:
1908    WriteVerifyPackage(input_zip, output_zip)
1909
1910  # Generate a full OTA.
1911  elif OPTIONS.incremental_source is None:
1912    WriteFullOTAPackage(input_zip, output_zip)
1913
1914  # Generate an incremental OTA. It will fall back to generate a full OTA on
1915  # failure unless no_fallback_to_full is specified.
1916  else:
1917    print "unzipping source target-files..."
1918    OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1919        OPTIONS.incremental_source)
1920    OPTIONS.target_info_dict = OPTIONS.info_dict
1921    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
1922                                                   OPTIONS.source_tmp)
1923    if OPTIONS.verbose:
1924      print "--- source info ---"
1925      common.DumpInfoDict(OPTIONS.source_info_dict)
1926    try:
1927      WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1928      if OPTIONS.log_diff:
1929        out_file = open(OPTIONS.log_diff, 'w')
1930        import target_files_diff
1931        target_files_diff.recursiveDiff('',
1932                                        OPTIONS.source_tmp,
1933                                        OPTIONS.input_tmp,
1934                                        out_file)
1935        out_file.close()
1936    except ValueError:
1937      if not OPTIONS.fallback_to_full:
1938        raise
1939      print "--- failed to build incremental; falling back to full ---"
1940      OPTIONS.incremental_source = None
1941      WriteFullOTAPackage(input_zip, output_zip)
1942
1943  common.ZipClose(output_zip)
1944
1945  # Sign the generated zip package unless no_signing is specified.
1946  if not OPTIONS.no_signing:
1947    SignOutput(temp_zip_file.name, args[1])
1948    temp_zip_file.close()
1949
1950  print "done."
1951
1952
1953if __name__ == '__main__':
1954  try:
1955    common.CloseInheritedPipes()
1956    main(sys.argv[1:])
1957  except common.ExternalError as e:
1958    print
1959    print "   ERROR: %s" % (e,)
1960    print
1961    sys.exit(1)
1962  finally:
1963    common.Cleanup()
1964