ota_from_target_files.py revision bebd3cfbf934beb18b73a4d4e98b98c2c0a1d6fe
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) == 0o120777
140
141def IsRegular(info):
142  """Return true if the zipfile.ZipInfo object passed in represents a
143  symlink."""
144  return (info.external_attr >> 28) == 0o10
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  source_version = OPTIONS.source_info_dict["recovery_api_version"]
726  target_version = OPTIONS.target_info_dict["recovery_api_version"]
727
728  if source_version == 0:
729    print ("WARNING: generating edify script for a source that "
730           "can't install it.")
731  script = edify_generator.EdifyGenerator(
732      source_version, OPTIONS.target_info_dict,
733      fstab=OPTIONS.source_info_dict["fstab"])
734
735  metadata = {
736      "pre-device": GetBuildProp("ro.product.device",
737                                 OPTIONS.source_info_dict),
738      "post-timestamp": GetBuildProp("ro.build.date.utc",
739                                     OPTIONS.target_info_dict),
740  }
741
742  device_specific = common.DeviceSpecificParams(
743      source_zip=source_zip,
744      source_version=source_version,
745      target_zip=target_zip,
746      target_version=target_version,
747      output_zip=output_zip,
748      script=script,
749      metadata=metadata,
750      info_dict=OPTIONS.info_dict)
751
752  # TODO: Currently this works differently from WriteIncrementalOTAPackage().
753  # This function doesn't consider thumbprints when writing
754  # metadata["pre/post-build"]. One possible reason is that the current
755  # devices with thumbprints are all using file-based OTAs. Long term we
756  # should factor out the common parts into a shared one to avoid further
757  # divergence.
758  source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
759  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
760  metadata["pre-build"] = source_fp
761  metadata["post-build"] = target_fp
762
763  source_boot = common.GetBootableImage(
764      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
765      OPTIONS.source_info_dict)
766  target_boot = common.GetBootableImage(
767      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
768  updating_boot = (not OPTIONS.two_step and
769                   (source_boot.data != target_boot.data))
770
771  target_recovery = common.GetBootableImage(
772      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
773
774  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
775  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
776
777  blockimgdiff_version = 1
778  if OPTIONS.info_dict:
779    blockimgdiff_version = max(
780        int(i) for i in
781        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
782
783  system_diff = common.BlockDifference("system", system_tgt, system_src,
784                                       version=blockimgdiff_version)
785
786  if HasVendorPartition(target_zip):
787    if not HasVendorPartition(source_zip):
788      raise RuntimeError("can't generate incremental that adds /vendor")
789    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
790                          OPTIONS.source_info_dict)
791    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
792                          OPTIONS.target_info_dict)
793    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
794                                         version=blockimgdiff_version)
795  else:
796    vendor_diff = None
797
798  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
799  recovery_mount_options = OPTIONS.source_info_dict.get(
800      "recovery_mount_options")
801  oem_dict = None
802  if oem_props is not None and len(oem_props) > 0:
803    if OPTIONS.oem_source is None:
804      raise common.ExternalError("OEM source required for this build")
805    script.Mount("/oem", recovery_mount_options)
806    oem_dict = common.LoadDictionaryFromLines(
807        open(OPTIONS.oem_source).readlines())
808
809  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
810  device_specific.IncrementalOTA_Assertions()
811
812  # Two-step incremental package strategy (in chronological order,
813  # which is *not* the order in which the generated script has
814  # things):
815  #
816  # if stage is not "2/3" or "3/3":
817  #    do verification on current system
818  #    write recovery image to boot partition
819  #    set stage to "2/3"
820  #    reboot to boot partition and restart recovery
821  # else if stage is "2/3":
822  #    write recovery image to recovery partition
823  #    set stage to "3/3"
824  #    reboot to recovery partition and restart recovery
825  # else:
826  #    (stage must be "3/3")
827  #    perform update:
828  #       patch system files, etc.
829  #       force full install of new boot image
830  #       set up system to update recovery partition on first boot
831  #    complete script normally
832  #    (allow recovery to mark itself finished and reboot)
833
834  if OPTIONS.two_step:
835    if not OPTIONS.info_dict.get("multistage_support", None):
836      assert False, "two-step packages not supported by this build"
837    fs = OPTIONS.info_dict["fstab"]["/misc"]
838    assert fs.fs_type.upper() == "EMMC", \
839        "two-step packages only supported on devices with EMMC /misc partitions"
840    bcb_dev = {"bcb_dev": fs.device}
841    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
842    script.AppendExtra("""
843if get_stage("%(bcb_dev)s") == "2/3" then
844""" % bcb_dev)
845    script.AppendExtra("sleep(20);\n")
846    script.WriteRawImage("/recovery", "recovery.img")
847    script.AppendExtra("""
848set_stage("%(bcb_dev)s", "3/3");
849reboot_now("%(bcb_dev)s", "recovery");
850else if get_stage("%(bcb_dev)s") != "3/3" then
851""" % bcb_dev)
852
853  # Dump fingerprints
854  script.Print("Source: %s" % CalculateFingerprint(
855      oem_props, oem_dict, OPTIONS.source_info_dict))
856  script.Print("Target: %s" % CalculateFingerprint(
857      oem_props, oem_dict, OPTIONS.target_info_dict))
858
859  script.Print("Verifying current system...")
860
861  device_specific.IncrementalOTA_VerifyBegin()
862
863  if oem_props is None:
864    # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
865    # patching on a device that's already on the target build will damage the
866    # system. Because operations like move don't check the block state, they
867    # always apply the changes unconditionally.
868    if blockimgdiff_version <= 2:
869      script.AssertSomeFingerprint(source_fp)
870    else:
871      script.AssertSomeFingerprint(source_fp, target_fp)
872  else:
873    if blockimgdiff_version <= 2:
874      script.AssertSomeThumbprint(
875          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
876    else:
877      script.AssertSomeThumbprint(
878          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
879          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
880
881  if updating_boot:
882    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
883    d = common.Difference(target_boot, source_boot)
884    _, _, d = d.ComputePatch()
885    if d is None:
886      include_full_boot = True
887      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
888    else:
889      include_full_boot = False
890
891      print "boot      target: %d  source: %d  diff: %d" % (
892          target_boot.size, source_boot.size, len(d))
893
894      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
895
896      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
897                        (boot_type, boot_device,
898                         source_boot.size, source_boot.sha1,
899                         target_boot.size, target_boot.sha1))
900
901  device_specific.IncrementalOTA_VerifyEnd()
902
903  if OPTIONS.two_step:
904    script.WriteRawImage("/boot", "recovery.img")
905    script.AppendExtra("""
906set_stage("%(bcb_dev)s", "2/3");
907reboot_now("%(bcb_dev)s", "");
908else
909""" % bcb_dev)
910
911  # Verify the existing partitions.
912  system_diff.WriteVerifyScript(script)
913  if vendor_diff:
914    vendor_diff.WriteVerifyScript(script)
915
916  script.Comment("---- start making changes here ----")
917
918  device_specific.IncrementalOTA_InstallBegin()
919
920  system_diff.WriteScript(script, output_zip,
921                          progress=0.8 if vendor_diff else 0.9)
922  if vendor_diff:
923    vendor_diff.WriteScript(script, output_zip, progress=0.1)
924
925  if OPTIONS.two_step:
926    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
927    script.WriteRawImage("/boot", "boot.img")
928    print "writing full boot image (forced by two-step mode)"
929
930  if not OPTIONS.two_step:
931    if updating_boot:
932      if include_full_boot:
933        print "boot image changed; including full."
934        script.Print("Installing boot image...")
935        script.WriteRawImage("/boot", "boot.img")
936      else:
937        # Produce the boot image by applying a patch to the current
938        # contents of the boot partition, and write it back to the
939        # partition.
940        print "boot image changed; including patch."
941        script.Print("Patching boot image...")
942        script.ShowProgress(0.1, 10)
943        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
944                          % (boot_type, boot_device,
945                             source_boot.size, source_boot.sha1,
946                             target_boot.size, target_boot.sha1),
947                          "-",
948                          target_boot.size, target_boot.sha1,
949                          source_boot.sha1, "patch/boot.img.p")
950    else:
951      print "boot image unchanged; skipping."
952
953  # Do device-specific installation (eg, write radio image).
954  device_specific.IncrementalOTA_InstallEnd()
955
956  if OPTIONS.extra_script is not None:
957    script.AppendExtra(OPTIONS.extra_script)
958
959  if OPTIONS.wipe_user_data:
960    script.Print("Erasing user data...")
961    script.FormatPartition("/data")
962
963  if OPTIONS.two_step:
964    script.AppendExtra("""
965set_stage("%(bcb_dev)s", "");
966endif;
967endif;
968""" % bcb_dev)
969
970  script.SetProgress(1)
971  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
972  WriteMetadata(metadata, output_zip)
973
974
975class FileDifference(object):
976  def __init__(self, partition, source_zip, target_zip, output_zip):
977    self.deferred_patch_list = None
978    print "Loading target..."
979    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
980    print "Loading source..."
981    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
982
983    self.verbatim_targets = verbatim_targets = []
984    self.patch_list = patch_list = []
985    diffs = []
986    self.renames = renames = {}
987    known_paths = set()
988    largest_source_size = 0
989
990    matching_file_cache = {}
991    for fn, sf in source_data.items():
992      assert fn == sf.name
993      matching_file_cache["path:" + fn] = sf
994      if fn in target_data.keys():
995        AddToKnownPaths(fn, known_paths)
996      # Only allow eligibility for filename/sha matching
997      # if there isn't a perfect path match.
998      if target_data.get(sf.name) is None:
999        matching_file_cache["file:" + fn.split("/")[-1]] = sf
1000        matching_file_cache["sha:" + sf.sha1] = sf
1001
1002    for fn in sorted(target_data.keys()):
1003      tf = target_data[fn]
1004      assert fn == tf.name
1005      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1006      if sf is not None and sf.name != tf.name:
1007        print "File has moved from " + sf.name + " to " + tf.name
1008        renames[sf.name] = tf
1009
1010      if sf is None or fn in OPTIONS.require_verbatim:
1011        # This file should be included verbatim
1012        if fn in OPTIONS.prohibit_verbatim:
1013          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1014        print "send", fn, "verbatim"
1015        tf.AddToZip(output_zip)
1016        verbatim_targets.append((fn, tf.size, tf.sha1))
1017        if fn in target_data.keys():
1018          AddToKnownPaths(fn, known_paths)
1019      elif tf.sha1 != sf.sha1:
1020        # File is different; consider sending as a patch
1021        diffs.append(common.Difference(tf, sf))
1022      else:
1023        # Target file data identical to source (may still be renamed)
1024        pass
1025
1026    common.ComputeDifferences(diffs)
1027
1028    for diff in diffs:
1029      tf, sf, d = diff.GetPatch()
1030      path = "/".join(tf.name.split("/")[:-1])
1031      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1032          path not in known_paths:
1033        # patch is almost as big as the file; don't bother patching
1034        # or a patch + rename cannot take place due to the target
1035        # directory not existing
1036        tf.AddToZip(output_zip)
1037        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1038        if sf.name in renames:
1039          del renames[sf.name]
1040        AddToKnownPaths(tf.name, known_paths)
1041      else:
1042        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1043        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1044        largest_source_size = max(largest_source_size, sf.size)
1045
1046    self.largest_source_size = largest_source_size
1047
1048  def EmitVerification(self, script):
1049    so_far = 0
1050    for tf, sf, _, _ in self.patch_list:
1051      if tf.name != sf.name:
1052        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1053      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1054      so_far += sf.size
1055    return so_far
1056
1057  def EmitExplicitTargetVerification(self, script):
1058    for fn, _, sha1 in self.verbatim_targets:
1059      if fn[-1] != "/":
1060        script.FileCheck("/"+fn, sha1)
1061    for tf, _, _, _ in self.patch_list:
1062      script.FileCheck(tf.name, tf.sha1)
1063
1064  def RemoveUnneededFiles(self, script, extras=()):
1065    script.DeleteFiles(
1066        ["/" + i[0] for i in self.verbatim_targets] +
1067        ["/" + i for i in sorted(self.source_data)
1068         if i not in self.target_data and i not in self.renames] +
1069        list(extras))
1070
1071  def TotalPatchSize(self):
1072    return sum(i[1].size for i in self.patch_list)
1073
1074  def EmitPatches(self, script, total_patch_size, so_far):
1075    self.deferred_patch_list = deferred_patch_list = []
1076    for item in self.patch_list:
1077      tf, sf, _, _ = item
1078      if tf.name == "system/build.prop":
1079        deferred_patch_list.append(item)
1080        continue
1081      if sf.name != tf.name:
1082        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1083      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1084                        "patch/" + sf.name + ".p")
1085      so_far += tf.size
1086      script.SetProgress(so_far / total_patch_size)
1087    return so_far
1088
1089  def EmitDeferredPatches(self, script):
1090    for item in self.deferred_patch_list:
1091      tf, sf, _, _ = item
1092      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1093                        "patch/" + sf.name + ".p")
1094    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1095
1096  def EmitRenames(self, script):
1097    if len(self.renames) > 0:
1098      script.Print("Renaming files...")
1099      for src, tgt in self.renames.iteritems():
1100        print "Renaming " + src + " to " + tgt.name
1101        script.RenameFile(src, tgt.name)
1102
1103
1104def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1105  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1106  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1107
1108  if (OPTIONS.block_based and
1109      target_has_recovery_patch and
1110      source_has_recovery_patch):
1111    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1112
1113  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1114  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1115
1116  if source_version == 0:
1117    print ("WARNING: generating edify script for a source that "
1118           "can't install it.")
1119  script = edify_generator.EdifyGenerator(
1120      source_version, OPTIONS.target_info_dict,
1121      fstab=OPTIONS.source_info_dict["fstab"])
1122
1123  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1124  recovery_mount_options = OPTIONS.source_info_dict.get(
1125      "recovery_mount_options")
1126  oem_dict = None
1127  if oem_props is not None and len(oem_props) > 0:
1128    if OPTIONS.oem_source is None:
1129      raise common.ExternalError("OEM source required for this build")
1130    script.Mount("/oem", recovery_mount_options)
1131    oem_dict = common.LoadDictionaryFromLines(
1132        open(OPTIONS.oem_source).readlines())
1133
1134  metadata = {
1135      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1136                                   OPTIONS.source_info_dict),
1137      "post-timestamp": GetBuildProp("ro.build.date.utc",
1138                                     OPTIONS.target_info_dict),
1139  }
1140
1141  device_specific = common.DeviceSpecificParams(
1142      source_zip=source_zip,
1143      source_version=source_version,
1144      target_zip=target_zip,
1145      target_version=target_version,
1146      output_zip=output_zip,
1147      script=script,
1148      metadata=metadata,
1149      info_dict=OPTIONS.info_dict)
1150
1151  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1152  script.Mount("/system", recovery_mount_options)
1153  if HasVendorPartition(target_zip):
1154    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1155    script.Mount("/vendor", recovery_mount_options)
1156  else:
1157    vendor_diff = None
1158
1159  target_fp = CalculateFingerprint(oem_props, oem_dict,
1160                                   OPTIONS.target_info_dict)
1161  source_fp = CalculateFingerprint(oem_props, oem_dict,
1162                                   OPTIONS.source_info_dict)
1163
1164  if oem_props is None:
1165    script.AssertSomeFingerprint(source_fp, target_fp)
1166  else:
1167    script.AssertSomeThumbprint(
1168        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1169        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1170
1171  metadata["pre-build"] = source_fp
1172  metadata["post-build"] = target_fp
1173
1174  source_boot = common.GetBootableImage(
1175      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1176      OPTIONS.source_info_dict)
1177  target_boot = common.GetBootableImage(
1178      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1179  updating_boot = (not OPTIONS.two_step and
1180                   (source_boot.data != target_boot.data))
1181
1182  source_recovery = common.GetBootableImage(
1183      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1184      OPTIONS.source_info_dict)
1185  target_recovery = common.GetBootableImage(
1186      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1187  updating_recovery = (source_recovery.data != target_recovery.data)
1188
1189  # Here's how we divide up the progress bar:
1190  #  0.1 for verifying the start state (PatchCheck calls)
1191  #  0.8 for applying patches (ApplyPatch calls)
1192  #  0.1 for unpacking verbatim files, symlinking, and doing the
1193  #      device-specific commands.
1194
1195  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1196  device_specific.IncrementalOTA_Assertions()
1197
1198  # Two-step incremental package strategy (in chronological order,
1199  # which is *not* the order in which the generated script has
1200  # things):
1201  #
1202  # if stage is not "2/3" or "3/3":
1203  #    do verification on current system
1204  #    write recovery image to boot partition
1205  #    set stage to "2/3"
1206  #    reboot to boot partition and restart recovery
1207  # else if stage is "2/3":
1208  #    write recovery image to recovery partition
1209  #    set stage to "3/3"
1210  #    reboot to recovery partition and restart recovery
1211  # else:
1212  #    (stage must be "3/3")
1213  #    perform update:
1214  #       patch system files, etc.
1215  #       force full install of new boot image
1216  #       set up system to update recovery partition on first boot
1217  #    complete script normally
1218  #    (allow recovery to mark itself finished and reboot)
1219
1220  if OPTIONS.two_step:
1221    if not OPTIONS.info_dict.get("multistage_support", None):
1222      assert False, "two-step packages not supported by this build"
1223    fs = OPTIONS.info_dict["fstab"]["/misc"]
1224    assert fs.fs_type.upper() == "EMMC", \
1225        "two-step packages only supported on devices with EMMC /misc partitions"
1226    bcb_dev = {"bcb_dev": fs.device}
1227    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1228    script.AppendExtra("""
1229if get_stage("%(bcb_dev)s") == "2/3" then
1230""" % bcb_dev)
1231    script.AppendExtra("sleep(20);\n")
1232    script.WriteRawImage("/recovery", "recovery.img")
1233    script.AppendExtra("""
1234set_stage("%(bcb_dev)s", "3/3");
1235reboot_now("%(bcb_dev)s", "recovery");
1236else if get_stage("%(bcb_dev)s") != "3/3" then
1237""" % bcb_dev)
1238
1239  # Dump fingerprints
1240  script.Print("Source: %s" % (source_fp,))
1241  script.Print("Target: %s" % (target_fp,))
1242
1243  script.Print("Verifying current system...")
1244
1245  device_specific.IncrementalOTA_VerifyBegin()
1246
1247  script.ShowProgress(0.1, 0)
1248  so_far = system_diff.EmitVerification(script)
1249  if vendor_diff:
1250    so_far += vendor_diff.EmitVerification(script)
1251
1252  if updating_boot:
1253    d = common.Difference(target_boot, source_boot)
1254    _, _, d = d.ComputePatch()
1255    print "boot      target: %d  source: %d  diff: %d" % (
1256        target_boot.size, source_boot.size, len(d))
1257
1258    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1259
1260    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
1261
1262    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1263                      (boot_type, boot_device,
1264                       source_boot.size, source_boot.sha1,
1265                       target_boot.size, target_boot.sha1))
1266    so_far += source_boot.size
1267
1268  size = []
1269  if system_diff.patch_list:
1270    size.append(system_diff.largest_source_size)
1271  if vendor_diff:
1272    if vendor_diff.patch_list:
1273      size.append(vendor_diff.largest_source_size)
1274  if size or updating_recovery or updating_boot:
1275    script.CacheFreeSpaceCheck(max(size))
1276
1277  device_specific.IncrementalOTA_VerifyEnd()
1278
1279  if OPTIONS.two_step:
1280    script.WriteRawImage("/boot", "recovery.img")
1281    script.AppendExtra("""
1282set_stage("%(bcb_dev)s", "2/3");
1283reboot_now("%(bcb_dev)s", "");
1284else
1285""" % bcb_dev)
1286
1287  script.Comment("---- start making changes here ----")
1288
1289  device_specific.IncrementalOTA_InstallBegin()
1290
1291  if OPTIONS.two_step:
1292    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1293    script.WriteRawImage("/boot", "boot.img")
1294    print "writing full boot image (forced by two-step mode)"
1295
1296  script.Print("Removing unneeded files...")
1297  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1298  if vendor_diff:
1299    vendor_diff.RemoveUnneededFiles(script)
1300
1301  script.ShowProgress(0.8, 0)
1302  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1303  if vendor_diff:
1304    total_patch_size += vendor_diff.TotalPatchSize()
1305  if updating_boot:
1306    total_patch_size += target_boot.size
1307
1308  script.Print("Patching system files...")
1309  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1310  if vendor_diff:
1311    script.Print("Patching vendor files...")
1312    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1313
1314  if not OPTIONS.two_step:
1315    if updating_boot:
1316      # Produce the boot image by applying a patch to the current
1317      # contents of the boot partition, and write it back to the
1318      # partition.
1319      script.Print("Patching boot image...")
1320      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1321                        % (boot_type, boot_device,
1322                           source_boot.size, source_boot.sha1,
1323                           target_boot.size, target_boot.sha1),
1324                        "-",
1325                        target_boot.size, target_boot.sha1,
1326                        source_boot.sha1, "patch/boot.img.p")
1327      so_far += target_boot.size
1328      script.SetProgress(so_far / total_patch_size)
1329      print "boot image changed; including."
1330    else:
1331      print "boot image unchanged; skipping."
1332
1333  system_items = ItemSet("system", "META/filesystem_config.txt")
1334  if vendor_diff:
1335    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1336
1337  if updating_recovery:
1338    # Recovery is generated as a patch using both the boot image
1339    # (which contains the same linux kernel as recovery) and the file
1340    # /system/etc/recovery-resource.dat (which contains all the images
1341    # used in the recovery UI) as sources.  This lets us minimize the
1342    # size of the patch, which must be included in every OTA package.
1343    #
1344    # For older builds where recovery-resource.dat is not present, we
1345    # use only the boot image as the source.
1346
1347    if not target_has_recovery_patch:
1348      def output_sink(fn, data):
1349        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1350        system_items.Get("system/" + fn)
1351
1352      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1353                               target_recovery, target_boot)
1354      script.DeleteFiles(["/system/recovery-from-boot.p",
1355                          "/system/etc/install-recovery.sh"])
1356    print "recovery image changed; including as patch from boot."
1357  else:
1358    print "recovery image unchanged; skipping."
1359
1360  script.ShowProgress(0.1, 10)
1361
1362  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1363  if vendor_diff:
1364    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1365
1366  temp_script = script.MakeTemporary()
1367  system_items.GetMetadata(target_zip)
1368  system_items.Get("system").SetPermissions(temp_script)
1369  if vendor_diff:
1370    vendor_items.GetMetadata(target_zip)
1371    vendor_items.Get("vendor").SetPermissions(temp_script)
1372
1373  # Note that this call will mess up the trees of Items, so make sure
1374  # we're done with them.
1375  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1376  if vendor_diff:
1377    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1378
1379  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1380  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1381
1382  # Delete all the symlinks in source that aren't in target.  This
1383  # needs to happen before verbatim files are unpacked, in case a
1384  # symlink in the source is replaced by a real file in the target.
1385  to_delete = []
1386  for dest, link in source_symlinks:
1387    if link not in target_symlinks_d:
1388      to_delete.append(link)
1389  script.DeleteFiles(to_delete)
1390
1391  if system_diff.verbatim_targets:
1392    script.Print("Unpacking new system files...")
1393    script.UnpackPackageDir("system", "/system")
1394  if vendor_diff and vendor_diff.verbatim_targets:
1395    script.Print("Unpacking new vendor files...")
1396    script.UnpackPackageDir("vendor", "/vendor")
1397
1398  if updating_recovery and not target_has_recovery_patch:
1399    script.Print("Unpacking new recovery...")
1400    script.UnpackPackageDir("recovery", "/system")
1401
1402  system_diff.EmitRenames(script)
1403  if vendor_diff:
1404    vendor_diff.EmitRenames(script)
1405
1406  script.Print("Symlinks and permissions...")
1407
1408  # Create all the symlinks that don't already exist, or point to
1409  # somewhere different than what we want.  Delete each symlink before
1410  # creating it, since the 'symlink' command won't overwrite.
1411  to_create = []
1412  for dest, link in target_symlinks:
1413    if link in source_symlinks_d:
1414      if dest != source_symlinks_d[link]:
1415        to_create.append((dest, link))
1416    else:
1417      to_create.append((dest, link))
1418  script.DeleteFiles([i[1] for i in to_create])
1419  script.MakeSymlinks(to_create)
1420
1421  # Now that the symlinks are created, we can set all the
1422  # permissions.
1423  script.AppendScript(temp_script)
1424
1425  # Do device-specific installation (eg, write radio image).
1426  device_specific.IncrementalOTA_InstallEnd()
1427
1428  if OPTIONS.extra_script is not None:
1429    script.AppendExtra(OPTIONS.extra_script)
1430
1431  # Patch the build.prop file last, so if something fails but the
1432  # device can still come up, it appears to be the old build and will
1433  # get set the OTA package again to retry.
1434  script.Print("Patching remaining system files...")
1435  system_diff.EmitDeferredPatches(script)
1436
1437  if OPTIONS.wipe_user_data:
1438    script.Print("Erasing user data...")
1439    script.FormatPartition("/data")
1440
1441  if OPTIONS.two_step:
1442    script.AppendExtra("""
1443set_stage("%(bcb_dev)s", "");
1444endif;
1445endif;
1446""" % bcb_dev)
1447
1448  if OPTIONS.verify and system_diff:
1449    script.Print("Remounting and verifying system partition files...")
1450    script.Unmount("/system")
1451    script.Mount("/system")
1452    system_diff.EmitExplicitTargetVerification(script)
1453
1454  if OPTIONS.verify and vendor_diff:
1455    script.Print("Remounting and verifying vendor partition files...")
1456    script.Unmount("/vendor")
1457    script.Mount("/vendor")
1458    vendor_diff.EmitExplicitTargetVerification(script)
1459  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1460
1461  WriteMetadata(metadata, output_zip)
1462
1463
1464def main(argv):
1465
1466  def option_handler(o, a):
1467    if o == "--board_config":
1468      pass   # deprecated
1469    elif o in ("-k", "--package_key"):
1470      OPTIONS.package_key = a
1471    elif o in ("-i", "--incremental_from"):
1472      OPTIONS.incremental_source = a
1473    elif o == "--full_radio":
1474      OPTIONS.full_radio = True
1475    elif o in ("-w", "--wipe_user_data"):
1476      OPTIONS.wipe_user_data = True
1477    elif o in ("-n", "--no_prereq"):
1478      OPTIONS.omit_prereq = True
1479    elif o in ("-o", "--oem_settings"):
1480      OPTIONS.oem_source = a
1481    elif o in ("-e", "--extra_script"):
1482      OPTIONS.extra_script = a
1483    elif o in ("-a", "--aslr_mode"):
1484      if a in ("on", "On", "true", "True", "yes", "Yes"):
1485        OPTIONS.aslr_mode = True
1486      else:
1487        OPTIONS.aslr_mode = False
1488    elif o in ("-t", "--worker_threads"):
1489      if a.isdigit():
1490        OPTIONS.worker_threads = int(a)
1491      else:
1492        raise ValueError("Cannot parse value %r for option %r - only "
1493                         "integers are allowed." % (a, o))
1494    elif o in ("-2", "--two_step"):
1495      OPTIONS.two_step = True
1496    elif o == "--no_signing":
1497      OPTIONS.no_signing = True
1498    elif o == "--verify":
1499      OPTIONS.verify = True
1500    elif o == "--block":
1501      OPTIONS.block_based = True
1502    elif o in ("-b", "--binary"):
1503      OPTIONS.updater_binary = a
1504    elif o in ("--no_fallback_to_full",):
1505      OPTIONS.fallback_to_full = False
1506    else:
1507      return False
1508    return True
1509
1510  args = common.ParseOptions(argv, __doc__,
1511                             extra_opts="b:k:i:d:wne:t:a:2o:",
1512                             extra_long_opts=[
1513                                 "board_config=",
1514                                 "package_key=",
1515                                 "incremental_from=",
1516                                 "full_radio",
1517                                 "wipe_user_data",
1518                                 "no_prereq",
1519                                 "extra_script=",
1520                                 "worker_threads=",
1521                                 "aslr_mode=",
1522                                 "two_step",
1523                                 "no_signing",
1524                                 "block",
1525                                 "binary=",
1526                                 "oem_settings=",
1527                                 "verify",
1528                                 "no_fallback_to_full",
1529                             ], extra_option_handler=option_handler)
1530
1531  if len(args) != 2:
1532    common.Usage(__doc__)
1533    sys.exit(1)
1534
1535  if OPTIONS.extra_script is not None:
1536    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1537
1538  print "unzipping target target-files..."
1539  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1540
1541  OPTIONS.target_tmp = OPTIONS.input_tmp
1542  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1543
1544  # If this image was originally labelled with SELinux contexts, make sure we
1545  # also apply the labels in our new image. During building, the "file_contexts"
1546  # is in the out/ directory tree, but for repacking from target-files.zip it's
1547  # in the root directory of the ramdisk.
1548  if "selinux_fc" in OPTIONS.info_dict:
1549    OPTIONS.info_dict["selinux_fc"] = os.path.join(
1550        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
1551
1552  if OPTIONS.verbose:
1553    print "--- target info ---"
1554    common.DumpInfoDict(OPTIONS.info_dict)
1555
1556  # If the caller explicitly specified the device-specific extensions
1557  # path via -s/--device_specific, use that.  Otherwise, use
1558  # META/releasetools.py if it is present in the target target_files.
1559  # Otherwise, take the path of the file from 'tool_extensions' in the
1560  # info dict and look for that in the local filesystem, relative to
1561  # the current directory.
1562
1563  if OPTIONS.device_specific is None:
1564    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1565    if os.path.exists(from_input):
1566      print "(using device-specific extensions from target_files)"
1567      OPTIONS.device_specific = from_input
1568    else:
1569      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1570
1571  if OPTIONS.device_specific is not None:
1572    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1573
1574  while True:
1575
1576    if OPTIONS.no_signing:
1577      if os.path.exists(args[1]):
1578        os.unlink(args[1])
1579      output_zip = zipfile.ZipFile(args[1], "w",
1580                                   compression=zipfile.ZIP_DEFLATED)
1581    else:
1582      temp_zip_file = tempfile.NamedTemporaryFile()
1583      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1584                                   compression=zipfile.ZIP_DEFLATED)
1585
1586    if OPTIONS.incremental_source is None:
1587      WriteFullOTAPackage(input_zip, output_zip)
1588      if OPTIONS.package_key is None:
1589        OPTIONS.package_key = OPTIONS.info_dict.get(
1590            "default_system_dev_certificate",
1591            "build/target/product/security/testkey")
1592      common.ZipClose(output_zip)
1593      break
1594
1595    else:
1596      print "unzipping source target-files..."
1597      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1598          OPTIONS.incremental_source)
1599      OPTIONS.target_info_dict = OPTIONS.info_dict
1600      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1601      if "selinux_fc" in OPTIONS.source_info_dict:
1602        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
1603            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
1604      if OPTIONS.package_key is None:
1605        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1606            "default_system_dev_certificate",
1607            "build/target/product/security/testkey")
1608      if OPTIONS.verbose:
1609        print "--- source info ---"
1610        common.DumpInfoDict(OPTIONS.source_info_dict)
1611      try:
1612        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1613        common.ZipClose(output_zip)
1614        break
1615      except ValueError:
1616        if not OPTIONS.fallback_to_full:
1617          raise
1618        print "--- failed to build incremental; falling back to full ---"
1619        OPTIONS.incremental_source = None
1620        common.ZipClose(output_zip)
1621
1622  if not OPTIONS.no_signing:
1623    SignOutput(temp_zip_file.name, args[1])
1624    temp_zip_file.close()
1625
1626  print "done."
1627
1628
1629if __name__ == '__main__':
1630  try:
1631    common.CloseInheritedPipes()
1632    main(sys.argv[1:])
1633  except common.ExternalError as e:
1634    print
1635    print "   ERROR: %s" % (e,)
1636    print
1637    sys.exit(1)
1638  finally:
1639    common.Cleanup()
1640