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