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