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