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