ota_from_target_files.py revision dd24da9ec91b74b7e3c8d1af9f1f9f792d41ac4d
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.source_info_dict.get("multistage_support", None):
842      assert False, "two-step packages not supported by this build"
843    fs = OPTIONS.source_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(
889        "/boot", OPTIONS.source_info_dict)
890    d = common.Difference(target_boot, source_boot)
891    _, _, d = d.ComputePatch()
892    if d is None:
893      include_full_boot = True
894      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
895    else:
896      include_full_boot = False
897
898      print "boot      target: %d  source: %d  diff: %d" % (
899          target_boot.size, source_boot.size, len(d))
900
901      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
902
903      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
904                        (boot_type, boot_device,
905                         source_boot.size, source_boot.sha1,
906                         target_boot.size, target_boot.sha1))
907
908  device_specific.IncrementalOTA_VerifyEnd()
909
910  if OPTIONS.two_step:
911    script.WriteRawImage("/boot", "recovery.img")
912    script.AppendExtra("""
913set_stage("%(bcb_dev)s", "2/3");
914reboot_now("%(bcb_dev)s", "");
915else
916""" % bcb_dev)
917
918  # Verify the existing partitions.
919  system_diff.WriteVerifyScript(script)
920  if vendor_diff:
921    vendor_diff.WriteVerifyScript(script)
922
923  script.Comment("---- start making changes here ----")
924
925  device_specific.IncrementalOTA_InstallBegin()
926
927  system_diff.WriteScript(script, output_zip,
928                          progress=0.8 if vendor_diff else 0.9)
929
930  if vendor_diff:
931    vendor_diff.WriteScript(script, output_zip, progress=0.1)
932
933  if OPTIONS.two_step:
934    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
935    script.WriteRawImage("/boot", "boot.img")
936    print "writing full boot image (forced by two-step mode)"
937
938  if not OPTIONS.two_step:
939    if updating_boot:
940      if include_full_boot:
941        print "boot image changed; including full."
942        script.Print("Installing boot image...")
943        script.WriteRawImage("/boot", "boot.img")
944      else:
945        # Produce the boot image by applying a patch to the current
946        # contents of the boot partition, and write it back to the
947        # partition.
948        print "boot image changed; including patch."
949        script.Print("Patching boot image...")
950        script.ShowProgress(0.1, 10)
951        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
952                          % (boot_type, boot_device,
953                             source_boot.size, source_boot.sha1,
954                             target_boot.size, target_boot.sha1),
955                          "-",
956                          target_boot.size, target_boot.sha1,
957                          source_boot.sha1, "patch/boot.img.p")
958    else:
959      print "boot image unchanged; skipping."
960
961  # Do device-specific installation (eg, write radio image).
962  device_specific.IncrementalOTA_InstallEnd()
963
964  if OPTIONS.extra_script is not None:
965    script.AppendExtra(OPTIONS.extra_script)
966
967  if OPTIONS.wipe_user_data:
968    script.Print("Erasing user data...")
969    script.FormatPartition("/data")
970
971  if OPTIONS.two_step:
972    script.AppendExtra("""
973set_stage("%(bcb_dev)s", "");
974endif;
975endif;
976""" % bcb_dev)
977
978  script.SetProgress(1)
979  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
980  WriteMetadata(metadata, output_zip)
981
982
983class FileDifference(object):
984  def __init__(self, partition, source_zip, target_zip, output_zip):
985    self.deferred_patch_list = None
986    print "Loading target..."
987    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
988    print "Loading source..."
989    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
990
991    self.verbatim_targets = verbatim_targets = []
992    self.patch_list = patch_list = []
993    diffs = []
994    self.renames = renames = {}
995    known_paths = set()
996    largest_source_size = 0
997
998    matching_file_cache = {}
999    for fn, sf in source_data.items():
1000      assert fn == sf.name
1001      matching_file_cache["path:" + fn] = sf
1002      if fn in target_data.keys():
1003        AddToKnownPaths(fn, known_paths)
1004      # Only allow eligibility for filename/sha matching
1005      # if there isn't a perfect path match.
1006      if target_data.get(sf.name) is None:
1007        matching_file_cache["file:" + fn.split("/")[-1]] = sf
1008        matching_file_cache["sha:" + sf.sha1] = sf
1009
1010    for fn in sorted(target_data.keys()):
1011      tf = target_data[fn]
1012      assert fn == tf.name
1013      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1014      if sf is not None and sf.name != tf.name:
1015        print "File has moved from " + sf.name + " to " + tf.name
1016        renames[sf.name] = tf
1017
1018      if sf is None or fn in OPTIONS.require_verbatim:
1019        # This file should be included verbatim
1020        if fn in OPTIONS.prohibit_verbatim:
1021          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1022        print "send", fn, "verbatim"
1023        tf.AddToZip(output_zip)
1024        verbatim_targets.append((fn, tf.size, tf.sha1))
1025        if fn in target_data.keys():
1026          AddToKnownPaths(fn, known_paths)
1027      elif tf.sha1 != sf.sha1:
1028        # File is different; consider sending as a patch
1029        diffs.append(common.Difference(tf, sf))
1030      else:
1031        # Target file data identical to source (may still be renamed)
1032        pass
1033
1034    common.ComputeDifferences(diffs)
1035
1036    for diff in diffs:
1037      tf, sf, d = diff.GetPatch()
1038      path = "/".join(tf.name.split("/")[:-1])
1039      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1040          path not in known_paths:
1041        # patch is almost as big as the file; don't bother patching
1042        # or a patch + rename cannot take place due to the target
1043        # directory not existing
1044        tf.AddToZip(output_zip)
1045        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1046        if sf.name in renames:
1047          del renames[sf.name]
1048        AddToKnownPaths(tf.name, known_paths)
1049      else:
1050        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1051        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1052        largest_source_size = max(largest_source_size, sf.size)
1053
1054    self.largest_source_size = largest_source_size
1055
1056  def EmitVerification(self, script):
1057    so_far = 0
1058    for tf, sf, _, _ in self.patch_list:
1059      if tf.name != sf.name:
1060        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1061      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1062      so_far += sf.size
1063    return so_far
1064
1065  def EmitExplicitTargetVerification(self, script):
1066    for fn, _, sha1 in self.verbatim_targets:
1067      if fn[-1] != "/":
1068        script.FileCheck("/"+fn, sha1)
1069    for tf, _, _, _ in self.patch_list:
1070      script.FileCheck(tf.name, tf.sha1)
1071
1072  def RemoveUnneededFiles(self, script, extras=()):
1073    script.DeleteFiles(
1074        ["/" + i[0] for i in self.verbatim_targets] +
1075        ["/" + i for i in sorted(self.source_data)
1076         if i not in self.target_data and i not in self.renames] +
1077        list(extras))
1078
1079  def TotalPatchSize(self):
1080    return sum(i[1].size for i in self.patch_list)
1081
1082  def EmitPatches(self, script, total_patch_size, so_far):
1083    self.deferred_patch_list = deferred_patch_list = []
1084    for item in self.patch_list:
1085      tf, sf, _, _ = item
1086      if tf.name == "system/build.prop":
1087        deferred_patch_list.append(item)
1088        continue
1089      if sf.name != tf.name:
1090        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1091      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1092                        "patch/" + sf.name + ".p")
1093      so_far += tf.size
1094      script.SetProgress(so_far / total_patch_size)
1095    return so_far
1096
1097  def EmitDeferredPatches(self, script):
1098    for item in self.deferred_patch_list:
1099      tf, sf, _, _ = item
1100      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1101                        "patch/" + sf.name + ".p")
1102    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1103
1104  def EmitRenames(self, script):
1105    if len(self.renames) > 0:
1106      script.Print("Renaming files...")
1107      for src, tgt in self.renames.iteritems():
1108        print "Renaming " + src + " to " + tgt.name
1109        script.RenameFile(src, tgt.name)
1110
1111
1112def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1113  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1114  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1115
1116  if (OPTIONS.block_based and
1117      target_has_recovery_patch and
1118      source_has_recovery_patch):
1119    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1120
1121  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1122  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1123
1124  if source_version == 0:
1125    print ("WARNING: generating edify script for a source that "
1126           "can't install it.")
1127  script = edify_generator.EdifyGenerator(
1128      source_version, OPTIONS.target_info_dict,
1129      fstab=OPTIONS.source_info_dict["fstab"])
1130
1131  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1132  recovery_mount_options = OPTIONS.source_info_dict.get(
1133      "recovery_mount_options")
1134  oem_dict = None
1135  if oem_props is not None and len(oem_props) > 0:
1136    if OPTIONS.oem_source is None:
1137      raise common.ExternalError("OEM source required for this build")
1138    script.Mount("/oem", recovery_mount_options)
1139    oem_dict = common.LoadDictionaryFromLines(
1140        open(OPTIONS.oem_source).readlines())
1141
1142  metadata = {
1143      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1144                                   OPTIONS.source_info_dict),
1145      "post-timestamp": GetBuildProp("ro.build.date.utc",
1146                                     OPTIONS.target_info_dict),
1147  }
1148
1149  device_specific = common.DeviceSpecificParams(
1150      source_zip=source_zip,
1151      source_version=source_version,
1152      target_zip=target_zip,
1153      target_version=target_version,
1154      output_zip=output_zip,
1155      script=script,
1156      metadata=metadata,
1157      info_dict=OPTIONS.info_dict)
1158
1159  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1160  script.Mount("/system", recovery_mount_options)
1161  if HasVendorPartition(target_zip):
1162    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1163    script.Mount("/vendor", recovery_mount_options)
1164  else:
1165    vendor_diff = None
1166
1167  target_fp = CalculateFingerprint(oem_props, oem_dict,
1168                                   OPTIONS.target_info_dict)
1169  source_fp = CalculateFingerprint(oem_props, oem_dict,
1170                                   OPTIONS.source_info_dict)
1171
1172  if oem_props is None:
1173    script.AssertSomeFingerprint(source_fp, target_fp)
1174  else:
1175    script.AssertSomeThumbprint(
1176        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1177        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1178
1179  metadata["pre-build"] = source_fp
1180  metadata["post-build"] = target_fp
1181
1182  source_boot = common.GetBootableImage(
1183      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1184      OPTIONS.source_info_dict)
1185  target_boot = common.GetBootableImage(
1186      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1187  updating_boot = (not OPTIONS.two_step and
1188                   (source_boot.data != target_boot.data))
1189
1190  source_recovery = common.GetBootableImage(
1191      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1192      OPTIONS.source_info_dict)
1193  target_recovery = common.GetBootableImage(
1194      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1195  updating_recovery = (source_recovery.data != target_recovery.data)
1196
1197  # Here's how we divide up the progress bar:
1198  #  0.1 for verifying the start state (PatchCheck calls)
1199  #  0.8 for applying patches (ApplyPatch calls)
1200  #  0.1 for unpacking verbatim files, symlinking, and doing the
1201  #      device-specific commands.
1202
1203  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1204  device_specific.IncrementalOTA_Assertions()
1205
1206  # Two-step incremental package strategy (in chronological order,
1207  # which is *not* the order in which the generated script has
1208  # things):
1209  #
1210  # if stage is not "2/3" or "3/3":
1211  #    do verification on current system
1212  #    write recovery image to boot partition
1213  #    set stage to "2/3"
1214  #    reboot to boot partition and restart recovery
1215  # else if stage is "2/3":
1216  #    write recovery image to recovery partition
1217  #    set stage to "3/3"
1218  #    reboot to recovery partition and restart recovery
1219  # else:
1220  #    (stage must be "3/3")
1221  #    perform update:
1222  #       patch system files, etc.
1223  #       force full install of new boot image
1224  #       set up system to update recovery partition on first boot
1225  #    complete script normally
1226  #    (allow recovery to mark itself finished and reboot)
1227
1228  if OPTIONS.two_step:
1229    if not OPTIONS.source_info_dict.get("multistage_support", None):
1230      assert False, "two-step packages not supported by this build"
1231    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
1232    assert fs.fs_type.upper() == "EMMC", \
1233        "two-step packages only supported on devices with EMMC /misc partitions"
1234    bcb_dev = {"bcb_dev": fs.device}
1235    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1236    script.AppendExtra("""
1237if get_stage("%(bcb_dev)s") == "2/3" then
1238""" % bcb_dev)
1239    script.AppendExtra("sleep(20);\n")
1240    script.WriteRawImage("/recovery", "recovery.img")
1241    script.AppendExtra("""
1242set_stage("%(bcb_dev)s", "3/3");
1243reboot_now("%(bcb_dev)s", "recovery");
1244else if get_stage("%(bcb_dev)s") != "3/3" then
1245""" % bcb_dev)
1246
1247  # Dump fingerprints
1248  script.Print("Source: %s" % (source_fp,))
1249  script.Print("Target: %s" % (target_fp,))
1250
1251  script.Print("Verifying current system...")
1252
1253  device_specific.IncrementalOTA_VerifyBegin()
1254
1255  script.ShowProgress(0.1, 0)
1256  so_far = system_diff.EmitVerification(script)
1257  if vendor_diff:
1258    so_far += vendor_diff.EmitVerification(script)
1259
1260  if updating_boot:
1261    d = common.Difference(target_boot, source_boot)
1262    _, _, d = d.ComputePatch()
1263    print "boot      target: %d  source: %d  diff: %d" % (
1264        target_boot.size, source_boot.size, len(d))
1265
1266    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1267
1268    boot_type, boot_device = common.GetTypeAndDevice(
1269        "/boot", OPTIONS.source_info_dict)
1270
1271    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1272                      (boot_type, boot_device,
1273                       source_boot.size, source_boot.sha1,
1274                       target_boot.size, target_boot.sha1))
1275    so_far += source_boot.size
1276
1277  size = []
1278  if system_diff.patch_list:
1279    size.append(system_diff.largest_source_size)
1280  if vendor_diff:
1281    if vendor_diff.patch_list:
1282      size.append(vendor_diff.largest_source_size)
1283  if size or updating_recovery or updating_boot:
1284    script.CacheFreeSpaceCheck(max(size))
1285
1286  device_specific.IncrementalOTA_VerifyEnd()
1287
1288  if OPTIONS.two_step:
1289    script.WriteRawImage("/boot", "recovery.img")
1290    script.AppendExtra("""
1291set_stage("%(bcb_dev)s", "2/3");
1292reboot_now("%(bcb_dev)s", "");
1293else
1294""" % bcb_dev)
1295
1296  script.Comment("---- start making changes here ----")
1297
1298  device_specific.IncrementalOTA_InstallBegin()
1299
1300  if OPTIONS.two_step:
1301    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1302    script.WriteRawImage("/boot", "boot.img")
1303    print "writing full boot image (forced by two-step mode)"
1304
1305  script.Print("Removing unneeded files...")
1306  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1307  if vendor_diff:
1308    vendor_diff.RemoveUnneededFiles(script)
1309
1310  script.ShowProgress(0.8, 0)
1311  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1312  if vendor_diff:
1313    total_patch_size += vendor_diff.TotalPatchSize()
1314  if updating_boot:
1315    total_patch_size += target_boot.size
1316
1317  script.Print("Patching system files...")
1318  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1319  if vendor_diff:
1320    script.Print("Patching vendor files...")
1321    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1322
1323  if not OPTIONS.two_step:
1324    if updating_boot:
1325      # Produce the boot image by applying a patch to the current
1326      # contents of the boot partition, and write it back to the
1327      # partition.
1328      script.Print("Patching boot image...")
1329      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1330                        % (boot_type, boot_device,
1331                           source_boot.size, source_boot.sha1,
1332                           target_boot.size, target_boot.sha1),
1333                        "-",
1334                        target_boot.size, target_boot.sha1,
1335                        source_boot.sha1, "patch/boot.img.p")
1336      so_far += target_boot.size
1337      script.SetProgress(so_far / total_patch_size)
1338      print "boot image changed; including."
1339    else:
1340      print "boot image unchanged; skipping."
1341
1342  system_items = ItemSet("system", "META/filesystem_config.txt")
1343  if vendor_diff:
1344    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1345
1346  if updating_recovery:
1347    # Recovery is generated as a patch using both the boot image
1348    # (which contains the same linux kernel as recovery) and the file
1349    # /system/etc/recovery-resource.dat (which contains all the images
1350    # used in the recovery UI) as sources.  This lets us minimize the
1351    # size of the patch, which must be included in every OTA package.
1352    #
1353    # For older builds where recovery-resource.dat is not present, we
1354    # use only the boot image as the source.
1355
1356    if not target_has_recovery_patch:
1357      def output_sink(fn, data):
1358        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1359        system_items.Get("system/" + fn)
1360
1361      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1362                               target_recovery, target_boot)
1363      script.DeleteFiles(["/system/recovery-from-boot.p",
1364                          "/system/etc/recovery.img",
1365                          "/system/etc/install-recovery.sh"])
1366    print "recovery image changed; including as patch from boot."
1367  else:
1368    print "recovery image unchanged; skipping."
1369
1370  script.ShowProgress(0.1, 10)
1371
1372  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1373  if vendor_diff:
1374    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1375
1376  temp_script = script.MakeTemporary()
1377  system_items.GetMetadata(target_zip)
1378  system_items.Get("system").SetPermissions(temp_script)
1379  if vendor_diff:
1380    vendor_items.GetMetadata(target_zip)
1381    vendor_items.Get("vendor").SetPermissions(temp_script)
1382
1383  # Note that this call will mess up the trees of Items, so make sure
1384  # we're done with them.
1385  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1386  if vendor_diff:
1387    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1388
1389  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1390  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1391
1392  # Delete all the symlinks in source that aren't in target.  This
1393  # needs to happen before verbatim files are unpacked, in case a
1394  # symlink in the source is replaced by a real file in the target.
1395  to_delete = []
1396  for dest, link in source_symlinks:
1397    if link not in target_symlinks_d:
1398      to_delete.append(link)
1399  script.DeleteFiles(to_delete)
1400
1401  if system_diff.verbatim_targets:
1402    script.Print("Unpacking new system files...")
1403    script.UnpackPackageDir("system", "/system")
1404  if vendor_diff and vendor_diff.verbatim_targets:
1405    script.Print("Unpacking new vendor files...")
1406    script.UnpackPackageDir("vendor", "/vendor")
1407
1408  if updating_recovery and not target_has_recovery_patch:
1409    script.Print("Unpacking new recovery...")
1410    script.UnpackPackageDir("recovery", "/system")
1411
1412  system_diff.EmitRenames(script)
1413  if vendor_diff:
1414    vendor_diff.EmitRenames(script)
1415
1416  script.Print("Symlinks and permissions...")
1417
1418  # Create all the symlinks that don't already exist, or point to
1419  # somewhere different than what we want.  Delete each symlink before
1420  # creating it, since the 'symlink' command won't overwrite.
1421  to_create = []
1422  for dest, link in target_symlinks:
1423    if link in source_symlinks_d:
1424      if dest != source_symlinks_d[link]:
1425        to_create.append((dest, link))
1426    else:
1427      to_create.append((dest, link))
1428  script.DeleteFiles([i[1] for i in to_create])
1429  script.MakeSymlinks(to_create)
1430
1431  # Now that the symlinks are created, we can set all the
1432  # permissions.
1433  script.AppendScript(temp_script)
1434
1435  # Do device-specific installation (eg, write radio image).
1436  device_specific.IncrementalOTA_InstallEnd()
1437
1438  if OPTIONS.extra_script is not None:
1439    script.AppendExtra(OPTIONS.extra_script)
1440
1441  # Patch the build.prop file last, so if something fails but the
1442  # device can still come up, it appears to be the old build and will
1443  # get set the OTA package again to retry.
1444  script.Print("Patching remaining system files...")
1445  system_diff.EmitDeferredPatches(script)
1446
1447  if OPTIONS.wipe_user_data:
1448    script.Print("Erasing user data...")
1449    script.FormatPartition("/data")
1450
1451  if OPTIONS.two_step:
1452    script.AppendExtra("""
1453set_stage("%(bcb_dev)s", "");
1454endif;
1455endif;
1456""" % bcb_dev)
1457
1458  if OPTIONS.verify and system_diff:
1459    script.Print("Remounting and verifying system partition files...")
1460    script.Unmount("/system")
1461    script.Mount("/system")
1462    system_diff.EmitExplicitTargetVerification(script)
1463
1464  if OPTIONS.verify and vendor_diff:
1465    script.Print("Remounting and verifying vendor partition files...")
1466    script.Unmount("/vendor")
1467    script.Mount("/vendor")
1468    vendor_diff.EmitExplicitTargetVerification(script)
1469  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1470
1471  WriteMetadata(metadata, output_zip)
1472
1473
1474def main(argv):
1475
1476  def option_handler(o, a):
1477    if o == "--board_config":
1478      pass   # deprecated
1479    elif o in ("-k", "--package_key"):
1480      OPTIONS.package_key = a
1481    elif o in ("-i", "--incremental_from"):
1482      OPTIONS.incremental_source = a
1483    elif o == "--full_radio":
1484      OPTIONS.full_radio = True
1485    elif o in ("-w", "--wipe_user_data"):
1486      OPTIONS.wipe_user_data = True
1487    elif o in ("-n", "--no_prereq"):
1488      OPTIONS.omit_prereq = True
1489    elif o in ("-o", "--oem_settings"):
1490      OPTIONS.oem_source = a
1491    elif o in ("-e", "--extra_script"):
1492      OPTIONS.extra_script = a
1493    elif o in ("-a", "--aslr_mode"):
1494      if a in ("on", "On", "true", "True", "yes", "Yes"):
1495        OPTIONS.aslr_mode = True
1496      else:
1497        OPTIONS.aslr_mode = False
1498    elif o in ("-t", "--worker_threads"):
1499      if a.isdigit():
1500        OPTIONS.worker_threads = int(a)
1501      else:
1502        raise ValueError("Cannot parse value %r for option %r - only "
1503                         "integers are allowed." % (a, o))
1504    elif o in ("-2", "--two_step"):
1505      OPTIONS.two_step = True
1506    elif o == "--no_signing":
1507      OPTIONS.no_signing = True
1508    elif o == "--verify":
1509      OPTIONS.verify = True
1510    elif o == "--block":
1511      OPTIONS.block_based = True
1512    elif o in ("-b", "--binary"):
1513      OPTIONS.updater_binary = a
1514    elif o in ("--no_fallback_to_full",):
1515      OPTIONS.fallback_to_full = False
1516    elif o == "--stash_threshold":
1517      try:
1518        OPTIONS.stash_threshold = float(a)
1519      except ValueError:
1520        raise ValueError("Cannot parse value %r for option %r - expecting "
1521                         "a float" % (a, o))
1522    else:
1523      return False
1524    return True
1525
1526  args = common.ParseOptions(argv, __doc__,
1527                             extra_opts="b:k:i:d:wne:t:a:2o:",
1528                             extra_long_opts=[
1529                                 "board_config=",
1530                                 "package_key=",
1531                                 "incremental_from=",
1532                                 "full_radio",
1533                                 "wipe_user_data",
1534                                 "no_prereq",
1535                                 "extra_script=",
1536                                 "worker_threads=",
1537                                 "aslr_mode=",
1538                                 "two_step",
1539                                 "no_signing",
1540                                 "block",
1541                                 "binary=",
1542                                 "oem_settings=",
1543                                 "verify",
1544                                 "no_fallback_to_full",
1545                                 "stash_threshold=",
1546                             ], extra_option_handler=option_handler)
1547
1548  if len(args) != 2:
1549    common.Usage(__doc__)
1550    sys.exit(1)
1551
1552  if OPTIONS.extra_script is not None:
1553    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1554
1555  print "unzipping target target-files..."
1556  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1557
1558  OPTIONS.target_tmp = OPTIONS.input_tmp
1559  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
1560
1561  if OPTIONS.verbose:
1562    print "--- target info ---"
1563    common.DumpInfoDict(OPTIONS.info_dict)
1564
1565  # If the caller explicitly specified the device-specific extensions
1566  # path via -s/--device_specific, use that.  Otherwise, use
1567  # META/releasetools.py if it is present in the target target_files.
1568  # Otherwise, take the path of the file from 'tool_extensions' in the
1569  # info dict and look for that in the local filesystem, relative to
1570  # the current directory.
1571
1572  if OPTIONS.device_specific is None:
1573    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1574    if os.path.exists(from_input):
1575      print "(using device-specific extensions from target_files)"
1576      OPTIONS.device_specific = from_input
1577    else:
1578      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1579
1580  if OPTIONS.device_specific is not None:
1581    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1582
1583  while True:
1584
1585    if OPTIONS.no_signing:
1586      if os.path.exists(args[1]):
1587        os.unlink(args[1])
1588      output_zip = zipfile.ZipFile(args[1], "w",
1589                                   compression=zipfile.ZIP_DEFLATED)
1590    else:
1591      temp_zip_file = tempfile.NamedTemporaryFile()
1592      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1593                                   compression=zipfile.ZIP_DEFLATED)
1594
1595    cache_size = OPTIONS.info_dict.get("cache_size", None)
1596    if cache_size is None:
1597      raise RuntimeError("can't determine the cache partition size")
1598    OPTIONS.cache_size = cache_size
1599
1600    if OPTIONS.incremental_source is None:
1601      WriteFullOTAPackage(input_zip, output_zip)
1602      if OPTIONS.package_key is None:
1603        OPTIONS.package_key = OPTIONS.info_dict.get(
1604            "default_system_dev_certificate",
1605            "build/target/product/security/testkey")
1606      common.ZipClose(output_zip)
1607      break
1608
1609    else:
1610      print "unzipping source target-files..."
1611      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1612          OPTIONS.incremental_source)
1613      OPTIONS.target_info_dict = OPTIONS.info_dict
1614      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
1615                                                     OPTIONS.source_tmp)
1616      if OPTIONS.package_key is None:
1617        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1618            "default_system_dev_certificate",
1619            "build/target/product/security/testkey")
1620      if OPTIONS.verbose:
1621        print "--- source info ---"
1622        common.DumpInfoDict(OPTIONS.source_info_dict)
1623      try:
1624        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1625        common.ZipClose(output_zip)
1626        break
1627      except ValueError:
1628        if not OPTIONS.fallback_to_full:
1629          raise
1630        print "--- failed to build incremental; falling back to full ---"
1631        OPTIONS.incremental_source = None
1632        common.ZipClose(output_zip)
1633
1634  if not OPTIONS.no_signing:
1635    SignOutput(temp_zip_file.name, args[1])
1636    temp_zip_file.close()
1637
1638  print "done."
1639
1640
1641if __name__ == '__main__':
1642  try:
1643    common.CloseInheritedPipes()
1644    main(sys.argv[1:])
1645  except common.ExternalError as e:
1646    print
1647    print "   ERROR: %s" % (e,)
1648    print
1649    sys.exit(1)
1650  finally:
1651    common.Cleanup()
1652