ota_from_target_files.py revision 8dcf738234d2701ad907fc14cfe0ce8fb760b8b0
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.
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  # Place a copy of file_contexts into the OTA package which will be used by
593  # the recovery program.
594  if "selinux_fc" in OPTIONS.info_dict:
595    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
596
597  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
598
599  system_items = ItemSet("system", "META/filesystem_config.txt")
600  script.ShowProgress(system_progress, 0)
601
602  if block_based:
603    # Full OTA is done as an "incremental" against an empty source
604    # image.  This has the effect of writing new data from the package
605    # to the entire partition, but lets us reuse the updater code that
606    # writes incrementals to do it.
607    system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
608    system_tgt.ResetFileMap()
609    system_diff = common.BlockDifference("system", system_tgt, src=None)
610    system_diff.WriteScript(script, output_zip)
611  else:
612    script.FormatPartition("/system")
613    script.Mount("/system", recovery_mount_options)
614    if not has_recovery_patch:
615      script.UnpackPackageDir("recovery", "/system")
616    script.UnpackPackageDir("system", "/system")
617
618    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
619    script.MakeSymlinks(symlinks)
620
621  boot_img = common.GetBootableImage("boot.img", "boot.img",
622                                     OPTIONS.input_tmp, "BOOT")
623
624  if not block_based:
625    def output_sink(fn, data):
626      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
627      system_items.Get("system/" + fn)
628
629    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
630                             recovery_img, boot_img)
631
632    system_items.GetMetadata(input_zip)
633    system_items.Get("system").SetPermissions(script)
634
635  if HasVendorPartition(input_zip):
636    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
637    script.ShowProgress(0.1, 0)
638
639    if block_based:
640      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
641      vendor_tgt.ResetFileMap()
642      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
643      vendor_diff.WriteScript(script, output_zip)
644    else:
645      script.FormatPartition("/vendor")
646      script.Mount("/vendor", recovery_mount_options)
647      script.UnpackPackageDir("vendor", "/vendor")
648
649      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
650      script.MakeSymlinks(symlinks)
651
652      vendor_items.GetMetadata(input_zip)
653      vendor_items.Get("vendor").SetPermissions(script)
654
655  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
656  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
657
658  script.ShowProgress(0.05, 5)
659  script.WriteRawImage("/boot", "boot.img")
660
661  script.ShowProgress(0.2, 10)
662  device_specific.FullOTA_InstallEnd()
663
664  if OPTIONS.extra_script is not None:
665    script.AppendExtra(OPTIONS.extra_script)
666
667  script.UnmountAll()
668
669  if OPTIONS.wipe_user_data:
670    script.ShowProgress(0.1, 10)
671    script.FormatPartition("/data")
672
673  if OPTIONS.two_step:
674    script.AppendExtra("""
675set_stage("%(bcb_dev)s", "");
676""" % bcb_dev)
677    script.AppendExtra("else\n")
678    script.WriteRawImage("/boot", "recovery.img")
679    script.AppendExtra("""
680set_stage("%(bcb_dev)s", "2/3");
681reboot_now("%(bcb_dev)s", "");
682endif;
683endif;
684""" % bcb_dev)
685  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
686  WriteMetadata(metadata, output_zip)
687
688
689def WritePolicyConfig(file_name, output_zip):
690  common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
691
692
693def WriteMetadata(metadata, output_zip):
694  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
695                     "".join(["%s=%s\n" % kv
696                              for kv in sorted(metadata.iteritems())]))
697
698
699def LoadPartitionFiles(z, partition):
700  """Load all the files from the given partition in a given target-files
701  ZipFile, and return a dict of {filename: File object}."""
702  out = {}
703  prefix = partition.upper() + "/"
704  for info in z.infolist():
705    if info.filename.startswith(prefix) and not IsSymlink(info):
706      basefilename = info.filename[len(prefix):]
707      fn = partition + "/" + basefilename
708      data = z.read(info.filename)
709      out[fn] = common.File(fn, data)
710  return out
711
712
713def GetBuildProp(prop, info_dict):
714  """Return the fingerprint of the build of a given target-files info_dict."""
715  try:
716    return info_dict.get("build.prop", {})[prop]
717  except KeyError:
718    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
719
720
721def AddToKnownPaths(filename, known_paths):
722  if filename[-1] == "/":
723    return
724  dirs = filename.split("/")[:-1]
725  while len(dirs) > 0:
726    path = "/".join(dirs)
727    if path in known_paths:
728      break
729    known_paths.add(path)
730    dirs.pop()
731
732
733def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
734  # TODO(tbao): We should factor out the common parts between
735  # WriteBlockIncrementalOTAPackage() and WriteIncrementalOTAPackage().
736  source_version = OPTIONS.source_info_dict["recovery_api_version"]
737  target_version = OPTIONS.target_info_dict["recovery_api_version"]
738
739  if source_version == 0:
740    print ("WARNING: generating edify script for a source that "
741           "can't install it.")
742  script = edify_generator.EdifyGenerator(
743      source_version, OPTIONS.target_info_dict,
744      fstab=OPTIONS.source_info_dict["fstab"])
745
746  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
747  recovery_mount_options = OPTIONS.source_info_dict.get(
748      "recovery_mount_options")
749  oem_dict = None
750  if oem_props is not None and len(oem_props) > 0:
751    if OPTIONS.oem_source is None:
752      raise common.ExternalError("OEM source required for this build")
753    script.Mount("/oem", recovery_mount_options)
754    oem_dict = common.LoadDictionaryFromLines(
755        open(OPTIONS.oem_source).readlines())
756
757  metadata = {
758      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
759                                   OPTIONS.source_info_dict),
760      "post-timestamp": GetBuildProp("ro.build.date.utc",
761                                     OPTIONS.target_info_dict),
762  }
763
764  device_specific = common.DeviceSpecificParams(
765      source_zip=source_zip,
766      source_version=source_version,
767      target_zip=target_zip,
768      target_version=target_version,
769      output_zip=output_zip,
770      script=script,
771      metadata=metadata,
772      info_dict=OPTIONS.info_dict)
773
774  source_fp = CalculateFingerprint(oem_props, oem_dict,
775                                   OPTIONS.source_info_dict)
776  target_fp = CalculateFingerprint(oem_props, oem_dict,
777                                   OPTIONS.target_info_dict)
778  metadata["pre-build"] = source_fp
779  metadata["post-build"] = target_fp
780
781  source_boot = common.GetBootableImage(
782      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
783      OPTIONS.source_info_dict)
784  target_boot = common.GetBootableImage(
785      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
786  updating_boot = (not OPTIONS.two_step and
787                   (source_boot.data != target_boot.data))
788
789  target_recovery = common.GetBootableImage(
790      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
791
792  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
793  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
794
795  blockimgdiff_version = 1
796  if OPTIONS.info_dict:
797    blockimgdiff_version = max(
798        int(i) for i in
799        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
800
801  system_diff = common.BlockDifference("system", system_tgt, system_src,
802                                       version=blockimgdiff_version)
803
804  if HasVendorPartition(target_zip):
805    if not HasVendorPartition(source_zip):
806      raise RuntimeError("can't generate incremental that adds /vendor")
807    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
808                          OPTIONS.source_info_dict)
809    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
810                          OPTIONS.target_info_dict)
811    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
812                                         version=blockimgdiff_version)
813  else:
814    vendor_diff = None
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.info_dict.get("multistage_support", None):
843      assert False, "two-step packages not supported by this build"
844    fs = OPTIONS.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("/boot", OPTIONS.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.info_dict.get("multistage_support", None):
1230      assert False, "two-step packages not supported by this build"
1231    fs = OPTIONS.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("/boot", OPTIONS.info_dict)
1269
1270    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1271                      (boot_type, boot_device,
1272                       source_boot.size, source_boot.sha1,
1273                       target_boot.size, target_boot.sha1))
1274    so_far += source_boot.size
1275
1276  size = []
1277  if system_diff.patch_list:
1278    size.append(system_diff.largest_source_size)
1279  if vendor_diff:
1280    if vendor_diff.patch_list:
1281      size.append(vendor_diff.largest_source_size)
1282  if size or updating_recovery or updating_boot:
1283    script.CacheFreeSpaceCheck(max(size))
1284
1285  device_specific.IncrementalOTA_VerifyEnd()
1286
1287  if OPTIONS.two_step:
1288    script.WriteRawImage("/boot", "recovery.img")
1289    script.AppendExtra("""
1290set_stage("%(bcb_dev)s", "2/3");
1291reboot_now("%(bcb_dev)s", "");
1292else
1293""" % bcb_dev)
1294
1295  script.Comment("---- start making changes here ----")
1296
1297  device_specific.IncrementalOTA_InstallBegin()
1298
1299  if OPTIONS.two_step:
1300    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1301    script.WriteRawImage("/boot", "boot.img")
1302    print "writing full boot image (forced by two-step mode)"
1303
1304  script.Print("Removing unneeded files...")
1305  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1306  if vendor_diff:
1307    vendor_diff.RemoveUnneededFiles(script)
1308
1309  script.ShowProgress(0.8, 0)
1310  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1311  if vendor_diff:
1312    total_patch_size += vendor_diff.TotalPatchSize()
1313  if updating_boot:
1314    total_patch_size += target_boot.size
1315
1316  script.Print("Patching system files...")
1317  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1318  if vendor_diff:
1319    script.Print("Patching vendor files...")
1320    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1321
1322  if not OPTIONS.two_step:
1323    if updating_boot:
1324      # Produce the boot image by applying a patch to the current
1325      # contents of the boot partition, and write it back to the
1326      # partition.
1327      script.Print("Patching boot image...")
1328      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1329                        % (boot_type, boot_device,
1330                           source_boot.size, source_boot.sha1,
1331                           target_boot.size, target_boot.sha1),
1332                        "-",
1333                        target_boot.size, target_boot.sha1,
1334                        source_boot.sha1, "patch/boot.img.p")
1335      so_far += target_boot.size
1336      script.SetProgress(so_far / total_patch_size)
1337      print "boot image changed; including."
1338    else:
1339      print "boot image unchanged; skipping."
1340
1341  system_items = ItemSet("system", "META/filesystem_config.txt")
1342  if vendor_diff:
1343    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1344
1345  if updating_recovery:
1346    # Recovery is generated as a patch using both the boot image
1347    # (which contains the same linux kernel as recovery) and the file
1348    # /system/etc/recovery-resource.dat (which contains all the images
1349    # used in the recovery UI) as sources.  This lets us minimize the
1350    # size of the patch, which must be included in every OTA package.
1351    #
1352    # For older builds where recovery-resource.dat is not present, we
1353    # use only the boot image as the source.
1354
1355    if not target_has_recovery_patch:
1356      def output_sink(fn, data):
1357        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1358        system_items.Get("system/" + fn)
1359
1360      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1361                               target_recovery, target_boot)
1362      script.DeleteFiles(["/system/recovery-from-boot.p",
1363                          "/system/etc/install-recovery.sh"])
1364    print "recovery image changed; including as patch from boot."
1365  else:
1366    print "recovery image unchanged; skipping."
1367
1368  script.ShowProgress(0.1, 10)
1369
1370  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1371  if vendor_diff:
1372    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1373
1374  temp_script = script.MakeTemporary()
1375  system_items.GetMetadata(target_zip)
1376  system_items.Get("system").SetPermissions(temp_script)
1377  if vendor_diff:
1378    vendor_items.GetMetadata(target_zip)
1379    vendor_items.Get("vendor").SetPermissions(temp_script)
1380
1381  # Note that this call will mess up the trees of Items, so make sure
1382  # we're done with them.
1383  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1384  if vendor_diff:
1385    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1386
1387  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1388  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1389
1390  # Delete all the symlinks in source that aren't in target.  This
1391  # needs to happen before verbatim files are unpacked, in case a
1392  # symlink in the source is replaced by a real file in the target.
1393  to_delete = []
1394  for dest, link in source_symlinks:
1395    if link not in target_symlinks_d:
1396      to_delete.append(link)
1397  script.DeleteFiles(to_delete)
1398
1399  if system_diff.verbatim_targets:
1400    script.Print("Unpacking new system files...")
1401    script.UnpackPackageDir("system", "/system")
1402  if vendor_diff and vendor_diff.verbatim_targets:
1403    script.Print("Unpacking new vendor files...")
1404    script.UnpackPackageDir("vendor", "/vendor")
1405
1406  if updating_recovery and not target_has_recovery_patch:
1407    script.Print("Unpacking new recovery...")
1408    script.UnpackPackageDir("recovery", "/system")
1409
1410  system_diff.EmitRenames(script)
1411  if vendor_diff:
1412    vendor_diff.EmitRenames(script)
1413
1414  script.Print("Symlinks and permissions...")
1415
1416  # Create all the symlinks that don't already exist, or point to
1417  # somewhere different than what we want.  Delete each symlink before
1418  # creating it, since the 'symlink' command won't overwrite.
1419  to_create = []
1420  for dest, link in target_symlinks:
1421    if link in source_symlinks_d:
1422      if dest != source_symlinks_d[link]:
1423        to_create.append((dest, link))
1424    else:
1425      to_create.append((dest, link))
1426  script.DeleteFiles([i[1] for i in to_create])
1427  script.MakeSymlinks(to_create)
1428
1429  # Now that the symlinks are created, we can set all the
1430  # permissions.
1431  script.AppendScript(temp_script)
1432
1433  # Do device-specific installation (eg, write radio image).
1434  device_specific.IncrementalOTA_InstallEnd()
1435
1436  if OPTIONS.extra_script is not None:
1437    script.AppendExtra(OPTIONS.extra_script)
1438
1439  # Patch the build.prop file last, so if something fails but the
1440  # device can still come up, it appears to be the old build and will
1441  # get set the OTA package again to retry.
1442  script.Print("Patching remaining system files...")
1443  system_diff.EmitDeferredPatches(script)
1444
1445  if OPTIONS.wipe_user_data:
1446    script.Print("Erasing user data...")
1447    script.FormatPartition("/data")
1448
1449  if OPTIONS.two_step:
1450    script.AppendExtra("""
1451set_stage("%(bcb_dev)s", "");
1452endif;
1453endif;
1454""" % bcb_dev)
1455
1456  if OPTIONS.verify and system_diff:
1457    script.Print("Remounting and verifying system partition files...")
1458    script.Unmount("/system")
1459    script.Mount("/system")
1460    system_diff.EmitExplicitTargetVerification(script)
1461
1462  if OPTIONS.verify and vendor_diff:
1463    script.Print("Remounting and verifying vendor partition files...")
1464    script.Unmount("/vendor")
1465    script.Mount("/vendor")
1466    vendor_diff.EmitExplicitTargetVerification(script)
1467  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1468
1469  WriteMetadata(metadata, output_zip)
1470
1471
1472def main(argv):
1473
1474  def option_handler(o, a):
1475    if o == "--board_config":
1476      pass   # deprecated
1477    elif o in ("-k", "--package_key"):
1478      OPTIONS.package_key = a
1479    elif o in ("-i", "--incremental_from"):
1480      OPTIONS.incremental_source = a
1481    elif o == "--full_radio":
1482      OPTIONS.full_radio = True
1483    elif o in ("-w", "--wipe_user_data"):
1484      OPTIONS.wipe_user_data = True
1485    elif o in ("-n", "--no_prereq"):
1486      OPTIONS.omit_prereq = True
1487    elif o in ("-o", "--oem_settings"):
1488      OPTIONS.oem_source = a
1489    elif o in ("-e", "--extra_script"):
1490      OPTIONS.extra_script = a
1491    elif o in ("-a", "--aslr_mode"):
1492      if a in ("on", "On", "true", "True", "yes", "Yes"):
1493        OPTIONS.aslr_mode = True
1494      else:
1495        OPTIONS.aslr_mode = False
1496    elif o in ("-t", "--worker_threads"):
1497      if a.isdigit():
1498        OPTIONS.worker_threads = int(a)
1499      else:
1500        raise ValueError("Cannot parse value %r for option %r - only "
1501                         "integers are allowed." % (a, o))
1502    elif o in ("-2", "--two_step"):
1503      OPTIONS.two_step = True
1504    elif o == "--no_signing":
1505      OPTIONS.no_signing = True
1506    elif o == "--verify":
1507      OPTIONS.verify = True
1508    elif o == "--block":
1509      OPTIONS.block_based = True
1510    elif o in ("-b", "--binary"):
1511      OPTIONS.updater_binary = a
1512    elif o in ("--no_fallback_to_full",):
1513      OPTIONS.fallback_to_full = False
1514    elif o == "--stash_threshold":
1515      try:
1516        OPTIONS.stash_threshold = float(a)
1517      except ValueError:
1518        raise ValueError("Cannot parse value %r for option %r - expecting "
1519                         "a float" % (a, o))
1520    else:
1521      return False
1522    return True
1523
1524  args = common.ParseOptions(argv, __doc__,
1525                             extra_opts="b:k:i:d:wne:t:a:2o:",
1526                             extra_long_opts=[
1527                                 "board_config=",
1528                                 "package_key=",
1529                                 "incremental_from=",
1530                                 "full_radio",
1531                                 "wipe_user_data",
1532                                 "no_prereq",
1533                                 "extra_script=",
1534                                 "worker_threads=",
1535                                 "aslr_mode=",
1536                                 "two_step",
1537                                 "no_signing",
1538                                 "block",
1539                                 "binary=",
1540                                 "oem_settings=",
1541                                 "verify",
1542                                 "no_fallback_to_full",
1543                                 "stash_threshold=",
1544                             ], extra_option_handler=option_handler)
1545
1546  if len(args) != 2:
1547    common.Usage(__doc__)
1548    sys.exit(1)
1549
1550  if OPTIONS.extra_script is not None:
1551    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1552
1553  print "unzipping target target-files..."
1554  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1555
1556  OPTIONS.target_tmp = OPTIONS.input_tmp
1557  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
1558
1559  if OPTIONS.verbose:
1560    print "--- target info ---"
1561    common.DumpInfoDict(OPTIONS.info_dict)
1562
1563  # If the caller explicitly specified the device-specific extensions
1564  # path via -s/--device_specific, use that.  Otherwise, use
1565  # META/releasetools.py if it is present in the target target_files.
1566  # Otherwise, take the path of the file from 'tool_extensions' in the
1567  # info dict and look for that in the local filesystem, relative to
1568  # the current directory.
1569
1570  if OPTIONS.device_specific is None:
1571    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1572    if os.path.exists(from_input):
1573      print "(using device-specific extensions from target_files)"
1574      OPTIONS.device_specific = from_input
1575    else:
1576      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1577
1578  if OPTIONS.device_specific is not None:
1579    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1580
1581  while True:
1582
1583    if OPTIONS.no_signing:
1584      if os.path.exists(args[1]):
1585        os.unlink(args[1])
1586      output_zip = zipfile.ZipFile(args[1], "w",
1587                                   compression=zipfile.ZIP_DEFLATED)
1588    else:
1589      temp_zip_file = tempfile.NamedTemporaryFile()
1590      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1591                                   compression=zipfile.ZIP_DEFLATED)
1592
1593    cache_size = OPTIONS.info_dict.get("cache_size", None)
1594    if cache_size is None:
1595      raise RuntimeError("can't determine the cache partition size")
1596    OPTIONS.cache_size = cache_size
1597
1598    if OPTIONS.incremental_source is None:
1599      WriteFullOTAPackage(input_zip, output_zip)
1600      if OPTIONS.package_key is None:
1601        OPTIONS.package_key = OPTIONS.info_dict.get(
1602            "default_system_dev_certificate",
1603            "build/target/product/security/testkey")
1604      common.ZipClose(output_zip)
1605      break
1606
1607    else:
1608      print "unzipping source target-files..."
1609      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1610          OPTIONS.incremental_source)
1611      OPTIONS.target_info_dict = OPTIONS.info_dict
1612      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
1613                                                     OPTIONS.source_tmp)
1614      if OPTIONS.package_key is None:
1615        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1616            "default_system_dev_certificate",
1617            "build/target/product/security/testkey")
1618      if OPTIONS.verbose:
1619        print "--- source info ---"
1620        common.DumpInfoDict(OPTIONS.source_info_dict)
1621      try:
1622        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1623        common.ZipClose(output_zip)
1624        break
1625      except ValueError:
1626        if not OPTIONS.fallback_to_full:
1627          raise
1628        print "--- failed to build incremental; falling back to full ---"
1629        OPTIONS.incremental_source = None
1630        common.ZipClose(output_zip)
1631
1632  if not OPTIONS.no_signing:
1633    SignOutput(temp_zip_file.name, args[1])
1634    temp_zip_file.close()
1635
1636  print "done."
1637
1638
1639if __name__ == '__main__':
1640  try:
1641    common.CloseInheritedPipes()
1642    main(sys.argv[1:])
1643  except common.ExternalError as e:
1644    print
1645    print "   ERROR: %s" % (e,)
1646    print
1647    sys.exit(1)
1648  finally:
1649    common.Cleanup()
1650