ota_from_target_files.py revision 1e7f6f7442ca2addc8e4447d3070c625dbba8d3f
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) == 0o120777
147
148def IsRegular(info):
149  """Return true if the zipfile.ZipInfo object passed in represents a
150  symlink."""
151  return (info.external_attr >> 28) == 0o10
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.
222    i = self.ITEMS.get("system/recovery-from-boot.p", None)
223    if i:
224      i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o644, None, None
225    i = self.ITEMS.get("system/etc/install-recovery.sh", None)
226    if i:
227      i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o544, None, None
228
229
230class Item(object):
231  """Items represent the metadata (user, group, mode) of files and
232  directories in the system image."""
233  def __init__(self, itemset, name, is_dir=False):
234    self.itemset = itemset
235    self.name = name
236    self.uid = None
237    self.gid = None
238    self.mode = None
239    self.selabel = None
240    self.capabilities = None
241    self.is_dir = is_dir
242    self.descendants = None
243    self.best_subtree = None
244
245    if name:
246      self.parent = itemset.Get(os.path.dirname(name), is_dir=True)
247      self.parent.children.append(self)
248    else:
249      self.parent = None
250    if self.is_dir:
251      self.children = []
252
253  def Dump(self, indent=0):
254    if self.uid is not None:
255      print "%s%s %d %d %o" % (
256          "  " * indent, self.name, self.uid, self.gid, self.mode)
257    else:
258      print "%s%s %s %s %s" % (
259          "  " * indent, self.name, self.uid, self.gid, self.mode)
260    if self.is_dir:
261      print "%s%s" % ("  "*indent, self.descendants)
262      print "%s%s" % ("  "*indent, self.best_subtree)
263      for i in self.children:
264        i.Dump(indent=indent+1)
265
266  def CountChildMetadata(self):
267    """Count up the (uid, gid, mode, selabel, capabilities) tuples for
268    all children and determine the best strategy for using set_perm_recursive
269    and set_perm to correctly chown/chmod all the files to their desired
270    values.  Recursively calls itself for all descendants.
271
272    Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count}
273    counting up all descendants of this node.  (dmode or fmode may be None.)
274    Also sets the best_subtree of each directory Item to the (uid, gid, dmode,
275    fmode, selabel, capabilities) tuple that will match the most descendants of
276    that Item.
277    """
278
279    assert self.is_dir
280    key = (self.uid, self.gid, self.mode, None, self.selabel,
281           self.capabilities)
282    self.descendants = {key: 1}
283    d = self.descendants
284    for i in self.children:
285      if i.is_dir:
286        for k, v in i.CountChildMetadata().iteritems():
287          d[k] = d.get(k, 0) + v
288      else:
289        k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities)
290        d[k] = d.get(k, 0) + 1
291
292    # Find the (uid, gid, dmode, fmode, selabel, capabilities)
293    # tuple that matches the most descendants.
294
295    # First, find the (uid, gid) pair that matches the most
296    # descendants.
297    ug = {}
298    for (uid, gid, _, _, _, _), count in d.iteritems():
299      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
300    ug = MostPopularKey(ug, (0, 0))
301
302    # Now find the dmode, fmode, selabel, and capabilities that match
303    # the most descendants with that (uid, gid), and choose those.
304    best_dmode = (0, 0o755)
305    best_fmode = (0, 0o644)
306    best_selabel = (0, None)
307    best_capabilities = (0, None)
308    for k, count in d.iteritems():
309      if k[:2] != ug:
310        continue
311      if k[2] is not None and count >= best_dmode[0]:
312        best_dmode = (count, k[2])
313      if k[3] is not None and count >= best_fmode[0]:
314        best_fmode = (count, k[3])
315      if k[4] is not None and count >= best_selabel[0]:
316        best_selabel = (count, k[4])
317      if k[5] is not None and count >= best_capabilities[0]:
318        best_capabilities = (count, k[5])
319    self.best_subtree = ug + (
320        best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1])
321
322    return d
323
324  def SetPermissions(self, script):
325    """Append set_perm/set_perm_recursive commands to 'script' to
326    set all permissions, users, and groups for the tree of files
327    rooted at 'self'."""
328
329    self.CountChildMetadata()
330
331    def recurse(item, current):
332      # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple
333      # that the current item (and all its children) have already been set to.
334      # We only need to issue set_perm/set_perm_recursive commands if we're
335      # supposed to be something different.
336      if item.is_dir:
337        if current != item.best_subtree:
338          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
339          current = item.best_subtree
340
341        if item.uid != current[0] or item.gid != current[1] or \
342           item.mode != current[2] or item.selabel != current[4] or \
343           item.capabilities != current[5]:
344          script.SetPermissions("/"+item.name, item.uid, item.gid,
345                                item.mode, item.selabel, item.capabilities)
346
347        for i in item.children:
348          recurse(i, current)
349      else:
350        if item.uid != current[0] or item.gid != current[1] or \
351               item.mode != current[3] or item.selabel != current[4] or \
352               item.capabilities != current[5]:
353          script.SetPermissions("/"+item.name, item.uid, item.gid,
354                                item.mode, item.selabel, item.capabilities)
355
356    recurse(self, (-1, -1, -1, -1, None, None))
357
358
359def CopyPartitionFiles(itemset, input_zip, output_zip=None, substitute=None):
360  """Copies files for the partition in the input zip to the output
361  zip.  Populates the Item class with their metadata, and returns a
362  list of symlinks.  output_zip may be None, in which case the copy is
363  skipped (but the other side effects still happen).  substitute is an
364  optional dict of {output filename: contents} to be output instead of
365  certain input files.
366  """
367
368  symlinks = []
369
370  partition = itemset.partition
371
372  for info in input_zip.infolist():
373    prefix = partition.upper() + "/"
374    if info.filename.startswith(prefix):
375      basefilename = info.filename[len(prefix):]
376      if IsSymlink(info):
377        symlinks.append((input_zip.read(info.filename),
378                         "/" + partition + "/" + basefilename))
379      else:
380        import copy
381        info2 = copy.copy(info)
382        fn = info2.filename = partition + "/" + basefilename
383        if substitute and fn in substitute and substitute[fn] is None:
384          continue
385        if output_zip is not None:
386          if substitute and fn in substitute:
387            data = substitute[fn]
388          else:
389            data = input_zip.read(info.filename)
390          common.ZipWriteStr(output_zip, info2, data)
391        if fn.endswith("/"):
392          itemset.Get(fn[:-1], is_dir=True)
393        else:
394          itemset.Get(fn)
395
396  symlinks.sort()
397  return symlinks
398
399
400def SignOutput(temp_zip_name, output_zip_name):
401  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
402  pw = key_passwords[OPTIONS.package_key]
403
404  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
405                  whole_file=True)
406
407
408def AppendAssertions(script, info_dict, oem_dict=None):
409  oem_props = info_dict.get("oem_fingerprint_properties")
410  if oem_props is None or len(oem_props) == 0:
411    device = GetBuildProp("ro.product.device", info_dict)
412    script.AssertDevice(device)
413  else:
414    if oem_dict is None:
415      raise common.ExternalError(
416          "No OEM file provided to answer expected assertions")
417    for prop in oem_props.split():
418      if oem_dict.get(prop) is None:
419        raise common.ExternalError(
420            "The OEM file is missing the property %s" % prop)
421      script.AssertOemProperty(prop, oem_dict.get(prop))
422
423
424def HasRecoveryPatch(target_files_zip):
425  try:
426    target_files_zip.getinfo("SYSTEM/recovery-from-boot.p")
427    return True
428  except KeyError:
429    return False
430
431def HasVendorPartition(target_files_zip):
432  try:
433    target_files_zip.getinfo("VENDOR/")
434    return True
435  except KeyError:
436    return False
437
438def GetOemProperty(name, oem_props, oem_dict, info_dict):
439  if oem_props is not None and name in oem_props:
440    return oem_dict[name]
441  return GetBuildProp(name, info_dict)
442
443
444def CalculateFingerprint(oem_props, oem_dict, info_dict):
445  if oem_props is None:
446    return GetBuildProp("ro.build.fingerprint", info_dict)
447  return "%s/%s/%s:%s" % (
448      GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
449      GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
450      GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
451      GetBuildProp("ro.build.thumbprint", info_dict))
452
453
454def GetImage(which, tmpdir, info_dict):
455  # Return an image object (suitable for passing to BlockImageDiff)
456  # for the 'which' partition (most be "system" or "vendor").  If a
457  # prebuilt image and file map are found in tmpdir they are used,
458  # otherwise they are reconstructed from the individual files.
459
460  assert which in ("system", "vendor")
461
462  path = os.path.join(tmpdir, "IMAGES", which + ".img")
463  mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
464  if os.path.exists(path) and os.path.exists(mappath):
465    print "using %s.img from target-files" % (which,)
466    # This is a 'new' target-files, which already has the image in it.
467
468  else:
469    print "building %s.img from target-files" % (which,)
470
471    # This is an 'old' target-files, which does not contain images
472    # already built.  Build them.
473
474    mappath = tempfile.mkstemp()[1]
475    OPTIONS.tempfiles.append(mappath)
476
477    import add_img_to_target_files
478    if which == "system":
479      path = add_img_to_target_files.BuildSystem(
480          tmpdir, info_dict, block_list=mappath)
481    elif which == "vendor":
482      path = add_img_to_target_files.BuildVendor(
483          tmpdir, info_dict, block_list=mappath)
484
485  # Bug: http://b/20939131
486  # In ext4 filesystems, block 0 might be changed even being mounted
487  # R/O. We add it to clobbered_blocks so that it will be written to the
488  # target unconditionally. Note that they are still part of care_map.
489  clobbered_blocks = "0"
490
491  return sparse_img.SparseImage(path, mappath, clobbered_blocks)
492
493
494def WriteFullOTAPackage(input_zip, output_zip):
495  # TODO: how to determine this?  We don't know what version it will
496  # be installed on top of. For now, we expect the API just won't
497  # change very often. Similarly for fstab, it might have changed
498  # in the target build.
499  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
500
501  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
502  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
503  oem_dict = None
504  if oem_props is not None and len(oem_props) > 0:
505    if OPTIONS.oem_source is None:
506      raise common.ExternalError("OEM source required for this build")
507    script.Mount("/oem", recovery_mount_options)
508    oem_dict = common.LoadDictionaryFromLines(
509        open(OPTIONS.oem_source).readlines())
510
511  metadata = {
512      "post-build": CalculateFingerprint(oem_props, oem_dict,
513                                         OPTIONS.info_dict),
514      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
515                                   OPTIONS.info_dict),
516      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
517  }
518
519  device_specific = common.DeviceSpecificParams(
520      input_zip=input_zip,
521      input_version=OPTIONS.info_dict["recovery_api_version"],
522      output_zip=output_zip,
523      script=script,
524      input_tmp=OPTIONS.input_tmp,
525      metadata=metadata,
526      info_dict=OPTIONS.info_dict)
527
528  has_recovery_patch = HasRecoveryPatch(input_zip)
529  block_based = OPTIONS.block_based and has_recovery_patch
530
531  if not OPTIONS.omit_prereq:
532    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
533    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
534    script.AssertOlderBuild(ts, ts_text)
535
536  AppendAssertions(script, OPTIONS.info_dict, oem_dict)
537  device_specific.FullOTA_Assertions()
538
539  # Two-step package strategy (in chronological order, which is *not*
540  # the order in which the generated script has things):
541  #
542  # if stage is not "2/3" or "3/3":
543  #    write recovery image to boot partition
544  #    set stage to "2/3"
545  #    reboot to boot partition and restart recovery
546  # else if stage is "2/3":
547  #    write recovery image to recovery partition
548  #    set stage to "3/3"
549  #    reboot to recovery partition and restart recovery
550  # else:
551  #    (stage must be "3/3")
552  #    set stage to ""
553  #    do normal full package installation:
554  #       wipe and install system, boot image, etc.
555  #       set up system to update recovery partition on first boot
556  #    complete script normally
557  #    (allow recovery to mark itself finished and reboot)
558
559  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
560                                         OPTIONS.input_tmp, "RECOVERY")
561  if OPTIONS.two_step:
562    if not OPTIONS.info_dict.get("multistage_support", None):
563      assert False, "two-step packages not supported by this build"
564    fs = OPTIONS.info_dict["fstab"]["/misc"]
565    assert fs.fs_type.upper() == "EMMC", \
566        "two-step packages only supported on devices with EMMC /misc partitions"
567    bcb_dev = {"bcb_dev": fs.device}
568    common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
569    script.AppendExtra("""
570if get_stage("%(bcb_dev)s") == "2/3" then
571""" % bcb_dev)
572    script.WriteRawImage("/recovery", "recovery.img")
573    script.AppendExtra("""
574set_stage("%(bcb_dev)s", "3/3");
575reboot_now("%(bcb_dev)s", "recovery");
576else if get_stage("%(bcb_dev)s") == "3/3" then
577""" % bcb_dev)
578
579  # Dump fingerprints
580  script.Print("Target: %s" % CalculateFingerprint(
581      oem_props, oem_dict, OPTIONS.info_dict))
582
583  device_specific.FullOTA_InstallBegin()
584
585  system_progress = 0.75
586
587  if OPTIONS.wipe_user_data:
588    system_progress -= 0.1
589  if HasVendorPartition(input_zip):
590    system_progress -= 0.1
591
592  if "selinux_fc" in OPTIONS.info_dict:
593    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
594
595  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
596
597  system_items = ItemSet("system", "META/filesystem_config.txt")
598  script.ShowProgress(system_progress, 0)
599
600  if block_based:
601    # Full OTA is done as an "incremental" against an empty source
602    # image.  This has the effect of writing new data from the package
603    # to the entire partition, but lets us reuse the updater code that
604    # writes incrementals to do it.
605    system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
606    system_tgt.ResetFileMap()
607    system_diff = common.BlockDifference("system", system_tgt, src=None)
608    system_diff.WriteScript(script, output_zip)
609  else:
610    script.FormatPartition("/system")
611    script.Mount("/system", recovery_mount_options)
612    if not has_recovery_patch:
613      script.UnpackPackageDir("recovery", "/system")
614    script.UnpackPackageDir("system", "/system")
615
616    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
617    script.MakeSymlinks(symlinks)
618
619  boot_img = common.GetBootableImage("boot.img", "boot.img",
620                                     OPTIONS.input_tmp, "BOOT")
621
622  if not block_based:
623    def output_sink(fn, data):
624      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
625      system_items.Get("system/" + fn)
626
627    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
628                             recovery_img, boot_img)
629
630    system_items.GetMetadata(input_zip)
631    system_items.Get("system").SetPermissions(script)
632
633  if HasVendorPartition(input_zip):
634    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
635    script.ShowProgress(0.1, 0)
636
637    if block_based:
638      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
639      vendor_tgt.ResetFileMap()
640      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
641      vendor_diff.WriteScript(script, output_zip)
642    else:
643      script.FormatPartition("/vendor")
644      script.Mount("/vendor", recovery_mount_options)
645      script.UnpackPackageDir("vendor", "/vendor")
646
647      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
648      script.MakeSymlinks(symlinks)
649
650      vendor_items.GetMetadata(input_zip)
651      vendor_items.Get("vendor").SetPermissions(script)
652
653  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
654  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
655
656  script.ShowProgress(0.05, 5)
657  script.WriteRawImage("/boot", "boot.img")
658
659  script.ShowProgress(0.2, 10)
660  device_specific.FullOTA_InstallEnd()
661
662  if OPTIONS.extra_script is not None:
663    script.AppendExtra(OPTIONS.extra_script)
664
665  script.UnmountAll()
666
667  if OPTIONS.wipe_user_data:
668    script.ShowProgress(0.1, 10)
669    script.FormatPartition("/data")
670
671  if OPTIONS.two_step:
672    script.AppendExtra("""
673set_stage("%(bcb_dev)s", "");
674""" % bcb_dev)
675    script.AppendExtra("else\n")
676    script.WriteRawImage("/boot", "recovery.img")
677    script.AppendExtra("""
678set_stage("%(bcb_dev)s", "2/3");
679reboot_now("%(bcb_dev)s", "");
680endif;
681endif;
682""" % bcb_dev)
683  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
684  WriteMetadata(metadata, output_zip)
685
686
687def WritePolicyConfig(file_name, output_zip):
688  common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
689
690
691def WriteMetadata(metadata, output_zip):
692  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
693                     "".join(["%s=%s\n" % kv
694                              for kv in sorted(metadata.iteritems())]))
695
696
697def LoadPartitionFiles(z, partition):
698  """Load all the files from the given partition in a given target-files
699  ZipFile, and return a dict of {filename: File object}."""
700  out = {}
701  prefix = partition.upper() + "/"
702  for info in z.infolist():
703    if info.filename.startswith(prefix) and not IsSymlink(info):
704      basefilename = info.filename[len(prefix):]
705      fn = partition + "/" + basefilename
706      data = z.read(info.filename)
707      out[fn] = common.File(fn, data)
708  return out
709
710
711def GetBuildProp(prop, info_dict):
712  """Return the fingerprint of the build of a given target-files info_dict."""
713  try:
714    return info_dict.get("build.prop", {})[prop]
715  except KeyError:
716    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
717
718
719def AddToKnownPaths(filename, known_paths):
720  if filename[-1] == "/":
721    return
722  dirs = filename.split("/")[:-1]
723  while len(dirs) > 0:
724    path = "/".join(dirs)
725    if path in known_paths:
726      break
727    known_paths.add(path)
728    dirs.pop()
729
730
731def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
732  source_version = OPTIONS.source_info_dict["recovery_api_version"]
733  target_version = OPTIONS.target_info_dict["recovery_api_version"]
734
735  if source_version == 0:
736    print ("WARNING: generating edify script for a source that "
737           "can't install it.")
738  script = edify_generator.EdifyGenerator(
739      source_version, OPTIONS.target_info_dict,
740      fstab=OPTIONS.source_info_dict["fstab"])
741
742  metadata = {
743      "pre-device": GetBuildProp("ro.product.device",
744                                 OPTIONS.source_info_dict),
745      "post-timestamp": GetBuildProp("ro.build.date.utc",
746                                     OPTIONS.target_info_dict),
747  }
748
749  device_specific = common.DeviceSpecificParams(
750      source_zip=source_zip,
751      source_version=source_version,
752      target_zip=target_zip,
753      target_version=target_version,
754      output_zip=output_zip,
755      script=script,
756      metadata=metadata,
757      info_dict=OPTIONS.info_dict)
758
759  # TODO: Currently this works differently from WriteIncrementalOTAPackage().
760  # This function doesn't consider thumbprints when writing
761  # metadata["pre/post-build"]. One possible reason is that the current
762  # devices with thumbprints are all using file-based OTAs. Long term we
763  # should factor out the common parts into a shared one to avoid further
764  # divergence.
765  source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
766  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
767  metadata["pre-build"] = source_fp
768  metadata["post-build"] = target_fp
769
770  source_boot = common.GetBootableImage(
771      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
772      OPTIONS.source_info_dict)
773  target_boot = common.GetBootableImage(
774      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
775  updating_boot = (not OPTIONS.two_step and
776                   (source_boot.data != target_boot.data))
777
778  target_recovery = common.GetBootableImage(
779      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
780
781  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
782  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
783
784  blockimgdiff_version = 1
785  if OPTIONS.info_dict:
786    blockimgdiff_version = max(
787        int(i) for i in
788        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
789
790  system_diff = common.BlockDifference("system", system_tgt, system_src,
791                                       version=blockimgdiff_version)
792
793  if HasVendorPartition(target_zip):
794    if not HasVendorPartition(source_zip):
795      raise RuntimeError("can't generate incremental that adds /vendor")
796    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
797                          OPTIONS.source_info_dict)
798    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
799                          OPTIONS.target_info_dict)
800    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
801                                         version=blockimgdiff_version)
802  else:
803    vendor_diff = None
804
805  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
806  recovery_mount_options = OPTIONS.source_info_dict.get(
807      "recovery_mount_options")
808  oem_dict = None
809  if oem_props is not None and len(oem_props) > 0:
810    if OPTIONS.oem_source is None:
811      raise common.ExternalError("OEM source required for this build")
812    script.Mount("/oem", recovery_mount_options)
813    oem_dict = common.LoadDictionaryFromLines(
814        open(OPTIONS.oem_source).readlines())
815
816  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
817  device_specific.IncrementalOTA_Assertions()
818
819  # Two-step incremental package strategy (in chronological order,
820  # which is *not* the order in which the generated script has
821  # things):
822  #
823  # if stage is not "2/3" or "3/3":
824  #    do verification on current system
825  #    write recovery image to boot partition
826  #    set stage to "2/3"
827  #    reboot to boot partition and restart recovery
828  # else if stage is "2/3":
829  #    write recovery image to recovery partition
830  #    set stage to "3/3"
831  #    reboot to recovery partition and restart recovery
832  # else:
833  #    (stage must be "3/3")
834  #    perform update:
835  #       patch system files, etc.
836  #       force full install of new boot image
837  #       set up system to update recovery partition on first boot
838  #    complete script normally
839  #    (allow recovery to mark itself finished and reboot)
840
841  if OPTIONS.two_step:
842    if not OPTIONS.source_info_dict.get("multistage_support", None):
843      assert False, "two-step packages not supported by this build"
844    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
845    assert fs.fs_type.upper() == "EMMC", \
846        "two-step packages only supported on devices with EMMC /misc partitions"
847    bcb_dev = {"bcb_dev": fs.device}
848    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
849    script.AppendExtra("""
850if get_stage("%(bcb_dev)s") == "2/3" then
851""" % bcb_dev)
852    script.AppendExtra("sleep(20);\n")
853    script.WriteRawImage("/recovery", "recovery.img")
854    script.AppendExtra("""
855set_stage("%(bcb_dev)s", "3/3");
856reboot_now("%(bcb_dev)s", "recovery");
857else if get_stage("%(bcb_dev)s") != "3/3" then
858""" % bcb_dev)
859
860  # Dump fingerprints
861  script.Print("Source: %s" % CalculateFingerprint(
862      oem_props, oem_dict, OPTIONS.source_info_dict))
863  script.Print("Target: %s" % CalculateFingerprint(
864      oem_props, oem_dict, OPTIONS.target_info_dict))
865
866  script.Print("Verifying current system...")
867
868  device_specific.IncrementalOTA_VerifyBegin()
869
870  if oem_props is None:
871    # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
872    # patching on a device that's already on the target build will damage the
873    # system. Because operations like move don't check the block state, they
874    # always apply the changes unconditionally.
875    if blockimgdiff_version <= 2:
876      script.AssertSomeFingerprint(source_fp)
877    else:
878      script.AssertSomeFingerprint(source_fp, target_fp)
879  else:
880    if blockimgdiff_version <= 2:
881      script.AssertSomeThumbprint(
882          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
883    else:
884      script.AssertSomeThumbprint(
885          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
886          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
887
888  if updating_boot:
889    boot_type, boot_device = common.GetTypeAndDevice(
890        "/boot", OPTIONS.source_info_dict)
891    d = common.Difference(target_boot, source_boot)
892    _, _, d = d.ComputePatch()
893    if d is None:
894      include_full_boot = True
895      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
896    else:
897      include_full_boot = False
898
899      print "boot      target: %d  source: %d  diff: %d" % (
900          target_boot.size, source_boot.size, len(d))
901
902      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
903
904      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
905                        (boot_type, boot_device,
906                         source_boot.size, source_boot.sha1,
907                         target_boot.size, target_boot.sha1))
908
909  device_specific.IncrementalOTA_VerifyEnd()
910
911  if OPTIONS.two_step:
912    script.WriteRawImage("/boot", "recovery.img")
913    script.AppendExtra("""
914set_stage("%(bcb_dev)s", "2/3");
915reboot_now("%(bcb_dev)s", "");
916else
917""" % bcb_dev)
918
919  # Verify the existing partitions.
920  system_diff.WriteVerifyScript(script)
921  if vendor_diff:
922    vendor_diff.WriteVerifyScript(script)
923
924  script.Comment("---- start making changes here ----")
925
926  device_specific.IncrementalOTA_InstallBegin()
927
928  system_diff.WriteScript(script, output_zip,
929                          progress=0.8 if vendor_diff else 0.9)
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/install-recovery.sh"])
1365    print "recovery image changed; including as patch from boot."
1366  else:
1367    print "recovery image unchanged; skipping."
1368
1369  script.ShowProgress(0.1, 10)
1370
1371  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1372  if vendor_diff:
1373    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1374
1375  temp_script = script.MakeTemporary()
1376  system_items.GetMetadata(target_zip)
1377  system_items.Get("system").SetPermissions(temp_script)
1378  if vendor_diff:
1379    vendor_items.GetMetadata(target_zip)
1380    vendor_items.Get("vendor").SetPermissions(temp_script)
1381
1382  # Note that this call will mess up the trees of Items, so make sure
1383  # we're done with them.
1384  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1385  if vendor_diff:
1386    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1387
1388  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1389  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1390
1391  # Delete all the symlinks in source that aren't in target.  This
1392  # needs to happen before verbatim files are unpacked, in case a
1393  # symlink in the source is replaced by a real file in the target.
1394
1395  # If a symlink in the source will be replaced by a regular file, we cannot
1396  # delete the symlink/file in case the package gets applied again. For such
1397  # a symlink, we prepend a sha1_check() to detect if it has been updated.
1398  # (Bug: 23646151)
1399  replaced_symlinks = dict()
1400  if system_diff:
1401    for i in system_diff.verbatim_targets:
1402      replaced_symlinks["/%s" % (i[0],)] = i[2]
1403  if vendor_diff:
1404    for i in vendor_diff.verbatim_targets:
1405      replaced_symlinks["/%s" % (i[0],)] = i[2]
1406
1407  if system_diff:
1408    for tf in system_diff.renames.values():
1409      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1410  if vendor_diff:
1411    for tf in vendor_diff.renames.values():
1412      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1413
1414  always_delete = []
1415  may_delete = []
1416  for dest, link in source_symlinks:
1417    if link not in target_symlinks_d:
1418      if link in replaced_symlinks:
1419        may_delete.append((link, replaced_symlinks[link]))
1420      else:
1421        always_delete.append(link)
1422  script.DeleteFiles(always_delete)
1423  script.DeleteFilesIfNotMatching(may_delete)
1424
1425  if system_diff.verbatim_targets:
1426    script.Print("Unpacking new system files...")
1427    script.UnpackPackageDir("system", "/system")
1428  if vendor_diff and vendor_diff.verbatim_targets:
1429    script.Print("Unpacking new vendor files...")
1430    script.UnpackPackageDir("vendor", "/vendor")
1431
1432  if updating_recovery and not target_has_recovery_patch:
1433    script.Print("Unpacking new recovery...")
1434    script.UnpackPackageDir("recovery", "/system")
1435
1436  system_diff.EmitRenames(script)
1437  if vendor_diff:
1438    vendor_diff.EmitRenames(script)
1439
1440  script.Print("Symlinks and permissions...")
1441
1442  # Create all the symlinks that don't already exist, or point to
1443  # somewhere different than what we want.  Delete each symlink before
1444  # creating it, since the 'symlink' command won't overwrite.
1445  to_create = []
1446  for dest, link in target_symlinks:
1447    if link in source_symlinks_d:
1448      if dest != source_symlinks_d[link]:
1449        to_create.append((dest, link))
1450    else:
1451      to_create.append((dest, link))
1452  script.DeleteFiles([i[1] for i in to_create])
1453  script.MakeSymlinks(to_create)
1454
1455  # Now that the symlinks are created, we can set all the
1456  # permissions.
1457  script.AppendScript(temp_script)
1458
1459  # Do device-specific installation (eg, write radio image).
1460  device_specific.IncrementalOTA_InstallEnd()
1461
1462  if OPTIONS.extra_script is not None:
1463    script.AppendExtra(OPTIONS.extra_script)
1464
1465  # Patch the build.prop file last, so if something fails but the
1466  # device can still come up, it appears to be the old build and will
1467  # get set the OTA package again to retry.
1468  script.Print("Patching remaining system files...")
1469  system_diff.EmitDeferredPatches(script)
1470
1471  if OPTIONS.wipe_user_data:
1472    script.Print("Erasing user data...")
1473    script.FormatPartition("/data")
1474
1475  if OPTIONS.two_step:
1476    script.AppendExtra("""
1477set_stage("%(bcb_dev)s", "");
1478endif;
1479endif;
1480""" % bcb_dev)
1481
1482  if OPTIONS.verify and system_diff:
1483    script.Print("Remounting and verifying system partition files...")
1484    script.Unmount("/system")
1485    script.Mount("/system")
1486    system_diff.EmitExplicitTargetVerification(script)
1487
1488  if OPTIONS.verify and vendor_diff:
1489    script.Print("Remounting and verifying vendor partition files...")
1490    script.Unmount("/vendor")
1491    script.Mount("/vendor")
1492    vendor_diff.EmitExplicitTargetVerification(script)
1493  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1494
1495  WriteMetadata(metadata, output_zip)
1496
1497
1498def main(argv):
1499
1500  def option_handler(o, a):
1501    if o == "--board_config":
1502      pass   # deprecated
1503    elif o in ("-k", "--package_key"):
1504      OPTIONS.package_key = a
1505    elif o in ("-i", "--incremental_from"):
1506      OPTIONS.incremental_source = a
1507    elif o == "--full_radio":
1508      OPTIONS.full_radio = True
1509    elif o in ("-w", "--wipe_user_data"):
1510      OPTIONS.wipe_user_data = True
1511    elif o in ("-n", "--no_prereq"):
1512      OPTIONS.omit_prereq = True
1513    elif o in ("-o", "--oem_settings"):
1514      OPTIONS.oem_source = a
1515    elif o in ("-e", "--extra_script"):
1516      OPTIONS.extra_script = a
1517    elif o in ("-a", "--aslr_mode"):
1518      if a in ("on", "On", "true", "True", "yes", "Yes"):
1519        OPTIONS.aslr_mode = True
1520      else:
1521        OPTIONS.aslr_mode = False
1522    elif o in ("-t", "--worker_threads"):
1523      if a.isdigit():
1524        OPTIONS.worker_threads = int(a)
1525      else:
1526        raise ValueError("Cannot parse value %r for option %r - only "
1527                         "integers are allowed." % (a, o))
1528    elif o in ("-2", "--two_step"):
1529      OPTIONS.two_step = True
1530    elif o == "--no_signing":
1531      OPTIONS.no_signing = True
1532    elif o == "--verify":
1533      OPTIONS.verify = True
1534    elif o == "--block":
1535      OPTIONS.block_based = True
1536    elif o in ("-b", "--binary"):
1537      OPTIONS.updater_binary = a
1538    elif o in ("--no_fallback_to_full",):
1539      OPTIONS.fallback_to_full = False
1540    elif o == "--stash_threshold":
1541      try:
1542        OPTIONS.stash_threshold = float(a)
1543      except ValueError:
1544        raise ValueError("Cannot parse value %r for option %r - expecting "
1545                         "a float" % (a, o))
1546    else:
1547      return False
1548    return True
1549
1550  args = common.ParseOptions(argv, __doc__,
1551                             extra_opts="b:k:i:d:wne:t:a:2o:",
1552                             extra_long_opts=[
1553                                 "board_config=",
1554                                 "package_key=",
1555                                 "incremental_from=",
1556                                 "full_radio",
1557                                 "wipe_user_data",
1558                                 "no_prereq",
1559                                 "extra_script=",
1560                                 "worker_threads=",
1561                                 "aslr_mode=",
1562                                 "two_step",
1563                                 "no_signing",
1564                                 "block",
1565                                 "binary=",
1566                                 "oem_settings=",
1567                                 "verify",
1568                                 "no_fallback_to_full",
1569                                 "stash_threshold=",
1570                             ], extra_option_handler=option_handler)
1571
1572  if len(args) != 2:
1573    common.Usage(__doc__)
1574    sys.exit(1)
1575
1576  if OPTIONS.extra_script is not None:
1577    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1578
1579  print "unzipping target target-files..."
1580  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1581
1582  OPTIONS.target_tmp = OPTIONS.input_tmp
1583  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1584
1585  # If this image was originally labelled with SELinux contexts, make sure we
1586  # also apply the labels in our new image. During building, the "file_contexts"
1587  # is in the out/ directory tree, but for repacking from target-files.zip it's
1588  # in the root directory of the ramdisk.
1589  if "selinux_fc" in OPTIONS.info_dict:
1590    OPTIONS.info_dict["selinux_fc"] = os.path.join(
1591        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
1592
1593  if OPTIONS.verbose:
1594    print "--- target info ---"
1595    common.DumpInfoDict(OPTIONS.info_dict)
1596
1597  # If the caller explicitly specified the device-specific extensions
1598  # path via -s/--device_specific, use that.  Otherwise, use
1599  # META/releasetools.py if it is present in the target target_files.
1600  # Otherwise, take the path of the file from 'tool_extensions' in the
1601  # info dict and look for that in the local filesystem, relative to
1602  # the current directory.
1603
1604  if OPTIONS.device_specific is None:
1605    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1606    if os.path.exists(from_input):
1607      print "(using device-specific extensions from target_files)"
1608      OPTIONS.device_specific = from_input
1609    else:
1610      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1611
1612  if OPTIONS.device_specific is not None:
1613    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1614
1615  while True:
1616
1617    if OPTIONS.no_signing:
1618      if os.path.exists(args[1]):
1619        os.unlink(args[1])
1620      output_zip = zipfile.ZipFile(args[1], "w",
1621                                   compression=zipfile.ZIP_DEFLATED)
1622    else:
1623      temp_zip_file = tempfile.NamedTemporaryFile()
1624      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1625                                   compression=zipfile.ZIP_DEFLATED)
1626
1627    cache_size = OPTIONS.info_dict.get("cache_size", None)
1628    if cache_size is None:
1629      raise RuntimeError("can't determine the cache partition size")
1630    OPTIONS.cache_size = cache_size
1631
1632    if OPTIONS.incremental_source is None:
1633      WriteFullOTAPackage(input_zip, output_zip)
1634      if OPTIONS.package_key is None:
1635        OPTIONS.package_key = OPTIONS.info_dict.get(
1636            "default_system_dev_certificate",
1637            "build/target/product/security/testkey")
1638      common.ZipClose(output_zip)
1639      break
1640
1641    else:
1642      print "unzipping source target-files..."
1643      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1644          OPTIONS.incremental_source)
1645      OPTIONS.target_info_dict = OPTIONS.info_dict
1646      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1647      if "selinux_fc" in OPTIONS.source_info_dict:
1648        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
1649            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
1650      if OPTIONS.package_key is None:
1651        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1652            "default_system_dev_certificate",
1653            "build/target/product/security/testkey")
1654      if OPTIONS.verbose:
1655        print "--- source info ---"
1656        common.DumpInfoDict(OPTIONS.source_info_dict)
1657      try:
1658        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1659        common.ZipClose(output_zip)
1660        break
1661      except ValueError:
1662        if not OPTIONS.fallback_to_full:
1663          raise
1664        print "--- failed to build incremental; falling back to full ---"
1665        OPTIONS.incremental_source = None
1666        common.ZipClose(output_zip)
1667
1668  if not OPTIONS.no_signing:
1669    SignOutput(temp_zip_file.name, args[1])
1670    temp_zip_file.close()
1671
1672  print "done."
1673
1674
1675if __name__ == '__main__':
1676  try:
1677    common.CloseInheritedPipes()
1678    main(sys.argv[1:])
1679  except common.ExternalError as e:
1680    print
1681    print "   ERROR: %s" % (e,)
1682    print
1683    sys.exit(1)
1684  finally:
1685    common.Cleanup()
1686