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