ota_from_target_files.py revision 39c322cefcc0ecba76a2ffb8881694fd010a87fc
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. 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  if "selinux_fc" in OPTIONS.info_dict:
586    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
587
588  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
589
590  system_items = ItemSet("system", "META/filesystem_config.txt")
591  script.ShowProgress(system_progress, 0)
592
593  if block_based:
594    # Full OTA is done as an "incremental" against an empty source
595    # image.  This has the effect of writing new data from the package
596    # to the entire partition, but lets us reuse the updater code that
597    # writes incrementals to do it.
598    system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict)
599    system_tgt.ResetFileMap()
600    system_diff = common.BlockDifference("system", system_tgt, src=None)
601    system_diff.WriteScript(script, output_zip)
602  else:
603    script.FormatPartition("/system")
604    script.Mount("/system", recovery_mount_options)
605    if not has_recovery_patch:
606      script.UnpackPackageDir("recovery", "/system")
607    script.UnpackPackageDir("system", "/system")
608
609    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
610    script.MakeSymlinks(symlinks)
611
612  boot_img = common.GetBootableImage("boot.img", "boot.img",
613                                     OPTIONS.input_tmp, "BOOT")
614
615  if not block_based:
616    def output_sink(fn, data):
617      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
618      system_items.Get("system/" + fn)
619
620    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
621                             recovery_img, boot_img)
622
623    system_items.GetMetadata(input_zip)
624    system_items.Get("system").SetPermissions(script)
625
626  if HasVendorPartition(input_zip):
627    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
628    script.ShowProgress(0.1, 0)
629
630    if block_based:
631      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
632      vendor_tgt.ResetFileMap()
633      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
634      vendor_diff.WriteScript(script, output_zip)
635    else:
636      script.FormatPartition("/vendor")
637      script.Mount("/vendor", recovery_mount_options)
638      script.UnpackPackageDir("vendor", "/vendor")
639
640      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
641      script.MakeSymlinks(symlinks)
642
643      vendor_items.GetMetadata(input_zip)
644      vendor_items.Get("vendor").SetPermissions(script)
645
646  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
647  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
648
649  script.ShowProgress(0.05, 5)
650  script.WriteRawImage("/boot", "boot.img")
651
652  script.ShowProgress(0.2, 10)
653  device_specific.FullOTA_InstallEnd()
654
655  if OPTIONS.extra_script is not None:
656    script.AppendExtra(OPTIONS.extra_script)
657
658  script.UnmountAll()
659
660  if OPTIONS.wipe_user_data:
661    script.ShowProgress(0.1, 10)
662    script.FormatPartition("/data")
663
664  if OPTIONS.two_step:
665    script.AppendExtra("""
666set_stage("%(bcb_dev)s", "");
667""" % bcb_dev)
668    script.AppendExtra("else\n")
669    script.WriteRawImage("/boot", "recovery.img")
670    script.AppendExtra("""
671set_stage("%(bcb_dev)s", "2/3");
672reboot_now("%(bcb_dev)s", "");
673endif;
674endif;
675""" % bcb_dev)
676  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
677  WriteMetadata(metadata, output_zip)
678
679
680def WritePolicyConfig(file_name, output_zip):
681  common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
682
683
684def WriteMetadata(metadata, output_zip):
685  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
686                     "".join(["%s=%s\n" % kv
687                              for kv in sorted(metadata.iteritems())]))
688
689
690def LoadPartitionFiles(z, partition):
691  """Load all the files from the given partition in a given target-files
692  ZipFile, and return a dict of {filename: File object}."""
693  out = {}
694  prefix = partition.upper() + "/"
695  for info in z.infolist():
696    if info.filename.startswith(prefix) and not IsSymlink(info):
697      basefilename = info.filename[len(prefix):]
698      fn = partition + "/" + basefilename
699      data = z.read(info.filename)
700      out[fn] = common.File(fn, data)
701  return out
702
703
704def GetBuildProp(prop, info_dict):
705  """Return the fingerprint of the build of a given target-files info_dict."""
706  try:
707    return info_dict.get("build.prop", {})[prop]
708  except KeyError:
709    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
710
711
712def AddToKnownPaths(filename, known_paths):
713  if filename[-1] == "/":
714    return
715  dirs = filename.split("/")[:-1]
716  while len(dirs) > 0:
717    path = "/".join(dirs)
718    if path in known_paths:
719      break
720    known_paths.add(path)
721    dirs.pop()
722
723
724def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
725  source_version = OPTIONS.source_info_dict["recovery_api_version"]
726  target_version = OPTIONS.target_info_dict["recovery_api_version"]
727
728  if source_version == 0:
729    print ("WARNING: generating edify script for a source that "
730           "can't install it.")
731  script = edify_generator.EdifyGenerator(
732      source_version, OPTIONS.target_info_dict,
733      fstab=OPTIONS.source_info_dict["fstab"])
734
735  metadata = {
736      "pre-device": GetBuildProp("ro.product.device",
737                                 OPTIONS.source_info_dict),
738      "post-timestamp": GetBuildProp("ro.build.date.utc",
739                                     OPTIONS.target_info_dict),
740  }
741
742  device_specific = common.DeviceSpecificParams(
743      source_zip=source_zip,
744      source_version=source_version,
745      target_zip=target_zip,
746      target_version=target_version,
747      output_zip=output_zip,
748      script=script,
749      metadata=metadata,
750      info_dict=OPTIONS.info_dict)
751
752  # TODO: Currently this works differently from WriteIncrementalOTAPackage().
753  # This function doesn't consider thumbprints when writing
754  # metadata["pre/post-build"]. One possible reason is that the current
755  # devices with thumbprints are all using file-based OTAs. Long term we
756  # should factor out the common parts into a shared one to avoid further
757  # divergence.
758  source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
759  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
760  metadata["pre-build"] = source_fp
761  metadata["post-build"] = target_fp
762
763  source_boot = common.GetBootableImage(
764      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
765      OPTIONS.source_info_dict)
766  target_boot = common.GetBootableImage(
767      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
768  updating_boot = (not OPTIONS.two_step and
769                   (source_boot.data != target_boot.data))
770
771  target_recovery = common.GetBootableImage(
772      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
773
774  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)
775  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)
776
777  blockimgdiff_version = 1
778  if OPTIONS.info_dict:
779    blockimgdiff_version = max(
780        int(i) for i in
781        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
782
783  system_diff = common.BlockDifference("system", system_tgt, system_src,
784                                       version=blockimgdiff_version)
785
786  if HasVendorPartition(target_zip):
787    if not HasVendorPartition(source_zip):
788      raise RuntimeError("can't generate incremental that adds /vendor")
789    vendor_src = GetImage("vendor", OPTIONS.source_tmp,
790                          OPTIONS.source_info_dict)
791    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp,
792                          OPTIONS.target_info_dict)
793    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
794                                         version=blockimgdiff_version)
795  else:
796    vendor_diff = None
797
798  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
799  recovery_mount_options = OPTIONS.source_info_dict.get(
800      "recovery_mount_options")
801  oem_dict = None
802  if oem_props is not None and len(oem_props) > 0:
803    if OPTIONS.oem_source is None:
804      raise common.ExternalError("OEM source required for this build")
805    script.Mount("/oem", recovery_mount_options)
806    oem_dict = common.LoadDictionaryFromLines(
807        open(OPTIONS.oem_source).readlines())
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.source_info_dict.get("multistage_support", None):
836      assert False, "two-step packages not supported by this build"
837    fs = OPTIONS.source_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(
883        "/boot", OPTIONS.source_info_dict)
884    d = common.Difference(target_boot, source_boot)
885    _, _, d = d.ComputePatch()
886    if d is None:
887      include_full_boot = True
888      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
889    else:
890      include_full_boot = False
891
892      print "boot      target: %d  source: %d  diff: %d" % (
893          target_boot.size, source_boot.size, len(d))
894
895      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
896
897      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
898                        (boot_type, boot_device,
899                         source_boot.size, source_boot.sha1,
900                         target_boot.size, target_boot.sha1))
901
902  device_specific.IncrementalOTA_VerifyEnd()
903
904  if OPTIONS.two_step:
905    script.WriteRawImage("/boot", "recovery.img")
906    script.AppendExtra("""
907set_stage("%(bcb_dev)s", "2/3");
908reboot_now("%(bcb_dev)s", "");
909else
910""" % bcb_dev)
911
912  # Verify the existing partitions.
913  system_diff.WriteVerifyScript(script)
914  if vendor_diff:
915    vendor_diff.WriteVerifyScript(script)
916
917  script.Comment("---- start making changes here ----")
918
919  device_specific.IncrementalOTA_InstallBegin()
920
921  system_diff.WriteScript(script, output_zip,
922                          progress=0.8 if vendor_diff else 0.9)
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.source_info_dict.get("multistage_support", None):
1223      assert False, "two-step packages not supported by this build"
1224    fs = OPTIONS.source_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(
1262        "/boot", OPTIONS.source_info_dict)
1263
1264    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1265                      (boot_type, boot_device,
1266                       source_boot.size, source_boot.sha1,
1267                       target_boot.size, target_boot.sha1))
1268    so_far += source_boot.size
1269
1270  size = []
1271  if system_diff.patch_list:
1272    size.append(system_diff.largest_source_size)
1273  if vendor_diff:
1274    if vendor_diff.patch_list:
1275      size.append(vendor_diff.largest_source_size)
1276  if size or updating_recovery or updating_boot:
1277    script.CacheFreeSpaceCheck(max(size))
1278
1279  device_specific.IncrementalOTA_VerifyEnd()
1280
1281  if OPTIONS.two_step:
1282    script.WriteRawImage("/boot", "recovery.img")
1283    script.AppendExtra("""
1284set_stage("%(bcb_dev)s", "2/3");
1285reboot_now("%(bcb_dev)s", "");
1286else
1287""" % bcb_dev)
1288
1289  script.Comment("---- start making changes here ----")
1290
1291  device_specific.IncrementalOTA_InstallBegin()
1292
1293  if OPTIONS.two_step:
1294    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1295    script.WriteRawImage("/boot", "boot.img")
1296    print "writing full boot image (forced by two-step mode)"
1297
1298  script.Print("Removing unneeded files...")
1299  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1300  if vendor_diff:
1301    vendor_diff.RemoveUnneededFiles(script)
1302
1303  script.ShowProgress(0.8, 0)
1304  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1305  if vendor_diff:
1306    total_patch_size += vendor_diff.TotalPatchSize()
1307  if updating_boot:
1308    total_patch_size += target_boot.size
1309
1310  script.Print("Patching system files...")
1311  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1312  if vendor_diff:
1313    script.Print("Patching vendor files...")
1314    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1315
1316  if not OPTIONS.two_step:
1317    if updating_boot:
1318      # Produce the boot image by applying a patch to the current
1319      # contents of the boot partition, and write it back to the
1320      # partition.
1321      script.Print("Patching boot image...")
1322      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1323                        % (boot_type, boot_device,
1324                           source_boot.size, source_boot.sha1,
1325                           target_boot.size, target_boot.sha1),
1326                        "-",
1327                        target_boot.size, target_boot.sha1,
1328                        source_boot.sha1, "patch/boot.img.p")
1329      so_far += target_boot.size
1330      script.SetProgress(so_far / total_patch_size)
1331      print "boot image changed; including."
1332    else:
1333      print "boot image unchanged; skipping."
1334
1335  system_items = ItemSet("system", "META/filesystem_config.txt")
1336  if vendor_diff:
1337    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1338
1339  if updating_recovery:
1340    # Recovery is generated as a patch using both the boot image
1341    # (which contains the same linux kernel as recovery) and the file
1342    # /system/etc/recovery-resource.dat (which contains all the images
1343    # used in the recovery UI) as sources.  This lets us minimize the
1344    # size of the patch, which must be included in every OTA package.
1345    #
1346    # For older builds where recovery-resource.dat is not present, we
1347    # use only the boot image as the source.
1348
1349    if not target_has_recovery_patch:
1350      def output_sink(fn, data):
1351        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1352        system_items.Get("system/" + fn)
1353
1354      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1355                               target_recovery, target_boot)
1356      script.DeleteFiles(["/system/recovery-from-boot.p",
1357                          "/system/etc/install-recovery.sh"])
1358    print "recovery image changed; including as patch from boot."
1359  else:
1360    print "recovery image unchanged; skipping."
1361
1362  script.ShowProgress(0.1, 10)
1363
1364  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1365  if vendor_diff:
1366    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1367
1368  temp_script = script.MakeTemporary()
1369  system_items.GetMetadata(target_zip)
1370  system_items.Get("system").SetPermissions(temp_script)
1371  if vendor_diff:
1372    vendor_items.GetMetadata(target_zip)
1373    vendor_items.Get("vendor").SetPermissions(temp_script)
1374
1375  # Note that this call will mess up the trees of Items, so make sure
1376  # we're done with them.
1377  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1378  if vendor_diff:
1379    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1380
1381  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1382  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1383
1384  # Delete all the symlinks in source that aren't in target.  This
1385  # needs to happen before verbatim files are unpacked, in case a
1386  # symlink in the source is replaced by a real file in the target.
1387
1388  # If a symlink in the source will be replaced by a regular file, we cannot
1389  # delete the symlink/file in case the package gets applied again. For such
1390  # a symlink, we prepend a sha1_check() to detect if it has been updated.
1391  # (Bug: 23646151)
1392  replaced_symlinks = dict()
1393  if system_diff:
1394    for i in system_diff.verbatim_targets:
1395      replaced_symlinks["/%s" % (i[0],)] = i[2]
1396  if vendor_diff:
1397    for i in vendor_diff.verbatim_targets:
1398      replaced_symlinks["/%s" % (i[0],)] = i[2]
1399
1400  if system_diff:
1401    for tf in system_diff.renames.values():
1402      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1403  if vendor_diff:
1404    for tf in vendor_diff.renames.values():
1405      replaced_symlinks["/%s" % (tf.name,)] = tf.sha1
1406
1407  always_delete = []
1408  may_delete = []
1409  for dest, link in source_symlinks:
1410    if link not in target_symlinks_d:
1411      if link in replaced_symlinks:
1412        may_delete.append((link, replaced_symlinks[link]))
1413      else:
1414        always_delete.append(link)
1415  script.DeleteFiles(always_delete)
1416  script.DeleteFilesIfNotMatching(may_delete)
1417
1418  if system_diff.verbatim_targets:
1419    script.Print("Unpacking new system files...")
1420    script.UnpackPackageDir("system", "/system")
1421  if vendor_diff and vendor_diff.verbatim_targets:
1422    script.Print("Unpacking new vendor files...")
1423    script.UnpackPackageDir("vendor", "/vendor")
1424
1425  if updating_recovery and not target_has_recovery_patch:
1426    script.Print("Unpacking new recovery...")
1427    script.UnpackPackageDir("recovery", "/system")
1428
1429  system_diff.EmitRenames(script)
1430  if vendor_diff:
1431    vendor_diff.EmitRenames(script)
1432
1433  script.Print("Symlinks and permissions...")
1434
1435  # Create all the symlinks that don't already exist, or point to
1436  # somewhere different than what we want.  Delete each symlink before
1437  # creating it, since the 'symlink' command won't overwrite.
1438  to_create = []
1439  for dest, link in target_symlinks:
1440    if link in source_symlinks_d:
1441      if dest != source_symlinks_d[link]:
1442        to_create.append((dest, link))
1443    else:
1444      to_create.append((dest, link))
1445  script.DeleteFiles([i[1] for i in to_create])
1446  script.MakeSymlinks(to_create)
1447
1448  # Now that the symlinks are created, we can set all the
1449  # permissions.
1450  script.AppendScript(temp_script)
1451
1452  # Do device-specific installation (eg, write radio image).
1453  device_specific.IncrementalOTA_InstallEnd()
1454
1455  if OPTIONS.extra_script is not None:
1456    script.AppendExtra(OPTIONS.extra_script)
1457
1458  # Patch the build.prop file last, so if something fails but the
1459  # device can still come up, it appears to be the old build and will
1460  # get set the OTA package again to retry.
1461  script.Print("Patching remaining system files...")
1462  system_diff.EmitDeferredPatches(script)
1463
1464  if OPTIONS.wipe_user_data:
1465    script.Print("Erasing user data...")
1466    script.FormatPartition("/data")
1467
1468  if OPTIONS.two_step:
1469    script.AppendExtra("""
1470set_stage("%(bcb_dev)s", "");
1471endif;
1472endif;
1473""" % bcb_dev)
1474
1475  if OPTIONS.verify and system_diff:
1476    script.Print("Remounting and verifying system partition files...")
1477    script.Unmount("/system")
1478    script.Mount("/system")
1479    system_diff.EmitExplicitTargetVerification(script)
1480
1481  if OPTIONS.verify and vendor_diff:
1482    script.Print("Remounting and verifying vendor partition files...")
1483    script.Unmount("/vendor")
1484    script.Mount("/vendor")
1485    vendor_diff.EmitExplicitTargetVerification(script)
1486  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1487
1488  WriteMetadata(metadata, output_zip)
1489
1490
1491def main(argv):
1492
1493  def option_handler(o, a):
1494    if o == "--board_config":
1495      pass   # deprecated
1496    elif o in ("-k", "--package_key"):
1497      OPTIONS.package_key = a
1498    elif o in ("-i", "--incremental_from"):
1499      OPTIONS.incremental_source = a
1500    elif o == "--full_radio":
1501      OPTIONS.full_radio = True
1502    elif o in ("-w", "--wipe_user_data"):
1503      OPTIONS.wipe_user_data = True
1504    elif o in ("-n", "--no_prereq"):
1505      OPTIONS.omit_prereq = True
1506    elif o in ("-o", "--oem_settings"):
1507      OPTIONS.oem_source = a
1508    elif o in ("-e", "--extra_script"):
1509      OPTIONS.extra_script = a
1510    elif o in ("-a", "--aslr_mode"):
1511      if a in ("on", "On", "true", "True", "yes", "Yes"):
1512        OPTIONS.aslr_mode = True
1513      else:
1514        OPTIONS.aslr_mode = False
1515    elif o in ("-t", "--worker_threads"):
1516      if a.isdigit():
1517        OPTIONS.worker_threads = int(a)
1518      else:
1519        raise ValueError("Cannot parse value %r for option %r - only "
1520                         "integers are allowed." % (a, o))
1521    elif o in ("-2", "--two_step"):
1522      OPTIONS.two_step = True
1523    elif o == "--no_signing":
1524      OPTIONS.no_signing = True
1525    elif o == "--verify":
1526      OPTIONS.verify = True
1527    elif o == "--block":
1528      OPTIONS.block_based = True
1529    elif o in ("-b", "--binary"):
1530      OPTIONS.updater_binary = a
1531    elif o in ("--no_fallback_to_full",):
1532      OPTIONS.fallback_to_full = False
1533    else:
1534      return False
1535    return True
1536
1537  args = common.ParseOptions(argv, __doc__,
1538                             extra_opts="b:k:i:d:wne:t:a:2o:",
1539                             extra_long_opts=[
1540                                 "board_config=",
1541                                 "package_key=",
1542                                 "incremental_from=",
1543                                 "full_radio",
1544                                 "wipe_user_data",
1545                                 "no_prereq",
1546                                 "extra_script=",
1547                                 "worker_threads=",
1548                                 "aslr_mode=",
1549                                 "two_step",
1550                                 "no_signing",
1551                                 "block",
1552                                 "binary=",
1553                                 "oem_settings=",
1554                                 "verify",
1555                                 "no_fallback_to_full",
1556                             ], extra_option_handler=option_handler)
1557
1558  if len(args) != 2:
1559    common.Usage(__doc__)
1560    sys.exit(1)
1561
1562  if OPTIONS.extra_script is not None:
1563    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1564
1565  print "unzipping target target-files..."
1566  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1567
1568  OPTIONS.target_tmp = OPTIONS.input_tmp
1569  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1570
1571  # If this image was originally labelled with SELinux contexts, make sure we
1572  # also apply the labels in our new image. During building, the "file_contexts"
1573  # is in the out/ directory tree, but for repacking from target-files.zip it's
1574  # in the root directory of the ramdisk.
1575  if "selinux_fc" in OPTIONS.info_dict:
1576    OPTIONS.info_dict["selinux_fc"] = os.path.join(
1577        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
1578
1579  if OPTIONS.verbose:
1580    print "--- target info ---"
1581    common.DumpInfoDict(OPTIONS.info_dict)
1582
1583  # If the caller explicitly specified the device-specific extensions
1584  # path via -s/--device_specific, use that.  Otherwise, use
1585  # META/releasetools.py if it is present in the target target_files.
1586  # Otherwise, take the path of the file from 'tool_extensions' in the
1587  # info dict and look for that in the local filesystem, relative to
1588  # the current directory.
1589
1590  if OPTIONS.device_specific is None:
1591    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1592    if os.path.exists(from_input):
1593      print "(using device-specific extensions from target_files)"
1594      OPTIONS.device_specific = from_input
1595    else:
1596      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1597
1598  if OPTIONS.device_specific is not None:
1599    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1600
1601  while True:
1602
1603    if OPTIONS.no_signing:
1604      if os.path.exists(args[1]):
1605        os.unlink(args[1])
1606      output_zip = zipfile.ZipFile(args[1], "w",
1607                                   compression=zipfile.ZIP_DEFLATED)
1608    else:
1609      temp_zip_file = tempfile.NamedTemporaryFile()
1610      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1611                                   compression=zipfile.ZIP_DEFLATED)
1612
1613    if OPTIONS.incremental_source is None:
1614      WriteFullOTAPackage(input_zip, output_zip)
1615      if OPTIONS.package_key is None:
1616        OPTIONS.package_key = OPTIONS.info_dict.get(
1617            "default_system_dev_certificate",
1618            "build/target/product/security/testkey")
1619      common.ZipClose(output_zip)
1620      break
1621
1622    else:
1623      print "unzipping source target-files..."
1624      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1625          OPTIONS.incremental_source)
1626      OPTIONS.target_info_dict = OPTIONS.info_dict
1627      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1628      if "selinux_fc" in OPTIONS.source_info_dict:
1629        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
1630            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
1631      if OPTIONS.package_key is None:
1632        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1633            "default_system_dev_certificate",
1634            "build/target/product/security/testkey")
1635      if OPTIONS.verbose:
1636        print "--- source info ---"
1637        common.DumpInfoDict(OPTIONS.source_info_dict)
1638      try:
1639        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1640        common.ZipClose(output_zip)
1641        break
1642      except ValueError:
1643        if not OPTIONS.fallback_to_full:
1644          raise
1645        print "--- failed to build incremental; falling back to full ---"
1646        OPTIONS.incremental_source = None
1647        common.ZipClose(output_zip)
1648
1649  if not OPTIONS.no_signing:
1650    SignOutput(temp_zip_file.name, args[1])
1651    temp_zip_file.close()
1652
1653  print "done."
1654
1655
1656if __name__ == '__main__':
1657  try:
1658    common.CloseInheritedPipes()
1659    main(sys.argv[1:])
1660  except common.ExternalError as e:
1661    print
1662    print "   ERROR: %s" % (e,)
1663    print
1664    sys.exit(1)
1665  finally:
1666    common.Cleanup()
1667