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