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