ota_from_target_files.py revision ff7778166bd13a90c89fa333591ee2037f587a11
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  if vendor_diff:
921    vendor_diff.WriteScript(script, output_zip, progress=0.1)
922
923  if OPTIONS.two_step:
924    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
925    script.WriteRawImage("/boot", "boot.img")
926    print "writing full boot image (forced by two-step mode)"
927
928  if not OPTIONS.two_step:
929    if updating_boot:
930      if include_full_boot:
931        print "boot image changed; including full."
932        script.Print("Installing boot image...")
933        script.WriteRawImage("/boot", "boot.img")
934      else:
935        # Produce the boot image by applying a patch to the current
936        # contents of the boot partition, and write it back to the
937        # partition.
938        print "boot image changed; including patch."
939        script.Print("Patching boot image...")
940        script.ShowProgress(0.1, 10)
941        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
942                          % (boot_type, boot_device,
943                             source_boot.size, source_boot.sha1,
944                             target_boot.size, target_boot.sha1),
945                          "-",
946                          target_boot.size, target_boot.sha1,
947                          source_boot.sha1, "patch/boot.img.p")
948    else:
949      print "boot image unchanged; skipping."
950
951  # Do device-specific installation (eg, write radio image).
952  device_specific.IncrementalOTA_InstallEnd()
953
954  if OPTIONS.extra_script is not None:
955    script.AppendExtra(OPTIONS.extra_script)
956
957  if OPTIONS.wipe_user_data:
958    script.Print("Erasing user data...")
959    script.FormatPartition("/data")
960
961  if OPTIONS.two_step:
962    script.AppendExtra("""
963set_stage("%(bcb_dev)s", "");
964endif;
965endif;
966""" % bcb_dev)
967
968  script.SetProgress(1)
969  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
970  WriteMetadata(metadata, output_zip)
971
972
973class FileDifference(object):
974  def __init__(self, partition, source_zip, target_zip, output_zip):
975    self.deferred_patch_list = None
976    print "Loading target..."
977    self.target_data = target_data = LoadPartitionFiles(target_zip, partition)
978    print "Loading source..."
979    self.source_data = source_data = LoadPartitionFiles(source_zip, partition)
980
981    self.verbatim_targets = verbatim_targets = []
982    self.patch_list = patch_list = []
983    diffs = []
984    self.renames = renames = {}
985    known_paths = set()
986    largest_source_size = 0
987
988    matching_file_cache = {}
989    for fn, sf in source_data.items():
990      assert fn == sf.name
991      matching_file_cache["path:" + fn] = sf
992      if fn in target_data.keys():
993        AddToKnownPaths(fn, known_paths)
994      # Only allow eligibility for filename/sha matching
995      # if there isn't a perfect path match.
996      if target_data.get(sf.name) is None:
997        matching_file_cache["file:" + fn.split("/")[-1]] = sf
998        matching_file_cache["sha:" + sf.sha1] = sf
999
1000    for fn in sorted(target_data.keys()):
1001      tf = target_data[fn]
1002      assert fn == tf.name
1003      sf = ClosestFileMatch(tf, matching_file_cache, renames)
1004      if sf is not None and sf.name != tf.name:
1005        print "File has moved from " + sf.name + " to " + tf.name
1006        renames[sf.name] = tf
1007
1008      if sf is None or fn in OPTIONS.require_verbatim:
1009        # This file should be included verbatim
1010        if fn in OPTIONS.prohibit_verbatim:
1011          raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
1012        print "send", fn, "verbatim"
1013        tf.AddToZip(output_zip)
1014        verbatim_targets.append((fn, tf.size, tf.sha1))
1015        if fn in target_data.keys():
1016          AddToKnownPaths(fn, known_paths)
1017      elif tf.sha1 != sf.sha1:
1018        # File is different; consider sending as a patch
1019        diffs.append(common.Difference(tf, sf))
1020      else:
1021        # Target file data identical to source (may still be renamed)
1022        pass
1023
1024    common.ComputeDifferences(diffs)
1025
1026    for diff in diffs:
1027      tf, sf, d = diff.GetPatch()
1028      path = "/".join(tf.name.split("/")[:-1])
1029      if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
1030          path not in known_paths:
1031        # patch is almost as big as the file; don't bother patching
1032        # or a patch + rename cannot take place due to the target
1033        # directory not existing
1034        tf.AddToZip(output_zip)
1035        verbatim_targets.append((tf.name, tf.size, tf.sha1))
1036        if sf.name in renames:
1037          del renames[sf.name]
1038        AddToKnownPaths(tf.name, known_paths)
1039      else:
1040        common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
1041        patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
1042        largest_source_size = max(largest_source_size, sf.size)
1043
1044    self.largest_source_size = largest_source_size
1045
1046  def EmitVerification(self, script):
1047    so_far = 0
1048    for tf, sf, _, _ in self.patch_list:
1049      if tf.name != sf.name:
1050        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1051      script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1052      so_far += sf.size
1053    return so_far
1054
1055  def EmitExplicitTargetVerification(self, script):
1056    for fn, _, sha1 in self.verbatim_targets:
1057      if fn[-1] != "/":
1058        script.FileCheck("/"+fn, sha1)
1059    for tf, _, _, _ in self.patch_list:
1060      script.FileCheck(tf.name, tf.sha1)
1061
1062  def RemoveUnneededFiles(self, script, extras=()):
1063    script.DeleteFiles(
1064        ["/" + i[0] for i in self.verbatim_targets] +
1065        ["/" + i for i in sorted(self.source_data)
1066         if i not in self.target_data and i not in self.renames] +
1067        list(extras))
1068
1069  def TotalPatchSize(self):
1070    return sum(i[1].size for i in self.patch_list)
1071
1072  def EmitPatches(self, script, total_patch_size, so_far):
1073    self.deferred_patch_list = deferred_patch_list = []
1074    for item in self.patch_list:
1075      tf, sf, _, _ = item
1076      if tf.name == "system/build.prop":
1077        deferred_patch_list.append(item)
1078        continue
1079      if sf.name != tf.name:
1080        script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1081      script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1,
1082                        "patch/" + sf.name + ".p")
1083      so_far += tf.size
1084      script.SetProgress(so_far / total_patch_size)
1085    return so_far
1086
1087  def EmitDeferredPatches(self, script):
1088    for item in self.deferred_patch_list:
1089      tf, sf, _, _ = item
1090      script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1,
1091                        "patch/" + sf.name + ".p")
1092    script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None)
1093
1094  def EmitRenames(self, script):
1095    if len(self.renames) > 0:
1096      script.Print("Renaming files...")
1097      for src, tgt in self.renames.iteritems():
1098        print "Renaming " + src + " to " + tgt.name
1099        script.RenameFile(src, tgt.name)
1100
1101
1102def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
1103  target_has_recovery_patch = HasRecoveryPatch(target_zip)
1104  source_has_recovery_patch = HasRecoveryPatch(source_zip)
1105
1106  if (OPTIONS.block_based and
1107      target_has_recovery_patch and
1108      source_has_recovery_patch):
1109    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
1110
1111  source_version = OPTIONS.source_info_dict["recovery_api_version"]
1112  target_version = OPTIONS.target_info_dict["recovery_api_version"]
1113
1114  if source_version == 0:
1115    print ("WARNING: generating edify script for a source that "
1116           "can't install it.")
1117  script = edify_generator.EdifyGenerator(source_version,
1118                                          OPTIONS.target_info_dict)
1119
1120  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
1121  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
1122  oem_dict = None
1123  if oem_props is not None and len(oem_props) > 0:
1124    if OPTIONS.oem_source is None:
1125      raise common.ExternalError("OEM source required for this build")
1126    script.Mount("/oem", recovery_mount_options)
1127    oem_dict = common.LoadDictionaryFromLines(
1128        open(OPTIONS.oem_source).readlines())
1129
1130  metadata = {
1131      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
1132                                   OPTIONS.source_info_dict),
1133      "post-timestamp": GetBuildProp("ro.build.date.utc",
1134                                     OPTIONS.target_info_dict),
1135  }
1136
1137  device_specific = common.DeviceSpecificParams(
1138      source_zip=source_zip,
1139      source_version=source_version,
1140      target_zip=target_zip,
1141      target_version=target_version,
1142      output_zip=output_zip,
1143      script=script,
1144      metadata=metadata,
1145      info_dict=OPTIONS.info_dict)
1146
1147  system_diff = FileDifference("system", source_zip, target_zip, output_zip)
1148  script.Mount("/system", recovery_mount_options)
1149  if HasVendorPartition(target_zip):
1150    vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip)
1151    script.Mount("/vendor", recovery_mount_options)
1152  else:
1153    vendor_diff = None
1154
1155  target_fp = CalculateFingerprint(oem_props, oem_dict,
1156                                   OPTIONS.target_info_dict)
1157  source_fp = CalculateFingerprint(oem_props, oem_dict,
1158                                   OPTIONS.source_info_dict)
1159
1160  if oem_props is None:
1161    script.AssertSomeFingerprint(source_fp, target_fp)
1162  else:
1163    script.AssertSomeThumbprint(
1164        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1165        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1166
1167  metadata["pre-build"] = source_fp
1168  metadata["post-build"] = target_fp
1169
1170  source_boot = common.GetBootableImage(
1171      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1172      OPTIONS.source_info_dict)
1173  target_boot = common.GetBootableImage(
1174      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1175  updating_boot = (not OPTIONS.two_step and
1176                   (source_boot.data != target_boot.data))
1177
1178  source_recovery = common.GetBootableImage(
1179      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1180      OPTIONS.source_info_dict)
1181  target_recovery = common.GetBootableImage(
1182      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1183  updating_recovery = (source_recovery.data != target_recovery.data)
1184
1185  # Here's how we divide up the progress bar:
1186  #  0.1 for verifying the start state (PatchCheck calls)
1187  #  0.8 for applying patches (ApplyPatch calls)
1188  #  0.1 for unpacking verbatim files, symlinking, and doing the
1189  #      device-specific commands.
1190
1191  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1192  device_specific.IncrementalOTA_Assertions()
1193
1194  # Two-step incremental package strategy (in chronological order,
1195  # which is *not* the order in which the generated script has
1196  # things):
1197  #
1198  # if stage is not "2/3" or "3/3":
1199  #    do verification on current system
1200  #    write recovery image to boot partition
1201  #    set stage to "2/3"
1202  #    reboot to boot partition and restart recovery
1203  # else if stage is "2/3":
1204  #    write recovery image to recovery partition
1205  #    set stage to "3/3"
1206  #    reboot to recovery partition and restart recovery
1207  # else:
1208  #    (stage must be "3/3")
1209  #    perform update:
1210  #       patch system files, etc.
1211  #       force full install of new boot image
1212  #       set up system to update recovery partition on first boot
1213  #    complete script normally
1214  #    (allow recovery to mark itself finished and reboot)
1215
1216  if OPTIONS.two_step:
1217    if not OPTIONS.info_dict.get("multistage_support", None):
1218      assert False, "two-step packages not supported by this build"
1219    fs = OPTIONS.info_dict["fstab"]["/misc"]
1220    assert fs.fs_type.upper() == "EMMC", \
1221        "two-step packages only supported on devices with EMMC /misc partitions"
1222    bcb_dev = {"bcb_dev": fs.device}
1223    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1224    script.AppendExtra("""
1225if get_stage("%(bcb_dev)s") == "2/3" then
1226""" % bcb_dev)
1227    script.AppendExtra("sleep(20);\n")
1228    script.WriteRawImage("/recovery", "recovery.img")
1229    script.AppendExtra("""
1230set_stage("%(bcb_dev)s", "3/3");
1231reboot_now("%(bcb_dev)s", "recovery");
1232else if get_stage("%(bcb_dev)s") != "3/3" then
1233""" % bcb_dev)
1234
1235  # Dump fingerprints
1236  script.Print("Source: %s" % (source_fp,))
1237  script.Print("Target: %s" % (target_fp,))
1238
1239  script.Print("Verifying current system...")
1240
1241  device_specific.IncrementalOTA_VerifyBegin()
1242
1243  script.ShowProgress(0.1, 0)
1244  so_far = system_diff.EmitVerification(script)
1245  if vendor_diff:
1246    so_far += vendor_diff.EmitVerification(script)
1247
1248  if updating_boot:
1249    d = common.Difference(target_boot, source_boot)
1250    _, _, d = d.ComputePatch()
1251    print "boot      target: %d  source: %d  diff: %d" % (
1252        target_boot.size, source_boot.size, len(d))
1253
1254    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1255
1256    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
1257
1258    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1259                      (boot_type, boot_device,
1260                       source_boot.size, source_boot.sha1,
1261                       target_boot.size, target_boot.sha1))
1262    so_far += source_boot.size
1263
1264  size = []
1265  if system_diff.patch_list:
1266    size.append(system_diff.largest_source_size)
1267  if vendor_diff:
1268    if vendor_diff.patch_list:
1269      size.append(vendor_diff.largest_source_size)
1270  if size or updating_recovery or updating_boot:
1271    script.CacheFreeSpaceCheck(max(size))
1272
1273  device_specific.IncrementalOTA_VerifyEnd()
1274
1275  if OPTIONS.two_step:
1276    script.WriteRawImage("/boot", "recovery.img")
1277    script.AppendExtra("""
1278set_stage("%(bcb_dev)s", "2/3");
1279reboot_now("%(bcb_dev)s", "");
1280else
1281""" % bcb_dev)
1282
1283  script.Comment("---- start making changes here ----")
1284
1285  device_specific.IncrementalOTA_InstallBegin()
1286
1287  if OPTIONS.two_step:
1288    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1289    script.WriteRawImage("/boot", "boot.img")
1290    print "writing full boot image (forced by two-step mode)"
1291
1292  script.Print("Removing unneeded files...")
1293  system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",))
1294  if vendor_diff:
1295    vendor_diff.RemoveUnneededFiles(script)
1296
1297  script.ShowProgress(0.8, 0)
1298  total_patch_size = 1.0 + system_diff.TotalPatchSize()
1299  if vendor_diff:
1300    total_patch_size += vendor_diff.TotalPatchSize()
1301  if updating_boot:
1302    total_patch_size += target_boot.size
1303
1304  script.Print("Patching system files...")
1305  so_far = system_diff.EmitPatches(script, total_patch_size, 0)
1306  if vendor_diff:
1307    script.Print("Patching vendor files...")
1308    so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far)
1309
1310  if not OPTIONS.two_step:
1311    if updating_boot:
1312      # Produce the boot image by applying a patch to the current
1313      # contents of the boot partition, and write it back to the
1314      # partition.
1315      script.Print("Patching boot image...")
1316      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1317                        % (boot_type, boot_device,
1318                           source_boot.size, source_boot.sha1,
1319                           target_boot.size, target_boot.sha1),
1320                        "-",
1321                        target_boot.size, target_boot.sha1,
1322                        source_boot.sha1, "patch/boot.img.p")
1323      so_far += target_boot.size
1324      script.SetProgress(so_far / total_patch_size)
1325      print "boot image changed; including."
1326    else:
1327      print "boot image unchanged; skipping."
1328
1329  system_items = ItemSet("system", "META/filesystem_config.txt")
1330  if vendor_diff:
1331    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
1332
1333  if updating_recovery:
1334    # Recovery is generated as a patch using both the boot image
1335    # (which contains the same linux kernel as recovery) and the file
1336    # /system/etc/recovery-resource.dat (which contains all the images
1337    # used in the recovery UI) as sources.  This lets us minimize the
1338    # size of the patch, which must be included in every OTA package.
1339    #
1340    # For older builds where recovery-resource.dat is not present, we
1341    # use only the boot image as the source.
1342
1343    if not target_has_recovery_patch:
1344      def output_sink(fn, data):
1345        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1346        system_items.Get("system/" + fn)
1347
1348      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1349                               target_recovery, target_boot)
1350      script.DeleteFiles(["/system/recovery-from-boot.p",
1351                          "/system/etc/install-recovery.sh"])
1352    print "recovery image changed; including as patch from boot."
1353  else:
1354    print "recovery image unchanged; skipping."
1355
1356  script.ShowProgress(0.1, 10)
1357
1358  target_symlinks = CopyPartitionFiles(system_items, target_zip, None)
1359  if vendor_diff:
1360    target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None))
1361
1362  temp_script = script.MakeTemporary()
1363  system_items.GetMetadata(target_zip)
1364  system_items.Get("system").SetPermissions(temp_script)
1365  if vendor_diff:
1366    vendor_items.GetMetadata(target_zip)
1367    vendor_items.Get("vendor").SetPermissions(temp_script)
1368
1369  # Note that this call will mess up the trees of Items, so make sure
1370  # we're done with them.
1371  source_symlinks = CopyPartitionFiles(system_items, source_zip, None)
1372  if vendor_diff:
1373    source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None))
1374
1375  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1376  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1377
1378  # Delete all the symlinks in source that aren't in target.  This
1379  # needs to happen before verbatim files are unpacked, in case a
1380  # symlink in the source is replaced by a real file in the target.
1381  to_delete = []
1382  for dest, link in source_symlinks:
1383    if link not in target_symlinks_d:
1384      to_delete.append(link)
1385  script.DeleteFiles(to_delete)
1386
1387  if system_diff.verbatim_targets:
1388    script.Print("Unpacking new system files...")
1389    script.UnpackPackageDir("system", "/system")
1390  if vendor_diff and vendor_diff.verbatim_targets:
1391    script.Print("Unpacking new vendor files...")
1392    script.UnpackPackageDir("vendor", "/vendor")
1393
1394  if updating_recovery and not target_has_recovery_patch:
1395    script.Print("Unpacking new recovery...")
1396    script.UnpackPackageDir("recovery", "/system")
1397
1398  system_diff.EmitRenames(script)
1399  if vendor_diff:
1400    vendor_diff.EmitRenames(script)
1401
1402  script.Print("Symlinks and permissions...")
1403
1404  # Create all the symlinks that don't already exist, or point to
1405  # somewhere different than what we want.  Delete each symlink before
1406  # creating it, since the 'symlink' command won't overwrite.
1407  to_create = []
1408  for dest, link in target_symlinks:
1409    if link in source_symlinks_d:
1410      if dest != source_symlinks_d[link]:
1411        to_create.append((dest, link))
1412    else:
1413      to_create.append((dest, link))
1414  script.DeleteFiles([i[1] for i in to_create])
1415  script.MakeSymlinks(to_create)
1416
1417  # Now that the symlinks are created, we can set all the
1418  # permissions.
1419  script.AppendScript(temp_script)
1420
1421  # Do device-specific installation (eg, write radio image).
1422  device_specific.IncrementalOTA_InstallEnd()
1423
1424  if OPTIONS.extra_script is not None:
1425    script.AppendExtra(OPTIONS.extra_script)
1426
1427  # Patch the build.prop file last, so if something fails but the
1428  # device can still come up, it appears to be the old build and will
1429  # get set the OTA package again to retry.
1430  script.Print("Patching remaining system files...")
1431  system_diff.EmitDeferredPatches(script)
1432
1433  if OPTIONS.wipe_user_data:
1434    script.Print("Erasing user data...")
1435    script.FormatPartition("/data")
1436
1437  if OPTIONS.two_step:
1438    script.AppendExtra("""
1439set_stage("%(bcb_dev)s", "");
1440endif;
1441endif;
1442""" % bcb_dev)
1443
1444  if OPTIONS.verify and system_diff:
1445    script.Print("Remounting and verifying system partition files...")
1446    script.Unmount("/system")
1447    script.Mount("/system")
1448    system_diff.EmitExplicitTargetVerification(script)
1449
1450  if OPTIONS.verify and vendor_diff:
1451    script.Print("Remounting and verifying vendor partition files...")
1452    script.Unmount("/vendor")
1453    script.Mount("/vendor")
1454    vendor_diff.EmitExplicitTargetVerification(script)
1455  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1456
1457  WriteMetadata(metadata, output_zip)
1458
1459
1460def main(argv):
1461
1462  def option_handler(o, a):
1463    if o == "--board_config":
1464      pass   # deprecated
1465    elif o in ("-k", "--package_key"):
1466      OPTIONS.package_key = a
1467    elif o in ("-i", "--incremental_from"):
1468      OPTIONS.incremental_source = a
1469    elif o == "--full_radio":
1470      OPTIONS.full_radio = True
1471    elif o in ("-w", "--wipe_user_data"):
1472      OPTIONS.wipe_user_data = True
1473    elif o in ("-n", "--no_prereq"):
1474      OPTIONS.omit_prereq = True
1475    elif o in ("-o", "--oem_settings"):
1476      OPTIONS.oem_source = a
1477    elif o in ("-e", "--extra_script"):
1478      OPTIONS.extra_script = a
1479    elif o in ("-a", "--aslr_mode"):
1480      if a in ("on", "On", "true", "True", "yes", "Yes"):
1481        OPTIONS.aslr_mode = True
1482      else:
1483        OPTIONS.aslr_mode = False
1484    elif o in ("-t", "--worker_threads"):
1485      if a.isdigit():
1486        OPTIONS.worker_threads = int(a)
1487      else:
1488        raise ValueError("Cannot parse value %r for option %r - only "
1489                         "integers are allowed." % (a, o))
1490    elif o in ("-2", "--two_step"):
1491      OPTIONS.two_step = True
1492    elif o == "--no_signing":
1493      OPTIONS.no_signing = True
1494    elif o == "--verify":
1495      OPTIONS.verify = True
1496    elif o == "--block":
1497      OPTIONS.block_based = True
1498    elif o in ("-b", "--binary"):
1499      OPTIONS.updater_binary = a
1500    elif o in ("--no_fallback_to_full",):
1501      OPTIONS.fallback_to_full = False
1502    else:
1503      return False
1504    return True
1505
1506  args = common.ParseOptions(argv, __doc__,
1507                             extra_opts="b:k:i:d:wne:t:a:2o:",
1508                             extra_long_opts=[
1509                                 "board_config=",
1510                                 "package_key=",
1511                                 "incremental_from=",
1512                                 "full_radio",
1513                                 "wipe_user_data",
1514                                 "no_prereq",
1515                                 "extra_script=",
1516                                 "worker_threads=",
1517                                 "aslr_mode=",
1518                                 "two_step",
1519                                 "no_signing",
1520                                 "block",
1521                                 "binary=",
1522                                 "oem_settings=",
1523                                 "verify",
1524                                 "no_fallback_to_full",
1525                             ], extra_option_handler=option_handler)
1526
1527  if len(args) != 2:
1528    common.Usage(__doc__)
1529    sys.exit(1)
1530
1531  if OPTIONS.extra_script is not None:
1532    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1533
1534  print "unzipping target target-files..."
1535  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1536
1537  OPTIONS.target_tmp = OPTIONS.input_tmp
1538  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1539
1540  # If this image was originally labelled with SELinux contexts, make sure we
1541  # also apply the labels in our new image. During building, the "file_contexts"
1542  # is in the out/ directory tree, but for repacking from target-files.zip it's
1543  # in the root directory of the ramdisk.
1544  if "selinux_fc" in OPTIONS.info_dict:
1545    OPTIONS.info_dict["selinux_fc"] = os.path.join(
1546        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
1547
1548  if OPTIONS.verbose:
1549    print "--- target info ---"
1550    common.DumpInfoDict(OPTIONS.info_dict)
1551
1552  # If the caller explicitly specified the device-specific extensions
1553  # path via -s/--device_specific, use that.  Otherwise, use
1554  # META/releasetools.py if it is present in the target target_files.
1555  # Otherwise, take the path of the file from 'tool_extensions' in the
1556  # info dict and look for that in the local filesystem, relative to
1557  # the current directory.
1558
1559  if OPTIONS.device_specific is None:
1560    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1561    if os.path.exists(from_input):
1562      print "(using device-specific extensions from target_files)"
1563      OPTIONS.device_specific = from_input
1564    else:
1565      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1566
1567  if OPTIONS.device_specific is not None:
1568    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1569
1570  while True:
1571
1572    if OPTIONS.no_signing:
1573      if os.path.exists(args[1]):
1574        os.unlink(args[1])
1575      output_zip = zipfile.ZipFile(args[1], "w",
1576                                   compression=zipfile.ZIP_DEFLATED)
1577    else:
1578      temp_zip_file = tempfile.NamedTemporaryFile()
1579      output_zip = zipfile.ZipFile(temp_zip_file, "w",
1580                                   compression=zipfile.ZIP_DEFLATED)
1581
1582    if OPTIONS.incremental_source is None:
1583      WriteFullOTAPackage(input_zip, output_zip)
1584      if OPTIONS.package_key is None:
1585        OPTIONS.package_key = OPTIONS.info_dict.get(
1586            "default_system_dev_certificate",
1587            "build/target/product/security/testkey")
1588      common.ZipClose(output_zip)
1589      break
1590
1591    else:
1592      print "unzipping source target-files..."
1593      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1594          OPTIONS.incremental_source)
1595      OPTIONS.target_info_dict = OPTIONS.info_dict
1596      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1597      if "selinux_fc" in OPTIONS.source_info_dict:
1598        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
1599            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
1600      if OPTIONS.package_key is None:
1601        OPTIONS.package_key = OPTIONS.source_info_dict.get(
1602            "default_system_dev_certificate",
1603            "build/target/product/security/testkey")
1604      if OPTIONS.verbose:
1605        print "--- source info ---"
1606        common.DumpInfoDict(OPTIONS.source_info_dict)
1607      try:
1608        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1609        common.ZipClose(output_zip)
1610        break
1611      except ValueError:
1612        if not OPTIONS.fallback_to_full:
1613          raise
1614        print "--- failed to build incremental; falling back to full ---"
1615        OPTIONS.incremental_source = None
1616        common.ZipClose(output_zip)
1617
1618  if not OPTIONS.no_signing:
1619    SignOutput(temp_zip_file.name, args[1])
1620    temp_zip_file.close()
1621
1622  print "done."
1623
1624
1625if __name__ == '__main__':
1626  try:
1627    common.CloseInheritedPipes()
1628    main(sys.argv[1:])
1629  except common.ExternalError as e:
1630    print
1631    print "   ERROR: %s" % (e,)
1632    print
1633    sys.exit(1)
1634  finally:
1635    common.Cleanup()
1636