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