ota_from_target_files.py revision c098e9efd989a46d5d1aa625bc899536de15eaaa
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  # Add the signed payload file into the zip.
1190  common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
1191                  compress_type=zipfile.ZIP_STORED)
1192  WriteMetadata(metadata, output_zip)
1193
1194  # Sign the whole package to comply with the Android OTA package format.
1195  common.ZipClose(output_zip)
1196  SignOutput(temp_zip_file.name, output_file)
1197  temp_zip_file.close()
1198
1199
1200class FileDifference(object):
1201  def __init__(self, partition, source_zip, target_zip, output_zip):
1202    self.deferred_patch_list = None
1203    print "Loading target..."
1204    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
1205    print "Loading source..."
1206    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
1207
1208    self.verbatim_targets = verbatim_targets = []
1209    self.patch_list = patch_list = []
1210    diffs = []
1211    self.renames = renames = {}
1212    known_paths = set()
1213    largest_source_size = 0
1214
1215    matching_file_cache = {}
1216    for fn, sf in source_data.items():
1217      assert fn == sf.name
1218      matching_file_cache["path:" + fn] = sf
1219      if fn in target_data.keys():
1220        AddToKnownPaths(fn, known_paths)
1221      # Only allow eligibility for filename/sha matching
1222      # if there isn't a perfect path match.
1223      if target_data.get(sf.name) is None:
1224        matching_file_cache["file:" + fn.split("/")[-1]] = sf
1225        matching_file_cache["sha:" + sf.sha1] = sf
1226
1227    for fn in sorted(target_data.keys()):
1228      tf = target_data[fn]
1229      assert fn == tf.name
1230      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1231      if sf is not None and sf.name != tf.name:
1232        print "File has moved from " + sf.name + " to " + tf.name
1233        renames[sf.name] = tf
1234
1235      if sf is None or fn in OPTIONS.require_verbatim:
1236        # This file should be included verbatim
1237        if fn in OPTIONS.prohibit_verbatim:
1238          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1239        print "send", fn, "verbatim"
1240        tf.AddToZip(output_zip)
1241        verbatim_targets.append((fn, tf.size, tf.sha1))
1242        if fn in target_data.keys():
1243          AddToKnownPaths(fn, known_paths)
1244      elif tf.sha1 != sf.sha1:
1245        # File is different; consider sending as a patch
1246        diffs.append(common.Difference(tf, sf))
1247      else:
1248        # Target file data identical to source (may still be renamed)
1249        pass
1250
1251    common.ComputeDifferences(diffs)
1252
1253    for diff in diffs:
1254      tf, sf, d = diff.GetPatch()
1255      path = "/".join(tf.name.split("/")[:-1])
1256      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1257          path not in known_paths:
1258        # patch is almost as big as the file; don't bother patching
1259        # or a patch + rename cannot take place due to the target
1260        # directory not existing
1261        tf.AddToZip(output_zip)
1262        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1263        if sf.name in renames:
1264          del renames[sf.name]
1265        AddToKnownPaths(tf.name, known_paths)
1266      else:
1267        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1268        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1269        largest_source_size = max(largest_source_size, sf.size)
1270
1271    self.largest_source_size = largest_source_size
1272
1273  def EmitVerification(self, script):
1274    so_far = 0
1275    for tf, sf, _, _ in self.patch_list:
1276      if tf.name != sf.name:
1277        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1278      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1279      so_far += sf.size
1280    return so_far
1281
1282  def EmitExplicitTargetVerification(self, script):
1283    for fn, _, sha1 in self.verbatim_targets:
1284      if fn[-1] != "/":
1285        script.FileCheck("/"+fn, sha1)
1286    for tf, _, _, _ in self.patch_list:
1287      script.FileCheck(tf.name, tf.sha1)
1288
1289  def RemoveUnneededFiles(self, script, extras=()):
1290    file_list = ["/" + i[0] for i in self.verbatim_targets]
1291    file_list += ["/" + i for i in self.source_data
1292                  if i not in self.target_data and i not in self.renames]
1293    file_list += list(extras)
1294    # Sort the list in descending order, which removes all the files first
1295    # before attempting to remove the folder. (Bug: 22960996)
1296    script.DeleteFiles(sorted(file_list, reverse=True))
1297
1298  def TotalPatchSize(self):
1299    return sum(i[1].size for i in self.patch_list)
1300
1301  def EmitPatches(self, script, total_patch_size, so_far):
1302    self.deferred_patch_list = deferred_patch_list = []
1303    for item in self.patch_list:
1304      tf, sf, _, _ = item
1305      if tf.name == "system/build.prop":
1306        deferred_patch_list.append(item)
1307        continue
1308      if sf.name != tf.name:
1309        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1310      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1311                        "patch/" + sf.name + ".p")
1312      so_far += tf.size
1313      script.SetProgress(so_far / total_patch_size)
1314    return so_far
1315
1316  def EmitDeferredPatches(self, script):
1317    for item in self.deferred_patch_list:
1318      tf, sf, _, _ = item
1319      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1320                        "patch/" + sf.name + ".p")
1321    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1322
1323  def EmitRenames(self, script):
1324    if len(self.renames) > 0:
1325      script.Print("Renaming files...")
1326      for src, tgt in self.renames.iteritems():
1327        print "Renaming " + src + " to " + tgt.name
1328        script.RenameFile(src, tgt.name)
1329
1330
1331def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1332  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1333  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1334
1335  if (OPTIONS.block_based and
1336      target_has_recovery_patch and
1337      source_has_recovery_patch):
1338    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1339
1340  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1341  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1342
1343  if source_version == 0:
1344    print ("WARNING: generating edify script for a source that "
1345           "can't install it.")
1346  script = edify_generator.EdifyGenerator(
1347      source_version, OPTIONS.target_info_dict,
1348      fstab=OPTIONS.source_info_dict["fstab"])
1349
1350  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1351  recovery_mount_options = OPTIONS.source_info_dict.get(
1352      "recovery_mount_options")
1353  oem_dict = None
1354  if oem_props is not None and len(oem_props) > 0:
1355    if OPTIONS.oem_source is None:
1356      raise common.ExternalError("OEM source required for this build")
1357    script.Mount("/oem", recovery_mount_options)
1358    oem_dict = common.LoadDictionaryFromLines(
1359        open(OPTIONS.oem_source).readlines())
1360
1361  metadata = {
1362      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1363                                   OPTIONS.source_info_dict),
1364      "post-timestamp": GetBuildProp("ro.build.date.utc",
1365                                     OPTIONS.target_info_dict),
1366  }
1367
1368  device_specific = common.DeviceSpecificParams(
1369      source_zip=source_zip,
1370      source_version=source_version,
1371      target_zip=target_zip,
1372      target_version=target_version,
1373      output_zip=output_zip,
1374      script=script,
1375      metadata=metadata,
1376      info_dict=OPTIONS.source_info_dict)
1377
1378  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1379  script.Mount("/system", recovery_mount_options)
1380  if HasVendorPartition(target_zip):
1381    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1382    script.Mount("/vendor", recovery_mount_options)
1383  else:
1384    vendor_diff = None
1385
1386  target_fp = CalculateFingerprint(oem_props, oem_dict,
1387                                   OPTIONS.target_info_dict)
1388  source_fp = CalculateFingerprint(oem_props, oem_dict,
1389                                   OPTIONS.source_info_dict)
1390
1391  if oem_props is None:
1392    script.AssertSomeFingerprint(source_fp, target_fp)
1393  else:
1394    script.AssertSomeThumbprint(
1395        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1396        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1397
1398  metadata["pre-build"] = source_fp
1399  metadata["post-build"] = target_fp
1400
1401  source_boot = common.GetBootableImage(
1402      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1403      OPTIONS.source_info_dict)
1404  target_boot = common.GetBootableImage(
1405      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1406  updating_boot = (not OPTIONS.two_step and
1407                   (source_boot.data != target_boot.data))
1408
1409  source_recovery = common.GetBootableImage(
1410      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1411      OPTIONS.source_info_dict)
1412  target_recovery = common.GetBootableImage(
1413      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1414  updating_recovery = (source_recovery.data != target_recovery.data)
1415
1416  # Here's how we divide up the progress bar:
1417  #  0.1 for verifying the start state (PatchCheck calls)
1418  #  0.8 for applying patches (ApplyPatch calls)
1419  #  0.1 for unpacking verbatim files, symlinking, and doing the
1420  #      device-specific commands.
1421
1422  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1423  device_specific.IncrementalOTA_Assertions()
1424
1425  # Two-step incremental package strategy (in chronological order,
1426  # which is *not* the order in which the generated script has
1427  # things):
1428  #
1429  # if stage is not "2/3" or "3/3":
1430  #    do verification on current system
1431  #    write recovery image to boot partition
1432  #    set stage to "2/3"
1433  #    reboot to boot partition and restart recovery
1434  # else if stage is "2/3":
1435  #    write recovery image to recovery partition
1436  #    set stage to "3/3"
1437  #    reboot to recovery partition and restart recovery
1438  # else:
1439  #    (stage must be "3/3")
1440  #    perform update:
1441  #       patch system files, etc.
1442  #       force full install of new boot image
1443  #       set up system to update recovery partition on first boot
1444  #    complete script normally
1445  #    (allow recovery to mark itself finished and reboot)
1446
1447  if OPTIONS.two_step:
1448    if not OPTIONS.source_info_dict.get("multistage_support", None):
1449      assert False, "two-step packages not supported by this build"
1450    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
1451    assert fs.fs_type.upper() == "EMMC", \
1452        "two-step packages only supported on devices with EMMC /misc partitions"
1453    bcb_dev = {"bcb_dev": fs.device}
1454    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1455    script.AppendExtra("""
1456if get_stage("%(bcb_dev)s") == "2/3" then
1457""" % bcb_dev)
1458    script.AppendExtra("sleep(20);\n")
1459    script.WriteRawImage("/recovery", "recovery.img")
1460    script.AppendExtra("""
1461set_stage("%(bcb_dev)s", "3/3");
1462reboot_now("%(bcb_dev)s", "recovery");
1463else if get_stage("%(bcb_dev)s") != "3/3" then
1464""" % bcb_dev)
1465
1466  # Dump fingerprints
1467  script.Print("Source: %s" % (source_fp,))
1468  script.Print("Target: %s" % (target_fp,))
1469
1470  script.Print("Verifying current system...")
1471
1472  device_specific.IncrementalOTA_VerifyBegin()
1473
1474  script.ShowProgress(0.1, 0)
1475  so_far = system_diff.EmitVerification(script)
1476  if vendor_diff:
1477    so_far += vendor_diff.EmitVerification(script)
1478
1479  if updating_boot:
1480    d = common.Difference(target_boot, source_boot)
1481    _, _, d = d.ComputePatch()
1482    print "boot      target: %d  source: %d  diff: %d" % (
1483        target_boot.size, source_boot.size, len(d))
1484
1485    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1486
1487    boot_type, boot_device = common.GetTypeAndDevice(
1488        "/boot", OPTIONS.source_info_dict)
1489
1490    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1491                      (boot_type, boot_device,
1492                       source_boot.size, source_boot.sha1,
1493                       target_boot.size, target_boot.sha1))
1494    so_far += source_boot.size
1495
1496  size = []
1497  if system_diff.patch_list:
1498    size.append(system_diff.largest_source_size)
1499  if vendor_diff:
1500    if vendor_diff.patch_list:
1501      size.append(vendor_diff.largest_source_size)
1502  if size or updating_recovery or updating_boot:
1503    script.CacheFreeSpaceCheck(max(size))
1504
1505  device_specific.IncrementalOTA_VerifyEnd()
1506
1507  if OPTIONS.two_step:
1508    script.WriteRawImage("/boot", "recovery.img")
1509    script.AppendExtra("""
1510set_stage("%(bcb_dev)s", "2/3");
1511reboot_now("%(bcb_dev)s", "");
1512else
1513""" % bcb_dev)
1514
1515  script.Comment("---- start making changes here ----")
1516
1517  device_specific.IncrementalOTA_InstallBegin()
1518
1519  if OPTIONS.two_step:
1520    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1521    script.WriteRawImage("/boot", "boot.img")
1522    print "writing full boot image (forced by two-step mode)"
1523
1524  script.Print("Removing unneeded files...")
1525  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1526  if vendor_diff:
1527    vendor_diff.RemoveUnneededFiles(script)
1528
1529  script.ShowProgress(0.8, 0)
1530  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1531  if vendor_diff:
1532    total_patch_size += vendor_diff.TotalPatchSize()
1533  if updating_boot:
1534    total_patch_size += target_boot.size
1535
1536  script.Print("Patching system files...")
1537  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1538  if vendor_diff:
1539    script.Print("Patching vendor files...")
1540    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1541
1542  if not OPTIONS.two_step:
1543    if updating_boot:
1544      # Produce the boot image by applying a patch to the current
1545      # contents of the boot partition, and write it back to the
1546      # partition.
1547      script.Print("Patching boot image...")
1548      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1549                        % (boot_type, boot_device,
1550                           source_boot.size, source_boot.sha1,
1551                           target_boot.size, target_boot.sha1),
1552                        "-",
1553                        target_boot.size, target_boot.sha1,
1554                        source_boot.sha1, "patch/boot.img.p")
1555      so_far += target_boot.size
1556      script.SetProgress(so_far / total_patch_size)
1557      print "boot image changed; including."
1558    else:
1559      print "boot image unchanged; skipping."
1560
1561  system_items = ItemSet("system", "META/filesystem_config.txt")
1562  if vendor_diff:
1563    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1564
1565  if updating_recovery:
1566    # Recovery is generated as a patch using both the boot image
1567    # (which contains the same linux kernel as recovery) and the file
1568    # /system/etc/recovery-resource.dat (which contains all the images
1569    # used in the recovery UI) as sources.  This lets us minimize the
1570    # size of the patch, which must be included in every OTA package.
1571    #
1572    # For older builds where recovery-resource.dat is not present, we
1573    # use only the boot image as the source.
1574
1575    if not target_has_recovery_patch:
1576      def output_sink(fn, data):
1577        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1578        system_items.Get("system/" + fn)
1579
1580      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1581                               target_recovery, target_boot)
1582      script.DeleteFiles(["/system/recovery-from-boot.p",
1583                          "/system/etc/recovery.img",
1584                          "/system/etc/install-recovery.sh"])
1585    print "recovery image changed; including as patch from boot."
1586  else:
1587    print "recovery image unchanged; skipping."
1588
1589  script.ShowProgress(0.1, 10)
1590
1591  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1592  if vendor_diff:
1593    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1594
1595  temp_script = script.MakeTemporary()
1596  system_items.GetMetadata(target_zip)
1597  system_items.Get("system").SetPermissions(temp_script)
1598  if vendor_diff:
1599    vendor_items.GetMetadata(target_zip)
1600    vendor_items.Get("vendor").SetPermissions(temp_script)
1601
1602  # Note that this call will mess up the trees of Items, so make sure
1603  # we're done with them.
1604  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1605  if vendor_diff:
1606    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1607
1608  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1609  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1610
1611  # Delete all the symlinks in source that aren't in target.  This
1612  # needs to happen before verbatim files are unpacked, in case a
1613  # symlink in the source is replaced by a real file in the target.
1614
1615  # If a symlink in the source will be replaced by a regular file, we cannot
1616  # delete the symlink/file in case the package gets applied again. For such
1617  # a symlink, we prepend a sha1_check() to detect if it has been updated.
1618  # (Bug: 23646151)
1619  replaced_symlinks = dict()
1620  if system_diff:
1621    for i in system_diff.verbatim_targets:
1622      replaced_symlinks["/%s" % (i[0],)] = i[2]
1623  if vendor_diff:
1624    for i in vendor_diff.verbatim_targets:
1625      replaced_symlinks["/%s" % (i[0],)] = i[2]
1626
1627  if system_diff:
1628    for tf in system_diff.renames.values():
1629      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1630  if vendor_diff:
1631    for tf in vendor_diff.renames.values():
1632      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1633
1634  always_delete = []
1635  may_delete = []
1636  for dest, link in source_symlinks:
1637    if link not in target_symlinks_d:
1638      if link in replaced_symlinks:
1639        may_delete.append((link, replaced_symlinks[link]))
1640      else:
1641        always_delete.append(link)
1642  script.DeleteFiles(always_delete)
1643  script.DeleteFilesIfNotMatching(may_delete)
1644
1645  if system_diff.verbatim_targets:
1646    script.Print("Unpacking new system files...")
1647    script.UnpackPackageDir("system", "/system")
1648  if vendor_diff and vendor_diff.verbatim_targets:
1649    script.Print("Unpacking new vendor files...")
1650    script.UnpackPackageDir("vendor", "/vendor")
1651
1652  if updating_recovery and not target_has_recovery_patch:
1653    script.Print("Unpacking new recovery...")
1654    script.UnpackPackageDir("recovery", "/system")
1655
1656  system_diff.EmitRenames(script)
1657  if vendor_diff:
1658    vendor_diff.EmitRenames(script)
1659
1660  script.Print("Symlinks and permissions...")
1661
1662  # Create all the symlinks that don't already exist, or point to
1663  # somewhere different than what we want.  Delete each symlink before
1664  # creating it, since the 'symlink' command won't overwrite.
1665  to_create = []
1666  for dest, link in target_symlinks:
1667    if link in source_symlinks_d:
1668      if dest != source_symlinks_d[link]:
1669        to_create.append((dest, link))
1670    else:
1671      to_create.append((dest, link))
1672  script.DeleteFiles([i[1] for i in to_create])
1673  script.MakeSymlinks(to_create)
1674
1675  # Now that the symlinks are created, we can set all the
1676  # permissions.
1677  script.AppendScript(temp_script)
1678
1679  # Do device-specific installation (eg, write radio image).
1680  device_specific.IncrementalOTA_InstallEnd()
1681
1682  if OPTIONS.extra_script is not None:
1683    script.AppendExtra(OPTIONS.extra_script)
1684
1685  # Patch the build.prop file last, so if something fails but the
1686  # device can still come up, it appears to be the old build and will
1687  # get set the OTA package again to retry.
1688  script.Print("Patching remaining system files...")
1689  system_diff.EmitDeferredPatches(script)
1690
1691  if OPTIONS.wipe_user_data:
1692    script.Print("Erasing user data...")
1693    script.FormatPartition("/data")
1694
1695  if OPTIONS.two_step:
1696    script.AppendExtra("""
1697set_stage("%(bcb_dev)s", "");
1698endif;
1699endif;
1700""" % bcb_dev)
1701
1702  if OPTIONS.verify and system_diff:
1703    script.Print("Remounting and verifying system partition files...")
1704    script.Unmount("/system")
1705    script.Mount("/system", recovery_mount_options)
1706    system_diff.EmitExplicitTargetVerification(script)
1707
1708  if OPTIONS.verify and vendor_diff:
1709    script.Print("Remounting and verifying vendor partition files...")
1710    script.Unmount("/vendor")
1711    script.Mount("/vendor", recovery_mount_options)
1712    vendor_diff.EmitExplicitTargetVerification(script)
1713  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1714
1715  WriteMetadata(metadata, output_zip)
1716
1717
1718def main(argv):
1719
1720  def option_handler(o, a):
1721    if o == "--board_config":
1722      pass   # deprecated
1723    elif o in ("-k", "--package_key"):
1724      OPTIONS.package_key = a
1725    elif o in ("-i", "--incremental_from"):
1726      OPTIONS.incremental_source = a
1727    elif o == "--full_radio":
1728      OPTIONS.full_radio = True
1729    elif o == "--full_bootloader":
1730      OPTIONS.full_bootloader = True
1731    elif o in ("-w", "--wipe_user_data"):
1732      OPTIONS.wipe_user_data = True
1733    elif o in ("-n", "--no_prereq"):
1734      OPTIONS.omit_prereq = True
1735    elif o in ("-o", "--oem_settings"):
1736      OPTIONS.oem_source = a
1737    elif o in ("-e", "--extra_script"):
1738      OPTIONS.extra_script = a
1739    elif o in ("-a", "--aslr_mode"):
1740      if a in ("on", "On", "true", "True", "yes", "Yes"):
1741        OPTIONS.aslr_mode = True
1742      else:
1743        OPTIONS.aslr_mode = False
1744    elif o in ("-t", "--worker_threads"):
1745      if a.isdigit():
1746        OPTIONS.worker_threads = int(a)
1747      else:
1748        raise ValueError("Cannot parse value %r for option %r - only "
1749                         "integers are allowed." % (a, o))
1750    elif o in ("-2", "--two_step"):
1751      OPTIONS.two_step = True
1752    elif o == "--no_signing":
1753      OPTIONS.no_signing = True
1754    elif o == "--verify":
1755      OPTIONS.verify = True
1756    elif o == "--block":
1757      OPTIONS.block_based = True
1758    elif o in ("-b", "--binary"):
1759      OPTIONS.updater_binary = a
1760    elif o in ("--no_fallback_to_full",):
1761      OPTIONS.fallback_to_full = False
1762    elif o == "--stash_threshold":
1763      try:
1764        OPTIONS.stash_threshold = float(a)
1765      except ValueError:
1766        raise ValueError("Cannot parse value %r for option %r - expecting "
1767                         "a float" % (a, o))
1768    elif o == "--gen_verify":
1769      OPTIONS.gen_verify = True
1770    elif o == "--log_diff":
1771      OPTIONS.log_diff = a
1772    else:
1773      return False
1774    return True
1775
1776  args = common.ParseOptions(argv, __doc__,
1777                             extra_opts="b:k:i:d:wne:t:a:2o:",
1778                             extra_long_opts=[
1779                                 "board_config=",
1780                                 "package_key=",
1781                                 "incremental_from=",
1782                                 "full_radio",
1783                                 "full_bootloader",
1784                                 "wipe_user_data",
1785                                 "no_prereq",
1786                                 "extra_script=",
1787                                 "worker_threads=",
1788                                 "aslr_mode=",
1789                                 "two_step",
1790                                 "no_signing",
1791                                 "block",
1792                                 "binary=",
1793                                 "oem_settings=",
1794                                 "verify",
1795                                 "no_fallback_to_full",
1796                                 "stash_threshold=",
1797                                 "gen_verify",
1798                                 "log_diff=",
1799                             ], extra_option_handler=option_handler)
1800
1801  if len(args) != 2:
1802    common.Usage(__doc__)
1803    sys.exit(1)
1804
1805  # Load the dict file from the zip directly to have a peek at the OTA type.
1806  # For packages using A/B update, unzipping is not needed.
1807  input_zip = zipfile.ZipFile(args[0], "r")
1808  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1809  common.ZipClose(input_zip)
1810
1811  ab_update = OPTIONS.info_dict.get("ab_update") == "true"
1812
1813  if ab_update:
1814    if OPTIONS.incremental_source is not None:
1815      OPTIONS.target_info_dict = OPTIONS.info_dict
1816      source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
1817      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1818      common.ZipClose(source_zip)
1819
1820    if OPTIONS.verbose:
1821      print "--- target info ---"
1822      common.DumpInfoDict(OPTIONS.info_dict)
1823
1824      if OPTIONS.incremental_source is not None:
1825        print "--- source info ---"
1826        common.DumpInfoDict(OPTIONS.source_info_dict)
1827
1828    WriteABOTAPackageWithBrilloScript(
1829        target_file=args[0],
1830        output_file=args[1],
1831        source_file=OPTIONS.incremental_source)
1832
1833    print "done."
1834    return
1835
1836  if OPTIONS.extra_script is not None:
1837    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1838
1839  print "unzipping target target-files..."
1840  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1841
1842  OPTIONS.target_tmp = OPTIONS.input_tmp
1843  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
1844
1845  if OPTIONS.verbose:
1846    print "--- target info ---"
1847    common.DumpInfoDict(OPTIONS.info_dict)
1848
1849  # If the caller explicitly specified the device-specific extensions
1850  # path via -s/--device_specific, use that.  Otherwise, use
1851  # META/releasetools.py if it is present in the target target_files.
1852  # Otherwise, take the path of the file from 'tool_extensions' in the
1853  # info dict and look for that in the local filesystem, relative to
1854  # the current directory.
1855
1856  if OPTIONS.device_specific is None:
1857    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1858    if os.path.exists(from_input):
1859      print "(using device-specific extensions from target_files)"
1860      OPTIONS.device_specific = from_input
1861    else:
1862      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1863
1864  if OPTIONS.device_specific is not None:
1865    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1866
1867  if OPTIONS.info_dict.get("no_recovery") == "true":
1868    raise common.ExternalError(
1869        "--- target build has specified no recovery ---")
1870
1871  # Use the default key to sign the package if not specified with package_key.
1872  if not OPTIONS.no_signing:
1873    if OPTIONS.package_key is None:
1874      OPTIONS.package_key = OPTIONS.info_dict.get(
1875          "default_system_dev_certificate",
1876          "build/target/product/security/testkey")
1877
1878  # Set up the output zip. Create a temporary zip file if signing is needed.
1879  if OPTIONS.no_signing:
1880    if os.path.exists(args[1]):
1881      os.unlink(args[1])
1882    output_zip = zipfile.ZipFile(args[1], "w",
1883                                 compression=zipfile.ZIP_DEFLATED)
1884  else:
1885    temp_zip_file = tempfile.NamedTemporaryFile()
1886    output_zip = zipfile.ZipFile(temp_zip_file, "w",
1887                                 compression=zipfile.ZIP_DEFLATED)
1888
1889  # Non A/B OTAs rely on /cache partition to store temporary files.
1890  cache_size = OPTIONS.info_dict.get("cache_size", None)
1891  if cache_size is None:
1892    print "--- can't determine the cache partition size ---"
1893  OPTIONS.cache_size = cache_size
1894
1895  # Generate a verify package.
1896  if OPTIONS.gen_verify:
1897    WriteVerifyPackage(input_zip, output_zip)
1898
1899  # Generate a full OTA.
1900  elif OPTIONS.incremental_source is None:
1901    WriteFullOTAPackage(input_zip, output_zip)
1902
1903  # Generate an incremental OTA. It will fall back to generate a full OTA on
1904  # failure unless no_fallback_to_full is specified.
1905  else:
1906    print "unzipping source target-files..."
1907    OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1908        OPTIONS.incremental_source)
1909    OPTIONS.target_info_dict = OPTIONS.info_dict
1910    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
1911                                                   OPTIONS.source_tmp)
1912    if OPTIONS.verbose:
1913      print "--- source info ---"
1914      common.DumpInfoDict(OPTIONS.source_info_dict)
1915    try:
1916      WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1917      if OPTIONS.log_diff:
1918        out_file = open(OPTIONS.log_diff, 'w')
1919        import target_files_diff
1920        target_files_diff.recursiveDiff('',
1921                                        OPTIONS.source_tmp,
1922                                        OPTIONS.input_tmp,
1923                                        out_file)
1924        out_file.close()
1925    except ValueError:
1926      if not OPTIONS.fallback_to_full:
1927        raise
1928      print "--- failed to build incremental; falling back to full ---"
1929      OPTIONS.incremental_source = None
1930      WriteFullOTAPackage(input_zip, output_zip)
1931
1932  common.ZipClose(output_zip)
1933
1934  # Sign the generated zip package unless no_signing is specified.
1935  if not OPTIONS.no_signing:
1936    SignOutput(temp_zip_file.name, args[1])
1937    temp_zip_file.close()
1938
1939  print "done."
1940
1941
1942if __name__ == '__main__':
1943  try:
1944    common.CloseInheritedPipes()
1945    main(sys.argv[1:])
1946  except common.ExternalError as e:
1947    print
1948    print "   ERROR: %s" % (e,)
1949    print
1950    sys.exit(1)
1951  finally:
1952    common.Cleanup()
1953