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