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