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