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