ota_from_target_files.py revision 951495fc4802a3603f654c02c7acceda4859f5e1
1c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#!/usr/bin/env python
2c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
3c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Copyright (C) 2008 The Android Open Source Project
4c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
5c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Licensed under the Apache License, Version 2.0 (the "License");
6c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# you may not use this file except in compliance with the License.
7c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# You may obtain a copy of the License at
8c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
9c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#      http://www.apache.org/licenses/LICENSE-2.0
10c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
11c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Unless required by applicable law or agreed to in writing, software
12c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# distributed under the License is distributed on an "AS IS" BASIS,
13c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# See the License for the specific language governing permissions and
15c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# limitations under the License.
1640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
1740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu"""
1840daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuGiven a target-files zipfile, produces an OTA package that installs
1940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuthat build.  An incremental OTA is produced if -i is given, otherwise
2022f1fbc11b69ee41af8370ec38f1b90577db6c3cBen Chana full OTA is produced.
2122f1fbc11b69ee41af8370ec38f1b90577db6c3cBen Chan
2240daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuUsage:  ota_from_target_files [flags] input_target_files output_ota_package
2340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
2440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  -b  (--board_config)  <file>
2540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      Deprecated.
2640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
27ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  -k  (--package_key)  <key>
283e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      Key to use to sign the package (default is
2940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      "build/target/product/security/testkey").
3040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
3140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  -i  (--incremental_from)  <file>
3240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      Generate an incremental OTA using the given target-files zip as
3340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      the starting build.
34a41ab517725d036b63420f8445550246f8f50b99Alex Vakulenko
3540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  -w  (--wipe_user_data)
363e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      Generate an OTA package that will wipe the user data partition
37ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      when installed.
3840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
398d6b59704591ba9fad57751858835dc332dbdd37Peter Qiu  -n  (--no_prereq)
40a92b0a0438803ebb3432d7b42672fbb7fb6bccf4Peter Qiu      Omit the timestamp prereq check normally included at the top of
4140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      the build scripts (used for developer OTA packages which
4240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      legitimately need to go back and forth).
4340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
44ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  -e  (--extra_script)  <file>
4540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      Insert the contents of file at the end of the update script.
4640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
4740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  -m  (--script_mode)  <mode>
48ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      Specify 'amend' or 'edify' scripts, or 'auto' to pick
4940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      automatically (this is the default).
5040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
5140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu"""
5240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
5340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport sys
5440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
5540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuif sys.hexversion < 0x02040000:
56ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  print >> sys.stderr, "Python 2.4 or newer is required."
5740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  sys.exit(1)
5840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
5940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport copy
6040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport os
6140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport re
6240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport sha
6340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport subprocess
6440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport tempfile
6540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport time
6640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport zipfile
6740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
6840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport common
6940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport amend_generator
7040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport edify_generator
7140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuimport both_generator
7240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
738a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex VakulenkoOPTIONS = common.OPTIONS
748a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex VakulenkoOPTIONS.package_key = "build/target/product/security/testkey"
758a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex VakulenkoOPTIONS.incremental_source = None
7640daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.require_verbatim = set()
7740daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.prohibit_verbatim = set(("system/build.prop",))
7840daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.patch_threshold = 0.95
7940daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.wipe_user_data = False
8040daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.omit_prereq = False
8140daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.extra_script = None
8240daa01c4c8d772889d9c4167dee312869152174Prathmesh PrabhuOPTIONS.script_mode = 'auto'
8340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
8440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef MostPopularKey(d, default):
85cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan  """Given a dict, return the key corresponding to the largest
863e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  value.  Returns 'default' if the dict is empty."""
87cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan  x = [(v, k) for (k, v) in d.iteritems()]
8840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if not x: return default
8940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  x.sort()
903b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  return x[-1][1]
9140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
9240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
9340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef IsSymlink(info):
9440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  """Return true if the zipfile.ZipInfo object passed in represents a
9540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  symlink."""
9640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  return (info.external_attr >> 16) == 0120777
9740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
9840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
9940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
10040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhuclass Item:
10140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  """Items represent the metadata (user, group, mode) of files and
1023b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  directories in the system image."""
1033b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  ITEMS = {}
10440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  def __init__(self, name, dir=False):
10540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.name = name
10640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.uid = None
10740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.gid = None
10840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.mode = None
10940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.dir = dir
11040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
11140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if name:
11240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      self.parent = Item.Get(os.path.dirname(name), dir=True)
11340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      self.parent.children.append(self)
11440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    else:
11540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      self.parent = None
11640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if dir:
11740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      self.children = []
11840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
1193e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  def Dump(self, indent=0):
1203e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    if self.uid is not None:
1213e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
1223e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    else:
1233e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
1243e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    if self.dir:
1253b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart      print "%s%s" % ("  "*indent, self.descendants)
1263e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      print "%s%s" % ("  "*indent, self.best_subtree)
1273e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      for i in self.children:
1283e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu        i.Dump(indent=indent+1)
1293e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
13040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  @classmethod
13140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  def Get(cls, name, dir=False):
13240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if name not in cls.ITEMS:
13340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      cls.ITEMS[name] = Item(name, dir=dir)
1343e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    return cls.ITEMS[name]
1353e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
136ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  @classmethod
137ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  def GetMetadata(cls):
13840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    """Run the external 'fs_config' program to determine the desired
13940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    uid, gid, and mode for every Item object."""
14040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    p = common.Run(["fs_config"], stdin=subprocess.PIPE,
14140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    suffix = { False: "", True: "/" }
14340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
14440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                     for i in cls.ITEMS.itervalues() if i.name])
145ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    output, error = p.communicate(input)
14640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    assert not error
14740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
14840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    for line in output.split("\n"):
14940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if not line: continue
15040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      name, uid, gid, mode = line.split()
15140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      i = cls.ITEMS[name]
15240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      i.uid = int(uid)
1533e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      i.gid = int(gid)
15440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      i.mode = int(mode, 8)
15540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if i.dir:
15640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        i.children.sort(key=lambda i: i.name)
157ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
15840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  def CountChildMetadata(self):
15940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    """Count up the (uid, gid, mode) tuples for all children and
16040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    determine the best strategy for using set_perm_recursive and
1613b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart    set_perm to correctly chown/chmod all the files to their desired
16240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    values.  Recursively calls itself for all descendants.
16340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
1643b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart    Returns a dict of {(uid, gid, dmode, fmode): count} counting up
1653e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    all descendants of this node.  (dmode or fmode may be None.)  Also
1663e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    sets the best_subtree of each directory Item to the (uid, gid,
1673b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart    dmode, fmode) tuple that will match the most descendants of that
1683e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    Item.
1693e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    """
1703e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
1713e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    assert self.dir
1723e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
173ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    for i in self.children:
174ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      if i.dir:
175ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu        for k, v in i.CountChildMetadata().iteritems():
176ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu          d[k] = d.get(k, 0) + v
177ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      else:
178ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu        k = (i.uid, i.gid, None, i.mode)
179ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu        d[k] = d.get(k, 0) + 1
180ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
181ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # Find the (uid, gid, dmode, fmode) tuple that matches the most
182ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # descendants.
183ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
184ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # First, find the (uid, gid) pair that matches the most
18581404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    # descendants.
18681404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    ug = {}
18781404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    for (uid, gid, _, _), count in d.iteritems():
18840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
18940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    ug = MostPopularKey(ug, (0, 0))
19040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
19140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    # Now find the dmode and fmode that match the most descendants
19240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    # with that (uid, gid), and choose those.
19340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    best_dmode = (0, 0755)
1943b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart    best_fmode = (0, 0644)
19540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    for k, count in d.iteritems():
19640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if k[:2] != ug: continue
19740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
19840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
19940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.best_subtree = ug + (best_dmode[1], best_fmode[1])
20040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
20140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    return d
20240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
20340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  def SetPermissions(self, script):
20440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    """Append set_perm/set_perm_recursive commands to 'script' to
20540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    set all permissions, users, and groups for the tree of files
20640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    rooted at 'self'."""
20740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
20840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    self.CountChildMetadata()
20940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
21040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    def recurse(item, current):
21140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # current is the (uid, gid, dmode, fmode) tuple that the current
21240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # item (and all its children) have already been set to.  We only
21340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # need to issue set_perm/set_perm_recursive commands if we're
21440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # supposed to be something different.
21540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if item.dir:
21640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        if current != item.best_subtree:
21740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
21840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          current = item.best_subtree
21940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
22040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        if item.uid != current[0] or item.gid != current[1] or \
22140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu           item.mode != current[2]:
22240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
22340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
22440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        for i in item.children:
22540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          recurse(i, current)
22640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      else:
227ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu        if item.uid != current[0] or item.gid != current[1] or \
2287fab89734d88724a288e96a9996b15548c5294c7Ben Chan               item.mode != current[3]:
22940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
23040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
23140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    recurse(self, (-1, -1, -1, -1))
23240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
23340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
23440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef CopySystemFiles(input_zip, output_zip=None,
23540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                    substitute=None):
23640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  """Copies files underneath system/ in the input zip to the output
237ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  zip.  Populates the Item class with their metadata, and returns a
23840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  list of symlinks.  output_zip may be None, in which case the copy is
23940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  skipped (but the other side effects still happen).  substitute is an
240ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  optional dict of {output filename: contents} to be output instead of
24140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  certain input files.
24240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  """
24340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
24440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  symlinks = []
24540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
246cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan  for info in input_zip.infolist():
24740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if info.filename.startswith("SYSTEM/"):
248cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan      basefilename = info.filename[7:]
24940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if IsSymlink(info):
25040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        symlinks.append((input_zip.read(info.filename),
251cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan                         "/system/" + basefilename))
25240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      else:
25340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        info2 = copy.copy(info)
254ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu        fn = info2.filename = "system/" + basefilename
25540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        if substitute and fn in substitute and substitute[fn] is None:
25640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          continue
25740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        if output_zip is not None:
25840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          if substitute and fn in substitute:
25940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu            data = substitute[fn]
26040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          else:
26140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu            data = input_zip.read(info.filename)
26240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          output_zip.writestr(info2, data)
26340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        if fn.endswith("/"):
26440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          Item.Get(fn[:-1], dir=True)
26540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        else:
26640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu          Item.Get(fn, dir=False)
26740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
26840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  symlinks.sort()
26940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  return symlinks
27040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
27140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
272ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhudef SignOutput(temp_zip_name, output_zip_name):
273ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
274ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  pw = key_passwords[OPTIONS.package_key]
27540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
27640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
27740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                  whole_file=True)
27840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
27940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
28040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef AppendAssertions(script, input_zip):
28140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  device = GetBuildProp("ro.product.device", input_zip)
28240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.AssertDevice(device)
28340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
28440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  info = input_zip.read("OTA/android-info.txt")
28540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
28640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if m:
28740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    bootloaders = m.group(1).split("|")
28840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if "*" not in bootloaders:
28940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      script.AssertSomeBootloader(*bootloaders)
29040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
29140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
292ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhudef MakeRecoveryPatch(output_zip, recovery_img, boot_img):
293ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  """Generate a binary patch that creates the recovery image starting
294ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  with the boot image.  (Most of the space in these images is just the
295ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  kernel, which is identical for the two, so the resulting patch
29640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  should be efficient.)  Add it to the output zip, along with a shell
29740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script that is run from init.rc on first boot to actually do the
29840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  patching and install the new recovery image.
29940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
30040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  recovery_img and boot_img should be File objects for the
30140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  corresponding images.
30240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
30340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  Returns an Item for the shell script, which must be made
304ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  executable.
305ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  """
306ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
307ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  patch = Difference(recovery_img, boot_img, "imgdiff")
308ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
309ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  Item.Get("system/recovery-from-boot.p", dir=False)
310ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
31140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # Images with different content will have a different first page, so
31240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # we check to see if this recovery has already been installed by
31340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # testing just the first 2k.
31440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  HEADER_SIZE = 2048
31540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
31640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  sh = """#!/system/bin/sh
317e2ee5e009081a392fb31596c1d0cfb30bc7bad6eBen Chanif ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
318a92b0a0438803ebb3432d7b42672fbb7fb6bccf4Peter Qiu  log -t recovery "Installing new recovery image"
319ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
3203b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewartelse
3213b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  log -t recovery "Recovery image already installed"
3223b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewartfi
3233e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu""" % { 'boot_size': boot_img.size,
32440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        'boot_sha1': boot_img.sha1,
32540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        'header_size': HEADER_SIZE,
32640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        'header_sha1': header_sha1,
32722f1fbc11b69ee41af8370ec38f1b90577db6c3cBen Chan        'recovery_size': recovery_img.size,
32840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        'recovery_sha1': recovery_img.sha1 }
32940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
33040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  return Item.Get("system/etc/install-recovery.sh", dir=False)
33140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
33240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
33340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef WriteFullOTAPackage(input_zip, output_zip):
334ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  if OPTIONS.script_mode == "auto":
335e2ee5e009081a392fb31596c1d0cfb30bc7bad6eBen Chan    script = both_generator.BothGenerator(2)
336e2ee5e009081a392fb31596c1d0cfb30bc7bad6eBen Chan  elif OPTIONS.script_mode == "amend":
337ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    script = amend_generator.AmendGenerator()
338ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  else:
339ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    # TODO: how to determine this?  We don't know what version it will
340ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    # be installed on top of.  For now, we expect the API just won't
341ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    # change very often.
34241a071e432b09ab1945f5c9ae74c998726142810Paul Stewart    script = edify_generator.EdifyGenerator(2)
343ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
344ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  device_specific = common.DeviceSpecificParams(
345ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      input_zip=input_zip,
346ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      output_zip=output_zip,
347ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      script=script,
348ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      input_tmp=OPTIONS.input_tmp)
349ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
350ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  if not OPTIONS.omit_prereq:
351ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    ts = GetBuildProp("ro.build.date.utc", input_zip)
352ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    script.AssertOlderBuild(ts)
353ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
354cc225ef3b77b5e098cc12c661a947e1737480777Ben Chan  AppendAssertions(script, input_zip)
35541a071e432b09ab1945f5c9ae74c998726142810Paul Stewart  device_specific.FullOTA_Assertions()
35641a071e432b09ab1945f5c9ae74c998726142810Paul Stewart
3573e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.ShowProgress(0.5, 0)
3583e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3593e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  if OPTIONS.wipe_user_data:
3603e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    script.FormatPartition("userdata")
3613e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3623e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.FormatPartition("system")
3633b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  script.Mount("MTD", "system", "/system")
3643e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.UnpackPackageDir("system", "/system")
3653e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3663e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  symlinks = CopySystemFiles(input_zip, output_zip)
3673e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.MakeSymlinks(symlinks)
3683e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3693e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  boot_img = File("boot.img", common.BuildBootableImage(
3703e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      os.path.join(OPTIONS.input_tmp, "BOOT")))
3713e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  recovery_img = File("recovery.img", common.BuildBootableImage(
3723b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart      os.path.join(OPTIONS.input_tmp, "RECOVERY")))
3733e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
3743e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3753e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  Item.GetMetadata()
3763e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3773e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  # GetMetadata uses the data in android_filesystem_config.h to assign
37840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # the uid/gid/mode of all files.  We want to override that for the
3793e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  # recovery patching shell script to make it executable.
3803e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  i.uid = 0
3813e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  i.gid = 0
3823e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  i.mode = 0544
3833e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  Item.Get("system").SetPermissions(script)
3843e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3853e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  common.CheckSize(boot_img.data, "boot.img")
3863e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
38740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.ShowProgress(0.2, 0)
3883e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3893e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.ShowProgress(0.2, 10)
3903e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.WriteRawImage("boot", "boot.img")
3913e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3923e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.ShowProgress(0.1, 0)
3933e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  device_specific.FullOTA_InstallEnd()
3943e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
3953b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  if OPTIONS.extra_script is not None:
3963e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    script.AppendExtra(OPTIONS.extra_script)
3973b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart
3983e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  script.AddToZip(input_zip, output_zip)
3993e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4003e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4013b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewartclass File(object):
4023e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  def __init__(self, name, data):
4033e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    self.name = name
4043e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    self.data = data
4053e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    self.size = len(data)
4063e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    self.sha1 = sha.sha(data).hexdigest()
4073e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4088a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko  def WriteToTemp(self):
4093e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    t = tempfile.NamedTemporaryFile()
4103e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    t.write(self.data)
4113e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    t.flush()
412ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    return t
41340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
4143e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  def AddToZip(self, z):
415ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    common.ZipWriteStr(z, self.name, self.data)
4163e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4173e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4183e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhudef LoadSystemFiles(z):
4193e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  """Load all the files from SYSTEM/... in a given target-files
4203e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  ZipFile, and return a dict of {filename: File object}."""
4213b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart  out = {}
4223e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  for info in z.infolist():
4233e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
4243e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      fn = "system/" + info.filename[7:]
4253e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      data = z.read(info.filename)
4263e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      out[fn] = File(fn, data)
4273e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  return out
4288a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko
4293e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4303e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhudef Difference(tf, sf, diff_program):
4313e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  """Return the patch (as a string of data) needed to turn sf into tf.
432ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  diff_program is the name of an external program (or list, if
43340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  additional arguments are desired) to run to generate the diff.
4348a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko  """
4353e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4363e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  ttemp = tf.WriteToTemp()
4373e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  stemp = sf.WriteToTemp()
4383e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
439ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  ext = os.path.splitext(tf.name)[1]
4403e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4413e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  try:
4423e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    ptemp = tempfile.NamedTemporaryFile()
4433e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    if isinstance(diff_program, list):
4443e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      cmd = copy.copy(diff_program)
4453b30ca58d13cf66b75ba0729b222ddc42ae68b33Paul Stewart    else:
4463e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      cmd = [diff_program]
4473e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    cmd.append(stemp.name)
4483e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    cmd.append(ttemp.name)
4493e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    cmd.append(ptemp.name)
4503e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    p = common.Run(cmd)
4513e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    _, err = p.communicate()
4528a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko    if err or p.returncode != 0:
4533e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
4543e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu      return None
4553e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    diff = ptemp.read()
456ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  finally:
457ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    ptemp.close()
4583e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    stemp.close()
4593e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu    ttemp.close()
4603e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4613e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  return diff
4623e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
4633e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu
464ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhudef GetBuildProp(property, z):
4653e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  """Return the fingerprint of the build of a given target-files
4663e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  ZipFile object."""
46740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  bp = z.read("SYSTEM/build.prop")
46840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if not property:
46940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    return bp
47040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  m = re.search(re.escape(property) + r"=(.*)\n", bp)
47140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if not m:
47240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
47340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  return m.group(1).strip()
4748a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko
47540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
47640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef GetRecoveryAPIVersion(zip):
47740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  """Returns the version of the recovery API.  Version 0 is the older
47840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  amend code (no separate binary)."""
47940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  try:
4808a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko    version = zip.read("META/recovery-api-version.txt")
48140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    return int(version)
48240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  except KeyError:
48340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    try:
48440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # version one didn't have the recovery-api-version.txt file, but
48540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # it did include an updater binary.
48640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      zip.getinfo("OTA/bin/updater")
48740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      return 1
48840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    except KeyError:
48940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      return 0
49040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
49140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhudef WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
49240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  source_version = GetRecoveryAPIVersion(source_zip)
49340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
49440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if OPTIONS.script_mode == 'amend':
49540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    script = amend_generator.AmendGenerator()
49640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  elif OPTIONS.script_mode == 'edify':
49740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if source_version == 0:
49840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      print ("WARNING: generating edify script for a source that "
49940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu             "can't install it.")
50040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    script = edify_generator.EdifyGenerator(source_version)
50140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  elif OPTIONS.script_mode == 'auto':
50240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if source_version > 0:
50340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      script = edify_generator.EdifyGenerator(source_version)
50440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    else:
50540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      script = amend_generator.AmendGenerator()
50640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  else:
50740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
50840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
50940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  device_specific = common.DeviceSpecificParams(
51040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      source_zip=source_zip,
51140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      target_zip=target_zip,
51240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      output_zip=output_zip,
51340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      script=script)
51440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
51540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  print "Loading target..."
51640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  target_data = LoadSystemFiles(target_zip)
51740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  print "Loading source..."
51840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  source_data = LoadSystemFiles(source_zip)
51940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
52040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  verbatim_targets = []
52140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  patch_list = []
52240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  largest_source_size = 0
52340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  for fn in sorted(target_data.keys()):
52440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    tf = target_data[fn]
52540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    sf = source_data.get(fn, None)
52640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
52740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    if sf is None or fn in OPTIONS.require_verbatim:
52840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # This file should be included verbatim
52940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if fn in OPTIONS.prohibit_verbatim:
53040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
53140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      print "send", fn, "verbatim"
53240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      tf.AddToZip(output_zip)
53340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      verbatim_targets.append((fn, tf.size))
53440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    elif tf.sha1 != sf.sha1:
53540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # File is different; consider sending as a patch
53640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      diff_method = "bsdiff"
53740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if tf.name.endswith(".gz"):
53840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        diff_method = "imgdiff"
53940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      d = Difference(tf, sf, diff_method)
54040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if d is not None:
54140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        print fn, tf.size, len(d), (float(len(d)) / tf.size)
54240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
54340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        # patch is almost as big as the file; don't bother patching
54440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        tf.AddToZip(output_zip)
54540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        verbatim_targets.append((fn, tf.size))
54640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      else:
54740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
54840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        patch_list.append((fn, tf, sf, tf.size))
54940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu        largest_source_size = max(largest_source_size, sf.size)
55040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    else:
55140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      # Target file identical to source.
55240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      pass
55340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
55440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  total_verbatim_size = sum([i[1] for i in verbatim_targets])
55540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  total_patched_size = sum([i[3] for i in patch_list])
55640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
55740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
55840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
55940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
56040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.Mount("MTD", "system", "/system")
56140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.AssertSomeFingerprint(source_fp, target_fp)
56240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
56340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  source_boot = File("/tmp/boot.img",
56440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                     common.BuildBootableImage(
56540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      os.path.join(OPTIONS.source_tmp, "BOOT")))
566ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  target_boot = File("/tmp/boot.img",
567ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                     common.BuildBootableImage(
568ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      os.path.join(OPTIONS.target_tmp, "BOOT")))
569ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  updating_boot = (source_boot.data != target_boot.data)
57040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
571ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  source_recovery = File("system/recovery.img",
572ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                         common.BuildBootableImage(
573ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      os.path.join(OPTIONS.source_tmp, "RECOVERY")))
574ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  target_recovery = File("system/recovery.img",
575ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                         common.BuildBootableImage(
576ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      os.path.join(OPTIONS.target_tmp, "RECOVERY")))
577ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  updating_recovery = (source_recovery.data != target_recovery.data)
578ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
57940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # We reserve the last 0.3 of the progress bar for the
580ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # device-specific IncrementalOTA_InstallEnd() call at the end, which
581ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # will typically install a radio image.
582ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  progress_bar_total = 0.7
583ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  if updating_boot:
584ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    progress_bar_total -= 0.1
585ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
586ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  AppendAssertions(script, target_zip)
587ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  device_specific.IncrementalOTA_Assertions()
588ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
58940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.Print("Verifying current system...")
590ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
591ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  pb_verify = progress_bar_total * 0.3 * \
592ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu              (total_patched_size /
593ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu               float(total_patched_size+total_verbatim_size+1))
594ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
595ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  for i, (fn, tf, sf, size) in enumerate(patch_list):
596ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    if i % 5 == 0:
597ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      next_sizes = sum([i[3] for i in patch_list[i:i+5]])
59840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
5998a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko
600ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
601ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
60240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if updating_boot:
603ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    d = Difference(target_boot, source_boot, "imgdiff")
604ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    print "boot      target: %d  source: %d  diff: %d" % (
6058a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko        target_boot.size, source_boot.size, len(d))
606ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
607ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
6088a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko
609ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
610ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                      (source_boot.size, source_boot.sha1,
611ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                       target_boot.size, target_boot.sha1))
61240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
61340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if patch_list or updating_recovery or updating_boot:
614ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.CacheFreeSpaceCheck(largest_source_size)
615ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.Print("Unpacking patches...")
616ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.UnpackPackageDir("patch", "/tmp/patchtmp")
617ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
618ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  device_specific.IncrementalOTA_VerifyEnd()
619ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
620ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  script.Comment("---- start making changes here ----")
621ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
622ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  if OPTIONS.wipe_user_data:
623ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.Print("Erasing user data...")
624ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.FormatPartition("userdata")
625ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
62640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  script.Print("Removing unneeded files...")
627ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
628ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                     ["/"+i for i in sorted(source_data)
629ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                            if i not in target_data])
63040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
631ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  if updating_boot:
632ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # Produce the boot image by applying a patch to the current
633ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # contents of the boot partition, and write it back to the
634ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # partition.
635ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.Print("Patching boot image...")
636ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
637ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                      % (source_boot.size, source_boot.sha1,
638ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                         target_boot.size, target_boot.sha1),
639ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                      "-",
64040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                      target_boot.size, target_boot.sha1,
641ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                      source_boot.sha1, "/tmp/patchtmp/boot.img.p")
642ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    print "boot image changed; including."
643ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  else:
64440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    print "boot image unchanged; skipping."
64540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
646ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  if updating_recovery:
647ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # Is it better to generate recovery as a patch from the current
648ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # boot image, or from the previous recovery image?  For large
649ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # updates with significant kernel changes, probably the former.
650ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # For small updates where the kernel hasn't changed, almost
651ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # certainly the latter.  We pick the first option.  Future
652ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # complicated schemes may let us effectively use both.
653ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    #
654ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # A wacky possibility: as long as there is room in the boot
65540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    # partition, include the binaries and image files from recovery in
656ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # the boot image (though not in the ramdisk) so they can be used
657ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    # as fodder for constructing the recovery image.
658ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    recovery_sh_item = MakeRecoveryPatch(output_zip,
659ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                                         target_recovery, target_boot)
660ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    print "recovery image changed; including as patch from boot."
66140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  else:
662ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    print "recovery image unchanged; skipping."
663ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
664ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  script.Print("Patching system files...")
665ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  pb_apply = progress_bar_total * 0.7 * \
666ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu             (total_patched_size /
667ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu              float(total_patched_size+total_verbatim_size+1))
668ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  for i, (fn, tf, sf, size) in enumerate(patch_list):
669ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    if i % 5 == 0:
670ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      next_sizes = sum([i[3] for i in patch_list[i:i+5]])
671ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu      script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
67240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
673ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                      sf.sha1, "/tmp/patchtmp/"+fn+".p")
674ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
675ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  target_symlinks = CopySystemFiles(target_zip, None)
676ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
677ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
678ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  temp_script = script.MakeTemporary()
679ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  Item.GetMetadata()
68040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if updating_recovery:
681ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    recovery_sh_item.uid = 0
682ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    recovery_sh_item.gid = 0
683ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    recovery_sh_item.mode = 0544
684ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  Item.Get("system").SetPermissions(temp_script)
685ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
686ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # Note that this call will mess up the tree of Items, so make sure
687ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # we're done with it.
688ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  source_symlinks = CopySystemFiles(source_zip, None)
689ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
690ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
691ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # Delete all the symlinks in source that aren't in target.  This
692ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  # needs to happen before verbatim files are unpacked, in case a
69340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  # symlink in the source is replaced by a real file in the target.
694ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  to_delete = []
695ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  for dest, link in source_symlinks:
696ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    if link not in target_symlinks_d:
69740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      to_delete.append(link)
698ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  script.DeleteFiles(to_delete)
699ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
700ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  if verbatim_targets:
701ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    pb_verbatim = progress_bar_total * \
702ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                  (total_verbatim_size /
703ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu                   float(total_patched_size+total_verbatim_size+1))
704ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.ShowProgress(pb_verbatim, 5)
705ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.Print("Unpacking new files...")
706ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    script.UnpackPackageDir("system", "/system")
707ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
708ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  script.Print("Symlinks and permissions...")
709ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu
71081404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # Create all the symlinks that don't already exist, or point to
71181404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # somewhere different than what we want.  Delete each symlink before
71281404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # creating it, since the 'symlink' command won't overwrite.
71381404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  to_create = []
71481404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  for dest, link in target_symlinks:
71581404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    if link in source_symlinks_d:
71681404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu      if dest != source_symlinks_d[link]:
71781404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu        to_create.append((dest, link))
71881404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    else:
71981404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu      to_create.append((dest, link))
72081404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  script.DeleteFiles([i[1] for i in to_create])
72181404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  script.MakeSymlinks(to_create)
72281404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
72381404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # Now that the symlinks are created, we can set all the
72481404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # permissions.
72581404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  script.AppendScript(temp_script)
72681404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
72781404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  # Write the radio image, if necessary.
72881404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  script.ShowProgress(0.3, 10)
72981404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  device_specific.IncrementalOTA_InstallEnd()
73081404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
73181404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  if OPTIONS.extra_script is not None:
73281404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    scirpt.AppendExtra(OPTIONS.extra_script)
73381404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
73481404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  script.AddToZip(target_zip, output_zip)
73581404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
73681404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
73781404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhudef main(argv):
73881404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu
73981404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu  def option_handler(o, a):
74081404c674c82f0ac2c958715b451b84521148b7ePrathmesh Prabhu    if o in ("-b", "--board_config"):
74140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      pass   # deprecated
74240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    elif o in ("-k", "--package_key"):
74340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      OPTIONS.package_key = a
74440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    elif o in ("-i", "--incremental_from"):
74540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      OPTIONS.incremental_source = a
7468a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko    elif o in ("-w", "--wipe_user_data"):
74740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      OPTIONS.wipe_user_data = True
748ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    elif o in ("-n", "--no_prereq"):
749ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu      OPTIONS.omit_prereq = True
750ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    elif o in ("-e", "--extra_script"):
75140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      OPTIONS.extra_script = a
75240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    elif o in ("-m", "--script_mode"):
75340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      OPTIONS.script_mode = a
75440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    else:
75540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu      return False
75640daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    return True
75740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
75840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  args = common.ParseOptions(argv, __doc__,
7598a5322984f2d81bcbfd8d44c59747a11bd9b904bAlex Vakulenko                             extra_opts="b:k:i:d:wne:m:",
76040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                             extra_long_opts=["board_config=",
761ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                                              "package_key=",
76240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                                              "incremental_from=",
76340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                                              "wipe_user_data",
764ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                                              "no_prereq",
765ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                                              "extra_script=",
766ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                                              "script_mode="],
76740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                             extra_option_handler=option_handler)
76840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
76940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if len(args) != 2:
77040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    common.Usage(__doc__)
77140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    sys.exit(1)
77240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
77340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if OPTIONS.script_mode not in ("amend", "edify", "auto"):
77440daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
775ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
776ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu  if OPTIONS.extra_script is not None:
77740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
778ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
77940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  print "unzipping target target-files..."
78040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  OPTIONS.input_tmp = common.UnzipTemp(args[0])
781ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu
78240daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  common.LoadMaxSizes()
78340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if not OPTIONS.max_image_size:
784ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    print
785ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    print "  WARNING:  Failed to load max image sizes; will not enforce"
786ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    print "  image size limits."
78740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    print
78840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
78940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  OPTIONS.target_tmp = OPTIONS.input_tmp
790ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu  input_zip = zipfile.ZipFile(args[0], "r")
7913e452f8c41b7983f1068a4b7229998c7e7de4334Prathmesh Prabhu  if OPTIONS.package_key:
792ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu    temp_zip_file = tempfile.NamedTemporaryFile()
79340daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    output_zip = zipfile.ZipFile(temp_zip_file, "w",
794ba99b598d3f399a41e57f49dccac5f988e653126Prathmesh Prabhu                                 compression=zipfile.ZIP_DEFLATED)
79540daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  else:
796ade9b9a914efaeac3b2f4d4888ffdfb3e76b76f6Prathmesh Prabhu    output_zip = zipfile.ZipFile(args[1], "w",
79740daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu                                 compression=zipfile.ZIP_DEFLATED)
79840daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu
79940daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  if OPTIONS.incremental_source is None:
80040daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu    WriteFullOTAPackage(input_zip, output_zip)
80140daa01c4c8d772889d9c4167dee312869152174Prathmesh Prabhu  else:
802    print "unzipping source target-files..."
803    OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
804    source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
805    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
806
807  output_zip.close()
808  if OPTIONS.package_key:
809    SignOutput(temp_zip_file.name, args[1])
810    temp_zip_file.close()
811
812  common.Cleanup()
813
814  print "done."
815
816
817if __name__ == '__main__':
818  try:
819    main(sys.argv[1:])
820  except common.ExternalError, e:
821    print
822    print "   ERROR: %s" % (e,)
823    print
824    sys.exit(1)
825