ota_from_target_files.py revision 68658c0f4fe5420226df5849b642f98fb7f5d984
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  # 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.
491  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
492
493  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
494  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
495  oem_dict = None
496  if oem_props is not None and len(oem_props) > 0:
497    if OPTIONS.oem_source is None:
498      raise common.ExternalError("OEM source required for this build")
499    script.Mount("/oem", recovery_mount_options)
500    oem_dict = common.LoadDictionaryFromLines(
501        open(OPTIONS.oem_source).readlines())
502
503  metadata = {
504      "post-build": CalculateFingerprint(oem_props, oem_dict,
505                                         OPTIONS.info_dict),
506      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
507                                   OPTIONS.info_dict),
508      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
509  }
510
511  device_specific = common.DeviceSpecificParams(
512      input_zip=input_zip,
513      input_version=OPTIONS.info_dict["recovery_api_version"],
514      output_zip=output_zip,
515      script=script,
516      input_tmp=OPTIONS.input_tmp,
517      metadata=metadata,
518      info_dict=OPTIONS.info_dict)
519
520  has_recovery_patch = HasRecoveryPatch(input_zip)
521  block_based = OPTIONS.block_based and has_recovery_patch
522
523  if not OPTIONS.omit_prereq:
524    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
525    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
526    script.AssertOlderBuild(ts, ts_text)
527
528  AppendAssertions(script, OPTIONS.info_dict, oem_dict)
529  device_specific.FullOTA_Assertions()
530
531  # Two-step package strategy (in chronological order, which is *not*
532  # the order in which the generated script has things):
533  #
534  # if stage is not "2/3" or "3/3":
535  #    write recovery image to boot partition
536  #    set stage to "2/3"
537  #    reboot to boot partition and restart recovery
538  # else if stage is "2/3":
539  #    write recovery image to recovery partition
540  #    set stage to "3/3"
541  #    reboot to recovery partition and restart recovery
542  # else:
543  #    (stage must be "3/3")
544  #    set stage to ""
545  #    do normal full package installation:
546  #       wipe and install system, boot image, etc.
547  #       set up system to update recovery partition on first boot
548  #    complete script normally
549  #    (allow recovery to mark itself finished and reboot)
550
551  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
552                                         OPTIONS.input_tmp, "RECOVERY")
553  if OPTIONS.two_step:
554    if not OPTIONS.info_dict.get("multistage_support", None):
555      assert False, "two-step packages not supported by this build"
556    fs = OPTIONS.info_dict["fstab"]["/misc"]
557    assert fs.fs_type.upper() == "EMMC", \
558        "two-step packages only supported on devices with EMMC /misc partitions"
559    bcb_dev = {"bcb_dev": fs.device}
560    common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
561    script.AppendExtra("""
562if get_stage("%(bcb_dev)s") == "2/3" then
563""" % bcb_dev)
564    script.WriteRawImage("/recovery", "recovery.img")
565    script.AppendExtra("""
566set_stage("%(bcb_dev)s", "3/3");
567reboot_now("%(bcb_dev)s", "recovery");
568else if get_stage("%(bcb_dev)s") == "3/3" then
569""" % bcb_dev)
570
571  # Dump fingerprints
572  script.Print("Target: %s" % CalculateFingerprint(
573      oem_props, oem_dict, OPTIONS.info_dict))
574
575  device_specific.FullOTA_InstallBegin()
576
577  system_progress = 0.75
578
579  if OPTIONS.wipe_user_data:
580    system_progress -= 0.1
581  if HasVendorPartition(input_zip):
582    system_progress -= 0.1
583
584  if "selinux_fc" in OPTIONS.info_dict:
585    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
586
587  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
588
589  system_items = ItemSet("system", "META/filesystem_config.txt")
590  script.ShowProgress(system_progress, 0)
591
592  if block_based:
593    # Full OTA is done as an "incremental" against an empty source
594    # image.  This has the effect of writing new data from the package
595    # to the entire partition, but lets us reuse the updater code that
596    # writes incrementals to do it.
597    system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
598    system_tgt.ResetFileMap()
599    system_diff = common.BlockDifference("system", system_tgt, src=None)
600    system_diff.WriteScript(script, output_zip)
601  else:
602    script.FormatPartition("/system")
603    script.Mount("/system", recovery_mount_options)
604    if not has_recovery_patch:
605      script.UnpackPackageDir("recovery", "/system")
606    script.UnpackPackageDir("system", "/system")
607
608    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
609    script.MakeSymlinks(symlinks)
610
611  boot_img = common.GetBootableImage("boot.img", "boot.img",
612                                     OPTIONS.input_tmp, "BOOT")
613
614  if not block_based:
615    def output_sink(fn, data):
616      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
617      system_items.Get("system/" + fn)
618
619    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
620                             recovery_img, boot_img)
621
622    system_items.GetMetadata(input_zip)
623    system_items.Get("system").SetPermissions(script)
624
625  if HasVendorPartition(input_zip):
626    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
627    script.ShowProgress(0.1, 0)
628
629    if block_based:
630      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
631      vendor_tgt.ResetFileMap()
632      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
633      vendor_diff.WriteScript(script, output_zip)
634    else:
635      script.FormatPartition("/vendor")
636      script.Mount("/vendor", recovery_mount_options)
637      script.UnpackPackageDir("vendor", "/vendor")
638
639      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
640      script.MakeSymlinks(symlinks)
641
642      vendor_items.GetMetadata(input_zip)
643      vendor_items.Get("vendor").SetPermissions(script)
644
645  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
646  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
647
648  script.ShowProgress(0.05, 5)
649  script.WriteRawImage("/boot", "boot.img")
650
651  script.ShowProgress(0.2, 10)
652  device_specific.FullOTA_InstallEnd()
653
654  if OPTIONS.extra_script is not None:
655    script.AppendExtra(OPTIONS.extra_script)
656
657  script.UnmountAll()
658
659  if OPTIONS.wipe_user_data:
660    script.ShowProgress(0.1, 10)
661    script.FormatPartition("/data")
662
663  if OPTIONS.two_step:
664    script.AppendExtra("""
665set_stage("%(bcb_dev)s", "");
666""" % bcb_dev)
667    script.AppendExtra("else\n")
668    script.WriteRawImage("/boot", "recovery.img")
669    script.AppendExtra("""
670set_stage("%(bcb_dev)s", "2/3");
671reboot_now("%(bcb_dev)s", "");
672endif;
673endif;
674""" % bcb_dev)
675  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
676  WriteMetadata(metadata, output_zip)
677
678
679def WritePolicyConfig(file_name, output_zip):
680  common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
681
682
683def WriteMetadata(metadata, output_zip):
684  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
685                     "".join(["%s=%s\n" % kv
686                              for kv in sorted(metadata.iteritems())]))
687
688
689def LoadPartitionFiles(z, partition):
690  """Load all the files from the given partition in a given target-files
691  ZipFile, and return a dict of {filename: File object}."""
692  out = {}
693  prefix = partition.upper() + "/"
694  for info in z.infolist():
695    if info.filename.startswith(prefix) and not IsSymlink(info):
696      basefilename = info.filename[len(prefix):]
697      fn = partition + "/" + basefilename
698      data = z.read(info.filename)
699      out[fn] = common.File(fn, data)
700  return out
701
702
703def GetBuildProp(prop, info_dict):
704  """Return the fingerprint of the build of a given target-files info_dict."""
705  try:
706    return info_dict.get("build.prop", {})[prop]
707  except KeyError:
708    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
709
710
711def AddToKnownPaths(filename, known_paths):
712  if filename[-1] == "/":
713    return
714  dirs = filename.split("/")[:-1]
715  while len(dirs) > 0:
716    path = "/".join(dirs)
717    if path in known_paths:
718      break
719    known_paths.add(path)
720    dirs.pop()
721
722
723def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
724  source_version = OPTIONS.source_info_dict["recovery_api_version"]
725  target_version = OPTIONS.target_info_dict["recovery_api_version"]
726
727  if source_version == 0:
728    print ("WARNING: generating edify script for a source that "
729           "can't install it.")
730  script = edify_generator.EdifyGenerator(source_version,
731                                          OPTIONS.target_info_dict)
732
733  metadata = {
734      "pre-device": GetBuildProp("ro.product.device",
735                                 OPTIONS.source_info_dict),
736      "post-timestamp": GetBuildProp("ro.build.date.utc",
737                                     OPTIONS.target_info_dict),
738  }
739
740  device_specific = common.DeviceSpecificParams(
741      source_zip=source_zip,
742      source_version=source_version,
743      target_zip=target_zip,
744      target_version=target_version,
745      output_zip=output_zip,
746      script=script,
747      metadata=metadata,
748      info_dict=OPTIONS.info_dict)
749
750  # TODO: Currently this works differently from WriteIncrementalOTAPackage().
751  # This function doesn't consider thumbprints when writing
752  # metadata["pre/post-build"]. One possible reason is that the current
753  # devices with thumbprints are all using file-based OTAs. Long term we
754  # should factor out the common parts into a shared one to avoid further
755  # divergence.
756  source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
757  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
758  metadata["pre-build"] = source_fp
759  metadata["post-build"] = target_fp
760
761  source_boot = common.GetBootableImage(
762      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
763      OPTIONS.source_info_dict)
764  target_boot = common.GetBootableImage(
765      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
766  updating_boot = (not OPTIONS.two_step and
767                   (source_boot.data != target_boot.data))
768
769  target_recovery = common.GetBootableImage(
770      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
771
772  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
773  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
774
775  blockimgdiff_version = 1
776  if OPTIONS.info_dict:
777    blockimgdiff_version = max(
778        int(i) for i in
779        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
780
781  system_diff = common.BlockDifference("system", system_tgt, system_src,
782                                       version=blockimgdiff_version)
783
784  if HasVendorPartition(target_zip):
785    if not HasVendorPartition(source_zip):
786      raise RuntimeError("can't generate incremental that adds /vendor")
787    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
788                          OPTIONS.source_info_dict)
789    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
790                          OPTIONS.target_info_dict)
791    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
792                                         version=blockimgdiff_version)
793  else:
794    vendor_diff = None
795
796  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
797  recovery_mount_options = OPTIONS.target_info_dict.get(
798      "recovery_mount_options")
799  oem_dict = None
800  if oem_props is not None and len(oem_props) > 0:
801    if OPTIONS.oem_source is None:
802      raise common.ExternalError("OEM source required for this build")
803    script.Mount("/oem", recovery_mount_options)
804    oem_dict = common.LoadDictionaryFromLines(
805        open(OPTIONS.oem_source).readlines())
806
807  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
808  device_specific.IncrementalOTA_Assertions()
809
810  # Two-step incremental package strategy (in chronological order,
811  # which is *not* the order in which the generated script has
812  # things):
813  #
814  # if stage is not "2/3" or "3/3":
815  #    do verification on current system
816  #    write recovery image to boot partition
817  #    set stage to "2/3"
818  #    reboot to boot partition and restart recovery
819  # else if stage is "2/3":
820  #    write recovery image to recovery partition
821  #    set stage to "3/3"
822  #    reboot to recovery partition and restart recovery
823  # else:
824  #    (stage must be "3/3")
825  #    perform update:
826  #       patch system files, etc.
827  #       force full install of new boot image
828  #       set up system to update recovery partition on first boot
829  #    complete script normally
830  #    (allow recovery to mark itself finished and reboot)
831
832  if OPTIONS.two_step:
833    if not OPTIONS.info_dict.get("multistage_support", None):
834      assert False, "two-step packages not supported by this build"
835    fs = OPTIONS.info_dict["fstab"]["/misc"]
836    assert fs.fs_type.upper() == "EMMC", \
837        "two-step packages only supported on devices with EMMC /misc partitions"
838    bcb_dev = {"bcb_dev": fs.device}
839    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
840    script.AppendExtra("""
841if get_stage("%(bcb_dev)s") == "2/3" then
842""" % bcb_dev)
843    script.AppendExtra("sleep(20);\n")
844    script.WriteRawImage("/recovery", "recovery.img")
845    script.AppendExtra("""
846set_stage("%(bcb_dev)s", "3/3");
847reboot_now("%(bcb_dev)s", "recovery");
848else if get_stage("%(bcb_dev)s") != "3/3" then
849""" % bcb_dev)
850
851  # Dump fingerprints
852  script.Print("Source: %s" % CalculateFingerprint(
853      oem_props, oem_dict, OPTIONS.source_info_dict))
854  script.Print("Target: %s" % CalculateFingerprint(
855      oem_props, oem_dict, OPTIONS.target_info_dict))
856
857  script.Print("Verifying current system...")
858
859  device_specific.IncrementalOTA_VerifyBegin()
860
861  if oem_props is None:
862    # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
863    # patching on a device that's already on the target build will damage the
864    # system. Because operations like move don't check the block state, they
865    # always apply the changes unconditionally.
866    if blockimgdiff_version <= 2:
867      script.AssertSomeFingerprint(source_fp)
868    else:
869      script.AssertSomeFingerprint(source_fp, target_fp)
870  else:
871    if blockimgdiff_version <= 2:
872      script.AssertSomeThumbprint(
873          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
874    else:
875      script.AssertSomeThumbprint(
876          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
877          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
878
879  if updating_boot:
880    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
881    d = common.Difference(target_boot, source_boot)
882    _, _, d = d.ComputePatch()
883    if d is None:
884      include_full_boot = True
885      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
886    else:
887      include_full_boot = False
888
889      print "boot      target: %d  source: %d  diff: %d" % (
890          target_boot.size, source_boot.size, len(d))
891
892      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
893
894      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
895                        (boot_type, boot_device,
896                         source_boot.size, source_boot.sha1,
897                         target_boot.size, target_boot.sha1))
898
899  device_specific.IncrementalOTA_VerifyEnd()
900
901  if OPTIONS.two_step:
902    script.WriteRawImage("/boot", "recovery.img")
903    script.AppendExtra("""
904set_stage("%(bcb_dev)s", "2/3");
905reboot_now("%(bcb_dev)s", "");
906else
907""" % bcb_dev)
908
909  # Verify the existing partitions.
910  system_diff.WriteVerifyScript(script)
911  if vendor_diff:
912    vendor_diff.WriteVerifyScript(script)
913
914  script.Comment("---- start making changes here ----")
915
916  device_specific.IncrementalOTA_InstallBegin()
917
918  system_diff.WriteScript(script, output_zip,
919                          progress=0.8 if vendor_diff else 0.9)
920
921  if vendor_diff:
922    vendor_diff.WriteScript(script, output_zip, progress=0.1)
923
924  if OPTIONS.two_step:
925    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
926    script.WriteRawImage("/boot", "boot.img")
927    print "writing full boot image (forced by two-step mode)"
928
929  if not OPTIONS.two_step:
930    if updating_boot:
931      if include_full_boot:
932        print "boot image changed; including full."
933        script.Print("Installing boot image...")
934        script.WriteRawImage("/boot", "boot.img")
935      else:
936        # Produce the boot image by applying a patch to the current
937        # contents of the boot partition, and write it back to the
938        # partition.
939        print "boot image changed; including patch."
940        script.Print("Patching boot image...")
941        script.ShowProgress(0.1, 10)
942        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
943                          % (boot_type, boot_device,
944                             source_boot.size, source_boot.sha1,
945                             target_boot.size, target_boot.sha1),
946                          "-",
947                          target_boot.size, target_boot.sha1,
948                          source_boot.sha1, "patch/boot.img.p")
949    else:
950      print "boot image unchanged; skipping."
951
952  # Do device-specific installation (eg, write radio image).
953  device_specific.IncrementalOTA_InstallEnd()
954
955  if OPTIONS.extra_script is not None:
956    script.AppendExtra(OPTIONS.extra_script)
957
958  if OPTIONS.wipe_user_data:
959    script.Print("Erasing user data...")
960    script.FormatPartition("/data")
961
962  if OPTIONS.two_step:
963    script.AppendExtra("""
964set_stage("%(bcb_dev)s", "");
965endif;
966endif;
967""" % bcb_dev)
968
969  script.SetProgress(1)
970  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
971  WriteMetadata(metadata, output_zip)
972
973
974class FileDifference(object):
975  def __init__(self, partition, source_zip, target_zip, output_zip):
976    self.deferred_patch_list = None
977    print "Loading target..."
978    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
979    print "Loading source..."
980    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
981
982    self.verbatim_targets = verbatim_targets = []
983    self.patch_list = patch_list = []
984    diffs = []
985    self.renames = renames = {}
986    known_paths = set()
987    largest_source_size = 0
988
989    matching_file_cache = {}
990    for fn, sf in source_data.items():
991      assert fn == sf.name
992      matching_file_cache["path:" + fn] = sf
993      if fn in target_data.keys():
994        AddToKnownPaths(fn, known_paths)
995      # Only allow eligibility for filename/sha matching
996      # if there isn't a perfect path match.
997      if target_data.get(sf.name) is None:
998        matching_file_cache["file:" + fn.split("/")[-1]] = sf
999        matching_file_cache["sha:" + sf.sha1] = sf
1000
1001    for fn in sorted(target_data.keys()):
1002      tf = target_data[fn]
1003      assert fn == tf.name
1004      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1005      if sf is not None and sf.name != tf.name:
1006        print "File has moved from " + sf.name + " to " + tf.name
1007        renames[sf.name] = tf
1008
1009      if sf is None or fn in OPTIONS.require_verbatim:
1010        # This file should be included verbatim
1011        if fn in OPTIONS.prohibit_verbatim:
1012          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1013        print "send", fn, "verbatim"
1014        tf.AddToZip(output_zip)
1015        verbatim_targets.append((fn, tf.size, tf.sha1))
1016        if fn in target_data.keys():
1017          AddToKnownPaths(fn, known_paths)
1018      elif tf.sha1 != sf.sha1:
1019        # File is different; consider sending as a patch
1020        diffs.append(common.Difference(tf, sf))
1021      else:
1022        # Target file data identical to source (may still be renamed)
1023        pass
1024
1025    common.ComputeDifferences(diffs)
1026
1027    for diff in diffs:
1028      tf, sf, d = diff.GetPatch()
1029      path = "/".join(tf.name.split("/")[:-1])
1030      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1031          path not in known_paths:
1032        # patch is almost as big as the file; don't bother patching
1033        # or a patch + rename cannot take place due to the target
1034        # directory not existing
1035        tf.AddToZip(output_zip)
1036        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1037        if sf.name in renames:
1038          del renames[sf.name]
1039        AddToKnownPaths(tf.name, known_paths)
1040      else:
1041        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1042        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1043        largest_source_size = max(largest_source_size, sf.size)
1044
1045    self.largest_source_size = largest_source_size
1046
1047  def EmitVerification(self, script):
1048    so_far = 0
1049    for tf, sf, _, _ in self.patch_list:
1050      if tf.name != sf.name:
1051        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1052      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1053      so_far += sf.size
1054    return so_far
1055
1056  def EmitExplicitTargetVerification(self, script):
1057    for fn, _, sha1 in self.verbatim_targets:
1058      if fn[-1] != "/":
1059        script.FileCheck("/"+fn, sha1)
1060    for tf, _, _, _ in self.patch_list:
1061      script.FileCheck(tf.name, tf.sha1)
1062
1063  def RemoveUnneededFiles(self, script, extras=()):
1064    script.DeleteFiles(
1065        ["/" + i[0] for i in self.verbatim_targets] +
1066        ["/" + i for i in sorted(self.source_data)
1067         if i not in self.target_data and i not in self.renames] +
1068        list(extras))
1069
1070  def TotalPatchSize(self):
1071    return sum(i[1].size for i in self.patch_list)
1072
1073  def EmitPatches(self, script, total_patch_size, so_far):
1074    self.deferred_patch_list = deferred_patch_list = []
1075    for item in self.patch_list:
1076      tf, sf, _, _ = item
1077      if tf.name == "system/build.prop":
1078        deferred_patch_list.append(item)
1079        continue
1080      if sf.name != tf.name:
1081        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1082      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1083                        "patch/" + sf.name + ".p")
1084      so_far += tf.size
1085      script.SetProgress(so_far / total_patch_size)
1086    return so_far
1087
1088  def EmitDeferredPatches(self, script):
1089    for item in self.deferred_patch_list:
1090      tf, sf, _, _ = item
1091      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1092                        "patch/" + sf.name + ".p")
1093    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1094
1095  def EmitRenames(self, script):
1096    if len(self.renames) > 0:
1097      script.Print("Renaming files...")
1098      for src, tgt in self.renames.iteritems():
1099        print "Renaming " + src + " to " + tgt.name
1100        script.RenameFile(src, tgt.name)
1101
1102
1103def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1104  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1105  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1106
1107  if (OPTIONS.block_based and
1108      target_has_recovery_patch and
1109      source_has_recovery_patch):
1110    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1111
1112  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1113  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1114
1115  if source_version == 0:
1116    print ("WARNING: generating edify script for a source that "
1117           "can't install it.")
1118  script = edify_generator.EdifyGenerator(source_version,
1119                                          OPTIONS.target_info_dict)
1120
1121  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1122  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
1123  oem_dict = None
1124  if oem_props is not None and len(oem_props) > 0:
1125    if OPTIONS.oem_source is None:
1126      raise common.ExternalError("OEM source required for this build")
1127    script.Mount("/oem", recovery_mount_options)
1128    oem_dict = common.LoadDictionaryFromLines(
1129        open(OPTIONS.oem_source).readlines())
1130
1131  metadata = {
1132      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1133                                   OPTIONS.source_info_dict),
1134      "post-timestamp": GetBuildProp("ro.build.date.utc",
1135                                     OPTIONS.target_info_dict),
1136  }
1137
1138  device_specific = common.DeviceSpecificParams(
1139      source_zip=source_zip,
1140      source_version=source_version,
1141      target_zip=target_zip,
1142      target_version=target_version,
1143      output_zip=output_zip,
1144      script=script,
1145      metadata=metadata,
1146      info_dict=OPTIONS.info_dict)
1147
1148  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1149  script.Mount("/system", recovery_mount_options)
1150  if HasVendorPartition(target_zip):
1151    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1152    script.Mount("/vendor", recovery_mount_options)
1153  else:
1154    vendor_diff = None
1155
1156  target_fp = CalculateFingerprint(oem_props, oem_dict,
1157                                   OPTIONS.target_info_dict)
1158  source_fp = CalculateFingerprint(oem_props, oem_dict,
1159                                   OPTIONS.source_info_dict)
1160
1161  if oem_props is None:
1162    script.AssertSomeFingerprint(source_fp, target_fp)
1163  else:
1164    script.AssertSomeThumbprint(
1165        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1166        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1167
1168  metadata["pre-build"] = source_fp
1169  metadata["post-build"] = target_fp
1170
1171  source_boot = common.GetBootableImage(
1172      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1173      OPTIONS.source_info_dict)
1174  target_boot = common.GetBootableImage(
1175      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1176  updating_boot = (not OPTIONS.two_step and
1177                   (source_boot.data != target_boot.data))
1178
1179  source_recovery = common.GetBootableImage(
1180      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1181      OPTIONS.source_info_dict)
1182  target_recovery = common.GetBootableImage(
1183      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1184  updating_recovery = (source_recovery.data != target_recovery.data)
1185
1186  # Here's how we divide up the progress bar:
1187  #  0.1 for verifying the start state (PatchCheck calls)
1188  #  0.8 for applying patches (ApplyPatch calls)
1189  #  0.1 for unpacking verbatim files, symlinking, and doing the
1190  #      device-specific commands.
1191
1192  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1193  device_specific.IncrementalOTA_Assertions()
1194
1195  # Two-step incremental package strategy (in chronological order,
1196  # which is *not* the order in which the generated script has
1197  # things):
1198  #
1199  # if stage is not "2/3" or "3/3":
1200  #    do verification on current system
1201  #    write recovery image to boot partition
1202  #    set stage to "2/3"
1203  #    reboot to boot partition and restart recovery
1204  # else if stage is "2/3":
1205  #    write recovery image to recovery partition
1206  #    set stage to "3/3"
1207  #    reboot to recovery partition and restart recovery
1208  # else:
1209  #    (stage must be "3/3")
1210  #    perform update:
1211  #       patch system files, etc.
1212  #       force full install of new boot image
1213  #       set up system to update recovery partition on first boot
1214  #    complete script normally
1215  #    (allow recovery to mark itself finished and reboot)
1216
1217  if OPTIONS.two_step:
1218    if not OPTIONS.info_dict.get("multistage_support", None):
1219      assert False, "two-step packages not supported by this build"
1220    fs = OPTIONS.info_dict["fstab"]["/misc"]
1221    assert fs.fs_type.upper() == "EMMC", \
1222        "two-step packages only supported on devices with EMMC /misc partitions"
1223    bcb_dev = {"bcb_dev": fs.device}
1224    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1225    script.AppendExtra("""
1226if get_stage("%(bcb_dev)s") == "2/3" then
1227""" % bcb_dev)
1228    script.AppendExtra("sleep(20);\n")
1229    script.WriteRawImage("/recovery", "recovery.img")
1230    script.AppendExtra("""
1231set_stage("%(bcb_dev)s", "3/3");
1232reboot_now("%(bcb_dev)s", "recovery");
1233else if get_stage("%(bcb_dev)s") != "3/3" then
1234""" % bcb_dev)
1235
1236  # Dump fingerprints
1237  script.Print("Source: %s" % (source_fp,))
1238  script.Print("Target: %s" % (target_fp,))
1239
1240  script.Print("Verifying current system...")
1241
1242  device_specific.IncrementalOTA_VerifyBegin()
1243
1244  script.ShowProgress(0.1, 0)
1245  so_far = system_diff.EmitVerification(script)
1246  if vendor_diff:
1247    so_far += vendor_diff.EmitVerification(script)
1248
1249  if updating_boot:
1250    d = common.Difference(target_boot, source_boot)
1251    _, _, d = d.ComputePatch()
1252    print "boot      target: %d  source: %d  diff: %d" % (
1253        target_boot.size, source_boot.size, len(d))
1254
1255    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1256
1257    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
1258
1259    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1260                      (boot_type, boot_device,
1261                       source_boot.size, source_boot.sha1,
1262                       target_boot.size, target_boot.sha1))
1263    so_far += source_boot.size
1264
1265  size = []
1266  if system_diff.patch_list:
1267    size.append(system_diff.largest_source_size)
1268  if vendor_diff:
1269    if vendor_diff.patch_list:
1270      size.append(vendor_diff.largest_source_size)
1271  if size or updating_recovery or updating_boot:
1272    script.CacheFreeSpaceCheck(max(size))
1273
1274  device_specific.IncrementalOTA_VerifyEnd()
1275
1276  if OPTIONS.two_step:
1277    script.WriteRawImage("/boot", "recovery.img")
1278    script.AppendExtra("""
1279set_stage("%(bcb_dev)s", "2/3");
1280reboot_now("%(bcb_dev)s", "");
1281else
1282""" % bcb_dev)
1283
1284  script.Comment("---- start making changes here ----")
1285
1286  device_specific.IncrementalOTA_InstallBegin()
1287
1288  if OPTIONS.two_step:
1289    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1290    script.WriteRawImage("/boot", "boot.img")
1291    print "writing full boot image (forced by two-step mode)"
1292
1293  script.Print("Removing unneeded files...")
1294  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1295  if vendor_diff:
1296    vendor_diff.RemoveUnneededFiles(script)
1297
1298  script.ShowProgress(0.8, 0)
1299  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1300  if vendor_diff:
1301    total_patch_size += vendor_diff.TotalPatchSize()
1302  if updating_boot:
1303    total_patch_size += target_boot.size
1304
1305  script.Print("Patching system files...")
1306  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1307  if vendor_diff:
1308    script.Print("Patching vendor files...")
1309    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1310
1311  if not OPTIONS.two_step:
1312    if updating_boot:
1313      # Produce the boot image by applying a patch to the current
1314      # contents of the boot partition, and write it back to the
1315      # partition.
1316      script.Print("Patching boot image...")
1317      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1318                        % (boot_type, boot_device,
1319                           source_boot.size, source_boot.sha1,
1320                           target_boot.size, target_boot.sha1),
1321                        "-",
1322                        target_boot.size, target_boot.sha1,
1323                        source_boot.sha1, "patch/boot.img.p")
1324      so_far += target_boot.size
1325      script.SetProgress(so_far / total_patch_size)
1326      print "boot image changed; including."
1327    else:
1328      print "boot image unchanged; skipping."
1329
1330  system_items = ItemSet("system", "META/filesystem_config.txt")
1331  if vendor_diff:
1332    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1333
1334  if updating_recovery:
1335    # Recovery is generated as a patch using both the boot image
1336    # (which contains the same linux kernel as recovery) and the file
1337    # /system/etc/recovery-resource.dat (which contains all the images
1338    # used in the recovery UI) as sources.  This lets us minimize the
1339    # size of the patch, which must be included in every OTA package.
1340    #
1341    # For older builds where recovery-resource.dat is not present, we
1342    # use only the boot image as the source.
1343
1344    if not target_has_recovery_patch:
1345      def output_sink(fn, data):
1346        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1347        system_items.Get("system/" + fn)
1348
1349      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1350                               target_recovery, target_boot)
1351      script.DeleteFiles(["/system/recovery-from-boot.p",
1352                          "/system/etc/install-recovery.sh"])
1353    print "recovery image changed; including as patch from boot."
1354  else:
1355    print "recovery image unchanged; skipping."
1356
1357  script.ShowProgress(0.1, 10)
1358
1359  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1360  if vendor_diff:
1361    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1362
1363  temp_script = script.MakeTemporary()
1364  system_items.GetMetadata(target_zip)
1365  system_items.Get("system").SetPermissions(temp_script)
1366  if vendor_diff:
1367    vendor_items.GetMetadata(target_zip)
1368    vendor_items.Get("vendor").SetPermissions(temp_script)
1369
1370  # Note that this call will mess up the trees of Items, so make sure
1371  # we're done with them.
1372  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1373  if vendor_diff:
1374    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1375
1376  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1377  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1378
1379  # Delete all the symlinks in source that aren't in target.  This
1380  # needs to happen before verbatim files are unpacked, in case a
1381  # symlink in the source is replaced by a real file in the target.
1382  to_delete = []
1383  for dest, link in source_symlinks:
1384    if link not in target_symlinks_d:
1385      to_delete.append(link)
1386  script.DeleteFiles(to_delete)
1387
1388  if system_diff.verbatim_targets:
1389    script.Print("Unpacking new system files...")
1390    script.UnpackPackageDir("system", "/system")
1391  if vendor_diff and vendor_diff.verbatim_targets:
1392    script.Print("Unpacking new vendor files...")
1393    script.UnpackPackageDir("vendor", "/vendor")
1394
1395  if updating_recovery and not target_has_recovery_patch:
1396    script.Print("Unpacking new recovery...")
1397    script.UnpackPackageDir("recovery", "/system")
1398
1399  system_diff.EmitRenames(script)
1400  if vendor_diff:
1401    vendor_diff.EmitRenames(script)
1402
1403  script.Print("Symlinks and permissions...")
1404
1405  # Create all the symlinks that don't already exist, or point to
1406  # somewhere different than what we want.  Delete each symlink before
1407  # creating it, since the 'symlink' command won't overwrite.
1408  to_create = []
1409  for dest, link in target_symlinks:
1410    if link in source_symlinks_d:
1411      if dest != source_symlinks_d[link]:
1412        to_create.append((dest, link))
1413    else:
1414      to_create.append((dest, link))
1415  script.DeleteFiles([i[1] for i in to_create])
1416  script.MakeSymlinks(to_create)
1417
1418  # Now that the symlinks are created, we can set all the
1419  # permissions.
1420  script.AppendScript(temp_script)
1421
1422  # Do device-specific installation (eg, write radio image).
1423  device_specific.IncrementalOTA_InstallEnd()
1424
1425  if OPTIONS.extra_script is not None:
1426    script.AppendExtra(OPTIONS.extra_script)
1427
1428  # Patch the build.prop file last, so if something fails but the
1429  # device can still come up, it appears to be the old build and will
1430  # get set the OTA package again to retry.
1431  script.Print("Patching remaining system files...")
1432  system_diff.EmitDeferredPatches(script)
1433
1434  if OPTIONS.wipe_user_data:
1435    script.Print("Erasing user data...")
1436    script.FormatPartition("/data")
1437
1438  if OPTIONS.two_step:
1439    script.AppendExtra("""
1440set_stage("%(bcb_dev)s", "");
1441endif;
1442endif;
1443""" % bcb_dev)
1444
1445  if OPTIONS.verify and system_diff:
1446    script.Print("Remounting and verifying system partition files...")
1447    script.Unmount("/system")
1448    script.Mount("/system")
1449    system_diff.EmitExplicitTargetVerification(script)
1450
1451  if OPTIONS.verify and vendor_diff:
1452    script.Print("Remounting and verifying vendor partition files...")
1453    script.Unmount("/vendor")
1454    script.Mount("/vendor")
1455    vendor_diff.EmitExplicitTargetVerification(script)
1456  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1457
1458  WriteMetadata(metadata, output_zip)
1459
1460
1461def main(argv):
1462
1463  def option_handler(o, a):
1464    if o == "--board_config":
1465      pass   # deprecated
1466    elif o in ("-k", "--package_key"):
1467      OPTIONS.package_key = a
1468    elif o in ("-i", "--incremental_from"):
1469      OPTIONS.incremental_source = a
1470    elif o == "--full_radio":
1471      OPTIONS.full_radio = True
1472    elif o in ("-w", "--wipe_user_data"):
1473      OPTIONS.wipe_user_data = True
1474    elif o in ("-n", "--no_prereq"):
1475      OPTIONS.omit_prereq = True
1476    elif o in ("-o", "--oem_settings"):
1477      OPTIONS.oem_source = a
1478    elif o in ("-e", "--extra_script"):
1479      OPTIONS.extra_script = a
1480    elif o in ("-a", "--aslr_mode"):
1481      if a in ("on", "On", "true", "True", "yes", "Yes"):
1482        OPTIONS.aslr_mode = True
1483      else:
1484        OPTIONS.aslr_mode = False
1485    elif o in ("-t", "--worker_threads"):
1486      if a.isdigit():
1487        OPTIONS.worker_threads = int(a)
1488      else:
1489        raise ValueError("Cannot parse value %r for option %r - only "
1490                         "integers are allowed." % (a, o))
1491    elif o in ("-2", "--two_step"):
1492      OPTIONS.two_step = True
1493    elif o == "--no_signing":
1494      OPTIONS.no_signing = True
1495    elif o == "--verify":
1496      OPTIONS.verify = True
1497    elif o == "--block":
1498      OPTIONS.block_based = True
1499    elif o in ("-b", "--binary"):
1500      OPTIONS.updater_binary = a
1501    elif o in ("--no_fallback_to_full",):
1502      OPTIONS.fallback_to_full = False
1503    else:
1504      return False
1505    return True
1506
1507  args = common.ParseOptions(argv, __doc__,
1508                             extra_opts="b:k:i:d:wne:t:a:2o:",
1509                             extra_long_opts=[
1510                                 "board_config=",
1511                                 "package_key=",
1512                                 "incremental_from=",
1513                                 "full_radio",
1514                                 "wipe_user_data",
1515                                 "no_prereq",
1516                                 "extra_script=",
1517                                 "worker_threads=",
1518                                 "aslr_mode=",
1519                                 "two_step",
1520                                 "no_signing",
1521                                 "block",
1522                                 "binary=",
1523                                 "oem_settings=",
1524                                 "verify",
1525                                 "no_fallback_to_full",
1526                             ], extra_option_handler=option_handler)
1527
1528  if len(args) != 2:
1529    common.Usage(__doc__)
1530    sys.exit(1)
1531
1532  if OPTIONS.extra_script is not None:
1533    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1534
1535  print "unzipping target target-files..."
1536  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1537
1538  OPTIONS.target_tmp = OPTIONS.input_tmp
1539  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1540
1541  # If this image was originally labelled with SELinux contexts, make sure we
1542  # also apply the labels in our new image. During building, the "file_contexts"
1543  # is in the out/ directory tree, but for repacking from target-files.zip it's
1544  # in the root directory of the ramdisk.
1545  if "selinux_fc" in OPTIONS.info_dict:
1546    OPTIONS.info_dict["selinux_fc"] = os.path.join(
1547        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
1548
1549  if OPTIONS.verbose:
1550    print "--- target info ---"
1551    common.DumpInfoDict(OPTIONS.info_dict)
1552
1553  # If the caller explicitly specified the device-specific extensions
1554  # path via -s/--device_specific, use that.  Otherwise, use
1555  # META/releasetools.py if it is present in the target target_files.
1556  # Otherwise, take the path of the file from 'tool_extensions' in the
1557  # info dict and look for that in the local filesystem, relative to
1558  # the current directory.
1559
1560  if OPTIONS.device_specific is None:
1561    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1562    if os.path.exists(from_input):
1563      print "(using device-specific extensions from target_files)"
1564      OPTIONS.device_specific = from_input
1565    else:
1566      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1567
1568  if OPTIONS.device_specific is not None:
1569    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1570
1571  while True:
1572
1573    if OPTIONS.no_signing:
1574      if os.path.exists(args[1]):
1575        os.unlink(args[1])
1576      output_zip = zipfile.ZipFile(args[1], "w",
1577                                   compression=zipfile.ZIP_DEFLATED)
1578    else:
1579      temp_zip_file = tempfile.NamedTemporaryFile()
1580      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1581                                   compression=zipfile.ZIP_DEFLATED)
1582
1583    if OPTIONS.incremental_source is None:
1584      WriteFullOTAPackage(input_zip, output_zip)
1585      if OPTIONS.package_key is None:
1586        OPTIONS.package_key = OPTIONS.info_dict.get(
1587            "default_system_dev_certificate",
1588            "build/target/product/security/testkey")
1589      common.ZipClose(output_zip)
1590      break
1591
1592    else:
1593      print "unzipping source target-files..."
1594      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1595          OPTIONS.incremental_source)
1596      OPTIONS.target_info_dict = OPTIONS.info_dict
1597      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1598      if "selinux_fc" in OPTIONS.source_info_dict:
1599        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
1600            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
1601      if OPTIONS.package_key is None:
1602        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1603            "default_system_dev_certificate",
1604            "build/target/product/security/testkey")
1605      if OPTIONS.verbose:
1606        print "--- source info ---"
1607        common.DumpInfoDict(OPTIONS.source_info_dict)
1608      try:
1609        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1610        common.ZipClose(output_zip)
1611        break
1612      except ValueError:
1613        if not OPTIONS.fallback_to_full:
1614          raise
1615        print "--- failed to build incremental; falling back to full ---"
1616        OPTIONS.incremental_source = None
1617        common.ZipClose(output_zip)
1618
1619  if not OPTIONS.no_signing:
1620    SignOutput(temp_zip_file.name, args[1])
1621    temp_zip_file.close()
1622
1623  print "done."
1624
1625
1626if __name__ == '__main__':
1627  try:
1628    common.CloseInheritedPipes()
1629    main(sys.argv[1:])
1630  except common.ExternalError as e:
1631    print
1632    print "   ERROR: %s" % (e,)
1633    print
1634    sys.exit(1)
1635  finally:
1636    common.Cleanup()
1637