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