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