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