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