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