| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 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 | """ | 
 | 18 | Given a target-files zipfile, produces an OTA package that installs | 
 | 19 | that build.  An incremental OTA is produced if -i is given, otherwise | 
 | 20 | a full OTA is produced. | 
 | 21 |  | 
 | 22 | Usage:  ota_from_target_files [flags] input_target_files output_ota_package | 
 | 23 |  | 
 | 24 |   -b  (--board_config)  <file> | 
| Doug Zongker | fdd8e69 | 2009-08-03 17:27:48 -0700 | [diff] [blame] | 25 |       Deprecated. | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 26 |  | 
 | 27 |   -k  (--package_key)  <key> | 
 | 28 |       Key to use to sign the package (default is | 
 | 29 |       "build/target/product/security/testkey"). | 
 | 30 |  | 
 | 31 |   -i  (--incremental_from)  <file> | 
 | 32 |       Generate an incremental OTA using the given target-files zip as | 
 | 33 |       the starting build. | 
 | 34 |  | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 35 |   -w  (--wipe_user_data) | 
 | 36 |       Generate an OTA package that will wipe the user data partition | 
 | 37 |       when installed. | 
 | 38 |  | 
| Doug Zongker | 962069c | 2009-04-23 11:41:58 -0700 | [diff] [blame] | 39 |   -n  (--no_prereq) | 
 | 40 |       Omit the timestamp prereq check normally included at the top of | 
 | 41 |       the build scripts (used for developer OTA packages which | 
 | 42 |       legitimately need to go back and forth). | 
 | 43 |  | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 44 |   -e  (--extra_script)  <file> | 
 | 45 |       Insert the contents of file at the end of the update script. | 
 | 46 |  | 
| Hristo Bojinov | dafb042 | 2010-08-26 14:35:16 -0700 | [diff] [blame] | 47 |   -a  (--aslr_mode)  <on|off> | 
 | 48 |       Specify whether to turn on ASLR for the package (on by default). | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 49 | """ | 
 | 50 |  | 
 | 51 | import sys | 
 | 52 |  | 
 | 53 | if sys.hexversion < 0x02040000: | 
 | 54 |   print >> sys.stderr, "Python 2.4 or newer is required." | 
 | 55 |   sys.exit(1) | 
 | 56 |  | 
 | 57 | import copy | 
| Doug Zongker | c18736b | 2009-09-30 09:20:32 -0700 | [diff] [blame] | 58 | import errno | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 59 | import os | 
 | 60 | import re | 
 | 61 | import sha | 
 | 62 | import subprocess | 
 | 63 | import tempfile | 
 | 64 | import time | 
 | 65 | import zipfile | 
 | 66 |  | 
 | 67 | import common | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 68 | import edify_generator | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 69 |  | 
 | 70 | OPTIONS = common.OPTIONS | 
 | 71 | OPTIONS.package_key = "build/target/product/security/testkey" | 
 | 72 | OPTIONS.incremental_source = None | 
 | 73 | OPTIONS.require_verbatim = set() | 
 | 74 | OPTIONS.prohibit_verbatim = set(("system/build.prop",)) | 
 | 75 | OPTIONS.patch_threshold = 0.95 | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 76 | OPTIONS.wipe_user_data = False | 
| Doug Zongker | 962069c | 2009-04-23 11:41:58 -0700 | [diff] [blame] | 77 | OPTIONS.omit_prereq = False | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 78 | OPTIONS.extra_script = None | 
| Hristo Bojinov | dafb042 | 2010-08-26 14:35:16 -0700 | [diff] [blame] | 79 | OPTIONS.aslr_mode = True | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 80 | OPTIONS.worker_threads = 3 | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 81 |  | 
 | 82 | def MostPopularKey(d, default): | 
 | 83 |   """Given a dict, return the key corresponding to the largest | 
 | 84 |   value.  Returns 'default' if the dict is empty.""" | 
 | 85 |   x = [(v, k) for (k, v) in d.iteritems()] | 
 | 86 |   if not x: return default | 
 | 87 |   x.sort() | 
 | 88 |   return x[-1][1] | 
 | 89 |  | 
 | 90 |  | 
 | 91 | def IsSymlink(info): | 
 | 92 |   """Return true if the zipfile.ZipInfo object passed in represents a | 
 | 93 |   symlink.""" | 
 | 94 |   return (info.external_attr >> 16) == 0120777 | 
 | 95 |  | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 96 | def IsRegular(info): | 
 | 97 |   """Return true if the zipfile.ZipInfo object passed in represents a | 
 | 98 |   symlink.""" | 
 | 99 |   return (info.external_attr >> 28) == 010 | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 100 |  | 
 | 101 |  | 
 | 102 | class Item: | 
 | 103 |   """Items represent the metadata (user, group, mode) of files and | 
 | 104 |   directories in the system image.""" | 
 | 105 |   ITEMS = {} | 
 | 106 |   def __init__(self, name, dir=False): | 
 | 107 |     self.name = name | 
 | 108 |     self.uid = None | 
 | 109 |     self.gid = None | 
 | 110 |     self.mode = None | 
 | 111 |     self.dir = dir | 
 | 112 |  | 
 | 113 |     if name: | 
 | 114 |       self.parent = Item.Get(os.path.dirname(name), dir=True) | 
 | 115 |       self.parent.children.append(self) | 
 | 116 |     else: | 
 | 117 |       self.parent = None | 
 | 118 |     if dir: | 
 | 119 |       self.children = [] | 
 | 120 |  | 
 | 121 |   def Dump(self, indent=0): | 
 | 122 |     if self.uid is not None: | 
 | 123 |       print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode) | 
 | 124 |     else: | 
 | 125 |       print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode) | 
 | 126 |     if self.dir: | 
 | 127 |       print "%s%s" % ("  "*indent, self.descendants) | 
 | 128 |       print "%s%s" % ("  "*indent, self.best_subtree) | 
 | 129 |       for i in self.children: | 
 | 130 |         i.Dump(indent=indent+1) | 
 | 131 |  | 
 | 132 |   @classmethod | 
 | 133 |   def Get(cls, name, dir=False): | 
 | 134 |     if name not in cls.ITEMS: | 
 | 135 |       cls.ITEMS[name] = Item(name, dir=dir) | 
 | 136 |     return cls.ITEMS[name] | 
 | 137 |  | 
 | 138 |   @classmethod | 
| Doug Zongker | 283e2a1 | 2010-03-15 17:52:32 -0700 | [diff] [blame] | 139 |   def GetMetadata(cls, input_zip): | 
 | 140 |  | 
 | 141 |     try: | 
 | 142 |       # See if the target_files contains a record of what the uid, | 
 | 143 |       # gid, and mode is supposed to be. | 
 | 144 |       output = input_zip.read("META/filesystem_config.txt") | 
 | 145 |     except KeyError: | 
 | 146 |       # Run the external 'fs_config' program to determine the desired | 
 | 147 |       # uid, gid, and mode for every Item object.  Note this uses the | 
 | 148 |       # one in the client now, which might not be the same as the one | 
 | 149 |       # used when this target_files was built. | 
 | 150 |       p = common.Run(["fs_config"], stdin=subprocess.PIPE, | 
 | 151 |                      stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
 | 152 |       suffix = { False: "", True: "/" } | 
 | 153 |       input = "".join(["%s%s\n" % (i.name, suffix[i.dir]) | 
 | 154 |                        for i in cls.ITEMS.itervalues() if i.name]) | 
| Doug Zongker | 3475d36 | 2010-03-17 16:39:30 -0700 | [diff] [blame] | 155 |       output, error = p.communicate(input) | 
| Doug Zongker | 283e2a1 | 2010-03-15 17:52:32 -0700 | [diff] [blame] | 156 |       assert not error | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 157 |  | 
 | 158 |     for line in output.split("\n"): | 
 | 159 |       if not line: continue | 
 | 160 |       name, uid, gid, mode = line.split() | 
| Doug Zongker | 283e2a1 | 2010-03-15 17:52:32 -0700 | [diff] [blame] | 161 |       i = cls.ITEMS.get(name, None) | 
 | 162 |       if i is not None: | 
 | 163 |         i.uid = int(uid) | 
 | 164 |         i.gid = int(gid) | 
 | 165 |         i.mode = int(mode, 8) | 
 | 166 |         if i.dir: | 
 | 167 |           i.children.sort(key=lambda i: i.name) | 
 | 168 |  | 
 | 169 |     # set metadata for the files generated by this script. | 
 | 170 |     i = cls.ITEMS.get("system/recovery-from-boot.p", None) | 
 | 171 |     if i: i.uid, i.gid, i.mode = 0, 0, 0644 | 
 | 172 |     i = cls.ITEMS.get("system/etc/install-recovery.sh", None) | 
 | 173 |     if i: i.uid, i.gid, i.mode = 0, 0, 0544 | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 174 |  | 
 | 175 |   def CountChildMetadata(self): | 
 | 176 |     """Count up the (uid, gid, mode) tuples for all children and | 
 | 177 |     determine the best strategy for using set_perm_recursive and | 
 | 178 |     set_perm to correctly chown/chmod all the files to their desired | 
 | 179 |     values.  Recursively calls itself for all descendants. | 
 | 180 |  | 
 | 181 |     Returns a dict of {(uid, gid, dmode, fmode): count} counting up | 
 | 182 |     all descendants of this node.  (dmode or fmode may be None.)  Also | 
 | 183 |     sets the best_subtree of each directory Item to the (uid, gid, | 
 | 184 |     dmode, fmode) tuple that will match the most descendants of that | 
 | 185 |     Item. | 
 | 186 |     """ | 
 | 187 |  | 
 | 188 |     assert self.dir | 
 | 189 |     d = self.descendants = {(self.uid, self.gid, self.mode, None): 1} | 
 | 190 |     for i in self.children: | 
 | 191 |       if i.dir: | 
 | 192 |         for k, v in i.CountChildMetadata().iteritems(): | 
 | 193 |           d[k] = d.get(k, 0) + v | 
 | 194 |       else: | 
 | 195 |         k = (i.uid, i.gid, None, i.mode) | 
 | 196 |         d[k] = d.get(k, 0) + 1 | 
 | 197 |  | 
 | 198 |     # Find the (uid, gid, dmode, fmode) tuple that matches the most | 
 | 199 |     # descendants. | 
 | 200 |  | 
 | 201 |     # First, find the (uid, gid) pair that matches the most | 
 | 202 |     # descendants. | 
 | 203 |     ug = {} | 
 | 204 |     for (uid, gid, _, _), count in d.iteritems(): | 
 | 205 |       ug[(uid, gid)] = ug.get((uid, gid), 0) + count | 
 | 206 |     ug = MostPopularKey(ug, (0, 0)) | 
 | 207 |  | 
 | 208 |     # Now find the dmode and fmode that match the most descendants | 
 | 209 |     # with that (uid, gid), and choose those. | 
 | 210 |     best_dmode = (0, 0755) | 
 | 211 |     best_fmode = (0, 0644) | 
 | 212 |     for k, count in d.iteritems(): | 
 | 213 |       if k[:2] != ug: continue | 
 | 214 |       if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) | 
 | 215 |       if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) | 
 | 216 |     self.best_subtree = ug + (best_dmode[1], best_fmode[1]) | 
 | 217 |  | 
 | 218 |     return d | 
 | 219 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 220 |   def SetPermissions(self, script): | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 221 |     """Append set_perm/set_perm_recursive commands to 'script' to | 
 | 222 |     set all permissions, users, and groups for the tree of files | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 223 |     rooted at 'self'.""" | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 224 |  | 
 | 225 |     self.CountChildMetadata() | 
 | 226 |  | 
 | 227 |     def recurse(item, current): | 
 | 228 |       # current is the (uid, gid, dmode, fmode) tuple that the current | 
 | 229 |       # item (and all its children) have already been set to.  We only | 
 | 230 |       # need to issue set_perm/set_perm_recursive commands if we're | 
 | 231 |       # supposed to be something different. | 
 | 232 |       if item.dir: | 
 | 233 |         if current != item.best_subtree: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 234 |           script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 235 |           current = item.best_subtree | 
 | 236 |  | 
 | 237 |         if item.uid != current[0] or item.gid != current[1] or \ | 
 | 238 |            item.mode != current[2]: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 239 |           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 240 |  | 
 | 241 |         for i in item.children: | 
 | 242 |           recurse(i, current) | 
 | 243 |       else: | 
 | 244 |         if item.uid != current[0] or item.gid != current[1] or \ | 
 | 245 |                item.mode != current[3]: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 246 |           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 247 |  | 
 | 248 |     recurse(self, (-1, -1, -1, -1)) | 
 | 249 |  | 
 | 250 |  | 
 | 251 | def CopySystemFiles(input_zip, output_zip=None, | 
 | 252 |                     substitute=None): | 
 | 253 |   """Copies files underneath system/ in the input zip to the output | 
 | 254 |   zip.  Populates the Item class with their metadata, and returns a | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 255 |   list of symlinks as well as a list of files that will be retouched. | 
 | 256 |   output_zip may be None, in which case the copy is skipped (but the | 
 | 257 |   other side effects still happen).  substitute is an optional dict | 
 | 258 |   of {output filename: contents} to be output instead of certain input | 
 | 259 |   files. | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 260 |   """ | 
 | 261 |  | 
 | 262 |   symlinks = [] | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 263 |   retouch_files = [] | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 264 |  | 
 | 265 |   for info in input_zip.infolist(): | 
 | 266 |     if info.filename.startswith("SYSTEM/"): | 
 | 267 |       basefilename = info.filename[7:] | 
 | 268 |       if IsSymlink(info): | 
 | 269 |         symlinks.append((input_zip.read(info.filename), | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 270 |                          "/system/" + basefilename)) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 271 |       else: | 
 | 272 |         info2 = copy.copy(info) | 
 | 273 |         fn = info2.filename = "system/" + basefilename | 
 | 274 |         if substitute and fn in substitute and substitute[fn] is None: | 
 | 275 |           continue | 
 | 276 |         if output_zip is not None: | 
 | 277 |           if substitute and fn in substitute: | 
 | 278 |             data = substitute[fn] | 
 | 279 |           else: | 
 | 280 |             data = input_zip.read(info.filename) | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 281 |           if info.filename.startswith("SYSTEM/lib/") and IsRegular(info): | 
 | 282 |             retouch_files.append(("/system/" + basefilename, | 
 | 283 |                                   sha.sha(data).hexdigest())) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 284 |           output_zip.writestr(info2, data) | 
 | 285 |         if fn.endswith("/"): | 
 | 286 |           Item.Get(fn[:-1], dir=True) | 
 | 287 |         else: | 
 | 288 |           Item.Get(fn, dir=False) | 
 | 289 |  | 
 | 290 |   symlinks.sort() | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 291 |   return (symlinks, retouch_files) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 292 |  | 
 | 293 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 294 | def SignOutput(temp_zip_name, output_zip_name): | 
 | 295 |   key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) | 
 | 296 |   pw = key_passwords[OPTIONS.package_key] | 
 | 297 |  | 
| Doug Zongker | 951495f | 2009-08-14 12:44:19 -0700 | [diff] [blame] | 298 |   common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw, | 
 | 299 |                   whole_file=True) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 300 |  | 
 | 301 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 302 | def AppendAssertions(script, input_zip): | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 303 |   device = GetBuildProp("ro.product.device", input_zip) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 304 |   script.AssertDevice(device) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 305 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 306 |  | 
| Doug Zongker | 486de12 | 2010-09-16 14:01:56 -0700 | [diff] [blame] | 307 | def MakeRecoveryPatch(output_zip, recovery_img, boot_img): | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 308 |   """Generate a binary patch that creates the recovery image starting | 
 | 309 |   with the boot image.  (Most of the space in these images is just the | 
 | 310 |   kernel, which is identical for the two, so the resulting patch | 
 | 311 |   should be efficient.)  Add it to the output zip, along with a shell | 
 | 312 |   script that is run from init.rc on first boot to actually do the | 
 | 313 |   patching and install the new recovery image. | 
 | 314 |  | 
 | 315 |   recovery_img and boot_img should be File objects for the | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 316 |   corresponding images.  info should be the dictionary returned by | 
 | 317 |   common.LoadInfoDict() on the input target_files. | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 318 |  | 
 | 319 |   Returns an Item for the shell script, which must be made | 
 | 320 |   executable. | 
 | 321 |   """ | 
 | 322 |  | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 323 |   d = common.Difference(recovery_img, boot_img) | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 324 |   _, _, patch = d.ComputePatch() | 
| Doug Zongker | cfd7db6 | 2009-10-07 11:35:53 -0700 | [diff] [blame] | 325 |   common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 326 |   Item.Get("system/recovery-from-boot.p", dir=False) | 
 | 327 |  | 
 | 328 |   # Images with different content will have a different first page, so | 
 | 329 |   # we check to see if this recovery has already been installed by | 
 | 330 |   # testing just the first 2k. | 
 | 331 |   HEADER_SIZE = 2048 | 
 | 332 |   header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest() | 
 | 333 |   sh = """#!/system/bin/sh | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 334 | if ! applypatch -c %(partition_type)s:%(partition_path)srecovery:%(header_size)d:%(header_sha1)s; then | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 335 |   log -t recovery "Installing new recovery image" | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 336 |   applypatch %(partition_type)s:%(partition_path)sboot:%(boot_size)d:%(boot_sha1)s %(partition_type)s:%(partition_path)srecovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 337 | else | 
 | 338 |   log -t recovery "Recovery image already installed" | 
 | 339 | fi | 
 | 340 | """ % { 'boot_size': boot_img.size, | 
 | 341 |         'boot_sha1': boot_img.sha1, | 
 | 342 |         'header_size': HEADER_SIZE, | 
 | 343 |         'header_sha1': header_sha1, | 
 | 344 |         'recovery_size': recovery_img.size, | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 345 |         'recovery_sha1': recovery_img.sha1, | 
| Doug Zongker | 486de12 | 2010-09-16 14:01:56 -0700 | [diff] [blame] | 346 |         'partition_type': OPTIONS.info_dict["partition_type"], | 
 | 347 |         'partition_path': OPTIONS.info_dict.get("partition_path", ""), | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 348 |         } | 
| Doug Zongker | cfd7db6 | 2009-10-07 11:35:53 -0700 | [diff] [blame] | 349 |   common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 350 |   return Item.Get("system/etc/install-recovery.sh", dir=False) | 
 | 351 |  | 
 | 352 |  | 
| Doug Zongker | c77a9ad | 2010-09-16 11:28:43 -0700 | [diff] [blame] | 353 | def WriteFullOTAPackage(input_zip, output_zip): | 
| Doug Zongker | 9ce2ebf | 2010-04-21 14:08:44 -0700 | [diff] [blame] | 354 |   # TODO: how to determine this?  We don't know what version it will | 
 | 355 |   # be installed on top of.  For now, we expect the API just won't | 
 | 356 |   # change very often. | 
| Doug Zongker | c77a9ad | 2010-09-16 11:28:43 -0700 | [diff] [blame] | 357 |   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 358 |  | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 359 |   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip), | 
 | 360 |               "pre-device": GetBuildProp("ro.product.device", input_zip), | 
| Doug Zongker | 3b85269 | 2010-06-21 15:30:45 -0700 | [diff] [blame] | 361 |               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip), | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 362 |               } | 
 | 363 |  | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 364 |   device_specific = common.DeviceSpecificParams( | 
 | 365 |       input_zip=input_zip, | 
| Doug Zongker | 3797473 | 2010-09-16 17:44:38 -0700 | [diff] [blame] | 366 |       input_version=OPTIONS.info_dict["recovery_api_version"], | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 367 |       output_zip=output_zip, | 
 | 368 |       script=script, | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 369 |       input_tmp=OPTIONS.input_tmp, | 
 | 370 |       metadata=metadata) | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 371 |  | 
| Doug Zongker | 962069c | 2009-04-23 11:41:58 -0700 | [diff] [blame] | 372 |   if not OPTIONS.omit_prereq: | 
 | 373 |     ts = GetBuildProp("ro.build.date.utc", input_zip) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 374 |     script.AssertOlderBuild(ts) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 375 |  | 
 | 376 |   AppendAssertions(script, input_zip) | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 377 |   device_specific.FullOTA_Assertions() | 
| Doug Zongker | 171f1cd | 2009-06-15 22:36:37 -0700 | [diff] [blame] | 378 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 379 |   script.ShowProgress(0.5, 0) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 380 |  | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 381 |   if OPTIONS.wipe_user_data: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 382 |     script.FormatPartition("userdata") | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 383 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 384 |   script.FormatPartition("system") | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 385 |   script.Mount("system", "/system") | 
| Doug Zongker | cfd7db6 | 2009-10-07 11:35:53 -0700 | [diff] [blame] | 386 |   script.UnpackPackageDir("recovery", "/system") | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 387 |   script.UnpackPackageDir("system", "/system") | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 388 |  | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 389 |   (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 390 |   script.MakeSymlinks(symlinks) | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 391 |   if OPTIONS.aslr_mode: | 
 | 392 |     script.RetouchBinaries(retouch_files) | 
 | 393 |   else: | 
 | 394 |     script.UndoRetouchBinaries(retouch_files) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 395 |  | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 396 |   boot_img = common.File("boot.img", common.BuildBootableImage( | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 397 |       os.path.join(OPTIONS.input_tmp, "BOOT"))) | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 398 |   recovery_img = common.File("recovery.img", common.BuildBootableImage( | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 399 |       os.path.join(OPTIONS.input_tmp, "RECOVERY"))) | 
| Doug Zongker | 486de12 | 2010-09-16 14:01:56 -0700 | [diff] [blame] | 400 |   MakeRecoveryPatch(output_zip, recovery_img, boot_img) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 401 |  | 
| Doug Zongker | 283e2a1 | 2010-03-15 17:52:32 -0700 | [diff] [blame] | 402 |   Item.GetMetadata(input_zip) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 403 |   Item.Get("system").SetPermissions(script) | 
 | 404 |  | 
| Doug Zongker | 3797473 | 2010-09-16 17:44:38 -0700 | [diff] [blame] | 405 |   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 406 |   common.ZipWriteStr(output_zip, "boot.img", boot_img.data) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 407 |   script.ShowProgress(0.2, 0) | 
 | 408 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 409 |   script.ShowProgress(0.2, 10) | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 410 |   script.WriteRawImage("boot", "boot.img") | 
 | 411 |  | 
 | 412 |   script.ShowProgress(0.1, 0) | 
 | 413 |   device_specific.FullOTA_InstallEnd() | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 414 |  | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 415 |   if OPTIONS.extra_script is not None: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 416 |     script.AppendExtra(OPTIONS.extra_script) | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 417 |  | 
| Doug Zongker | 1483360 | 2010-02-02 13:12:04 -0800 | [diff] [blame] | 418 |   script.UnmountAll() | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 419 |   script.AddToZip(input_zip, output_zip) | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 420 |   WriteMetadata(metadata, output_zip) | 
 | 421 |  | 
 | 422 |  | 
 | 423 | def WriteMetadata(metadata, output_zip): | 
 | 424 |   common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", | 
 | 425 |                      "".join(["%s=%s\n" % kv | 
 | 426 |                               for kv in sorted(metadata.iteritems())])) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 427 |  | 
 | 428 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 429 |  | 
 | 430 |  | 
 | 431 | def LoadSystemFiles(z): | 
 | 432 |   """Load all the files from SYSTEM/... in a given target-files | 
 | 433 |   ZipFile, and return a dict of {filename: File object}.""" | 
 | 434 |   out = {} | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 435 |   retouch_files = [] | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 436 |   for info in z.infolist(): | 
 | 437 |     if info.filename.startswith("SYSTEM/") and not IsSymlink(info): | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 438 |       basefilename = info.filename[7:] | 
 | 439 |       fn = "system/" + basefilename | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 440 |       data = z.read(info.filename) | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 441 |       out[fn] = common.File(fn, data) | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 442 |       if info.filename.startswith("SYSTEM/lib/") and IsRegular(info): | 
 | 443 |         retouch_files.append(("/system/" + basefilename, | 
 | 444 |                               out[fn].sha1)) | 
 | 445 |   return (out, retouch_files) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 446 |  | 
 | 447 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 448 | def GetBuildProp(property, z): | 
 | 449 |   """Return the fingerprint of the build of a given target-files | 
 | 450 |   ZipFile object.""" | 
 | 451 |   bp = z.read("SYSTEM/build.prop") | 
 | 452 |   if not property: | 
 | 453 |     return bp | 
 | 454 |   m = re.search(re.escape(property) + r"=(.*)\n", bp) | 
 | 455 |   if not m: | 
| Doug Zongker | 9fc74c7 | 2009-06-23 16:27:38 -0700 | [diff] [blame] | 456 |     raise common.ExternalError("couldn't find %s in build.prop" % (property,)) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 457 |   return m.group(1).strip() | 
 | 458 |  | 
 | 459 |  | 
| Doug Zongker | c77a9ad | 2010-09-16 11:28:43 -0700 | [diff] [blame] | 460 | def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): | 
| Doug Zongker | 3797473 | 2010-09-16 17:44:38 -0700 | [diff] [blame] | 461 |   source_version = OPTIONS.source_info_dict["recovery_api_version"] | 
 | 462 |   target_version = OPTIONS.target_info_dict["recovery_api_version"] | 
| Doug Zongker | b984ae5 | 2010-09-16 23:13:11 -0700 | [diff] [blame] | 463 |   partition_type = OPTIONS.target_info_dict["partition_type"] | 
 | 464 |   partition_path = OPTIONS.target_info_dict.get("partition_path", "") | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 465 |  | 
| Doug Zongker | 9ce2ebf | 2010-04-21 14:08:44 -0700 | [diff] [blame] | 466 |   if source_version == 0: | 
 | 467 |     print ("WARNING: generating edify script for a source that " | 
 | 468 |            "can't install it.") | 
| Doug Zongker | b984ae5 | 2010-09-16 23:13:11 -0700 | [diff] [blame] | 469 |   script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 470 |  | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 471 |   metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip), | 
| Doug Zongker | 3b85269 | 2010-06-21 15:30:45 -0700 | [diff] [blame] | 472 |               "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip), | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 473 |               } | 
 | 474 |  | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 475 |   device_specific = common.DeviceSpecificParams( | 
 | 476 |       source_zip=source_zip, | 
| Doug Zongker | 1483360 | 2010-02-02 13:12:04 -0800 | [diff] [blame] | 477 |       source_version=source_version, | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 478 |       target_zip=target_zip, | 
| Doug Zongker | 1483360 | 2010-02-02 13:12:04 -0800 | [diff] [blame] | 479 |       target_version=target_version, | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 480 |       output_zip=output_zip, | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 481 |       script=script, | 
 | 482 |       metadata=metadata) | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 483 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 484 |   print "Loading target..." | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 485 |   (target_data, target_retouch_files) = LoadSystemFiles(target_zip) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 486 |   print "Loading source..." | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 487 |   (source_data, source_retouch_files) = LoadSystemFiles(source_zip) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 488 |  | 
 | 489 |   verbatim_targets = [] | 
 | 490 |   patch_list = [] | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 491 |   diffs = [] | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 492 |   largest_source_size = 0 | 
 | 493 |   for fn in sorted(target_data.keys()): | 
 | 494 |     tf = target_data[fn] | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 495 |     assert fn == tf.name | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 496 |     sf = source_data.get(fn, None) | 
 | 497 |  | 
 | 498 |     if sf is None or fn in OPTIONS.require_verbatim: | 
 | 499 |       # This file should be included verbatim | 
 | 500 |       if fn in OPTIONS.prohibit_verbatim: | 
| Doug Zongker | 9fc74c7 | 2009-06-23 16:27:38 -0700 | [diff] [blame] | 501 |         raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 502 |       print "send", fn, "verbatim" | 
 | 503 |       tf.AddToZip(output_zip) | 
 | 504 |       verbatim_targets.append((fn, tf.size)) | 
 | 505 |     elif tf.sha1 != sf.sha1: | 
 | 506 |       # File is different; consider sending as a patch | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 507 |       diffs.append(common.Difference(tf, sf)) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 508 |     else: | 
 | 509 |       # Target file identical to source. | 
 | 510 |       pass | 
 | 511 |  | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 512 |   common.ComputeDifferences(diffs) | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 513 |  | 
 | 514 |   for diff in diffs: | 
 | 515 |     tf, sf, d = diff.GetPatch() | 
 | 516 |     if d is None or len(d) > tf.size * OPTIONS.patch_threshold: | 
 | 517 |       # patch is almost as big as the file; don't bother patching | 
 | 518 |       tf.AddToZip(output_zip) | 
 | 519 |       verbatim_targets.append((tf.name, tf.size)) | 
 | 520 |     else: | 
 | 521 |       common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d) | 
| Doug Zongker | 5a48209 | 2010-02-17 16:09:18 -0800 | [diff] [blame] | 522 |       patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest())) | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 523 |       largest_source_size = max(largest_source_size, sf.size) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 524 |  | 
 | 525 |   source_fp = GetBuildProp("ro.build.fingerprint", source_zip) | 
 | 526 |   target_fp = GetBuildProp("ro.build.fingerprint", target_zip) | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 527 |   metadata["pre-build"] = source_fp | 
 | 528 |   metadata["post-build"] = target_fp | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 529 |  | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 530 |   script.Mount("system", "/system") | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 531 |   script.AssertSomeFingerprint(source_fp, target_fp) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 532 |  | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 533 |   source_boot = common.File("/tmp/boot.img", | 
 | 534 |                             common.BuildBootableImage( | 
 | 535 |                                 os.path.join(OPTIONS.source_tmp, "BOOT"))) | 
 | 536 |   target_boot = common.File("/tmp/boot.img", | 
 | 537 |                             common.BuildBootableImage( | 
 | 538 |                                 os.path.join(OPTIONS.target_tmp, "BOOT"))) | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 539 |   updating_boot = (source_boot.data != target_boot.data) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 540 |  | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 541 |   source_recovery = common.File("system/recovery.img", | 
 | 542 |                                 common.BuildBootableImage( | 
 | 543 |                                     os.path.join(OPTIONS.source_tmp, "RECOVERY"))) | 
 | 544 |   target_recovery = common.File("system/recovery.img", | 
 | 545 |                                 common.BuildBootableImage( | 
 | 546 |                                     os.path.join(OPTIONS.target_tmp, "RECOVERY"))) | 
| Doug Zongker | f6a8bad | 2009-05-29 11:41:21 -0700 | [diff] [blame] | 547 |   updating_recovery = (source_recovery.data != target_recovery.data) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 548 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 549 |   # Here's how we divide up the progress bar: | 
 | 550 |   #  0.1 for verifying the start state (PatchCheck calls) | 
 | 551 |   #  0.8 for applying patches (ApplyPatch calls) | 
 | 552 |   #  0.1 for unpacking verbatim files, symlinking, and doing the | 
 | 553 |   #      device-specific commands. | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 554 |  | 
 | 555 |   AppendAssertions(script, target_zip) | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 556 |   device_specific.IncrementalOTA_Assertions() | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 557 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 558 |   script.Print("Verifying current system...") | 
 | 559 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 560 |   script.ShowProgress(0.1, 0) | 
 | 561 |   total_verify_size = float(sum([i[2].size for i in patch_list]) + 1) | 
 | 562 |   if updating_boot: | 
 | 563 |     total_verify_size += source_boot.size | 
 | 564 |   so_far = 0 | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 565 |  | 
| Doug Zongker | 5a48209 | 2010-02-17 16:09:18 -0800 | [diff] [blame] | 566 |   for fn, tf, sf, size, patch_sha in patch_list: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 567 |     script.PatchCheck("/"+fn, tf.sha1, sf.sha1) | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 568 |     so_far += sf.size | 
 | 569 |     script.SetProgress(so_far / total_verify_size) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 570 |  | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 571 |   if updating_boot: | 
| Doug Zongker | ea5d7a9 | 2010-09-12 15:26:16 -0700 | [diff] [blame] | 572 |     d = common.Difference(target_boot, source_boot) | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 573 |     _, _, d = d.ComputePatch() | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 574 |     print "boot      target: %d  source: %d  diff: %d" % ( | 
 | 575 |         target_boot.size, source_boot.size, len(d)) | 
 | 576 |  | 
| Doug Zongker | 048e7ca | 2009-06-15 14:31:53 -0700 | [diff] [blame] | 577 |     common.ZipWriteStr(output_zip, "patch/boot.img.p", d) | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 578 |  | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 579 |     script.PatchCheck("%s:%sboot:%d:%s:%d:%s" % | 
 | 580 |                       (partition_type, partition_path, | 
 | 581 |                        source_boot.size, source_boot.sha1, | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 582 |                        target_boot.size, target_boot.sha1)) | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 583 |     so_far += source_boot.size | 
 | 584 |     script.SetProgress(so_far / total_verify_size) | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 585 |  | 
 | 586 |   if patch_list or updating_recovery or updating_boot: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 587 |     script.CacheFreeSpaceCheck(largest_source_size) | 
| Doug Zongker | 5a48209 | 2010-02-17 16:09:18 -0800 | [diff] [blame] | 588 |  | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 589 |   device_specific.IncrementalOTA_VerifyEnd() | 
 | 590 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 591 |   script.Comment("---- start making changes here ----") | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 592 |  | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 593 |   if OPTIONS.wipe_user_data: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 594 |     script.Print("Erasing user data...") | 
 | 595 |     script.FormatPartition("userdata") | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 596 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 597 |   script.Print("Removing unneeded files...") | 
| Doug Zongker | 0f3298a | 2009-06-30 08:16:58 -0700 | [diff] [blame] | 598 |   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + | 
 | 599 |                      ["/"+i for i in sorted(source_data) | 
| Doug Zongker | 3b949f0 | 2009-08-24 10:24:32 -0700 | [diff] [blame] | 600 |                             if i not in target_data] + | 
 | 601 |                      ["/system/recovery.img"]) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 602 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 603 |   script.ShowProgress(0.8, 0) | 
 | 604 |   total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) | 
 | 605 |   if updating_boot: | 
 | 606 |     total_patch_size += target_boot.size | 
 | 607 |   so_far = 0 | 
 | 608 |  | 
 | 609 |   script.Print("Patching system files...") | 
| Doug Zongker | 5a48209 | 2010-02-17 16:09:18 -0800 | [diff] [blame] | 610 |   for fn, tf, sf, size, _ in patch_list: | 
| Doug Zongker | c8d446b | 2010-02-22 15:41:53 -0800 | [diff] [blame] | 611 |     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 612 |     so_far += tf.size | 
 | 613 |     script.SetProgress(so_far / total_patch_size) | 
 | 614 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 615 |   if updating_boot: | 
| Doug Zongker | 5da317e | 2009-06-02 13:38:17 -0700 | [diff] [blame] | 616 |     # Produce the boot image by applying a patch to the current | 
 | 617 |     # contents of the boot partition, and write it back to the | 
 | 618 |     # partition. | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 619 |     script.Print("Patching boot image...") | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 620 |     script.ApplyPatch("%s:%sboot:%d:%s:%d:%s" | 
 | 621 |                       % (partition_type, partition_path, | 
 | 622 |                          source_boot.size, source_boot.sha1, | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 623 |                          target_boot.size, target_boot.sha1), | 
 | 624 |                       "-", | 
 | 625 |                       target_boot.size, target_boot.sha1, | 
| Doug Zongker | c8d446b | 2010-02-22 15:41:53 -0800 | [diff] [blame] | 626 |                       source_boot.sha1, "patch/boot.img.p") | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 627 |     so_far += target_boot.size | 
 | 628 |     script.SetProgress(so_far / total_patch_size) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 629 |     print "boot image changed; including." | 
 | 630 |   else: | 
 | 631 |     print "boot image unchanged; skipping." | 
 | 632 |  | 
 | 633 |   if updating_recovery: | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 634 |     # Is it better to generate recovery as a patch from the current | 
 | 635 |     # boot image, or from the previous recovery image?  For large | 
 | 636 |     # updates with significant kernel changes, probably the former. | 
 | 637 |     # For small updates where the kernel hasn't changed, almost | 
 | 638 |     # certainly the latter.  We pick the first option.  Future | 
 | 639 |     # complicated schemes may let us effectively use both. | 
 | 640 |     # | 
 | 641 |     # A wacky possibility: as long as there is room in the boot | 
 | 642 |     # partition, include the binaries and image files from recovery in | 
 | 643 |     # the boot image (though not in the ramdisk) so they can be used | 
 | 644 |     # as fodder for constructing the recovery image. | 
| Doug Zongker | 486de12 | 2010-09-16 14:01:56 -0700 | [diff] [blame] | 645 |     MakeRecoveryPatch(output_zip, target_recovery, target_boot) | 
| Doug Zongker | 4226539 | 2010-02-12 10:21:00 -0800 | [diff] [blame] | 646 |     script.DeleteFiles(["/system/recovery-from-boot.p", | 
 | 647 |                         "/system/etc/install-recovery.sh"]) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 648 |     print "recovery image changed; including as patch from boot." | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 649 |   else: | 
 | 650 |     print "recovery image unchanged; skipping." | 
 | 651 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 652 |   script.ShowProgress(0.1, 10) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 653 |  | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 654 |   (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 655 |  | 
 | 656 |   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 657 |   temp_script = script.MakeTemporary() | 
| Doug Zongker | 283e2a1 | 2010-03-15 17:52:32 -0700 | [diff] [blame] | 658 |   Item.GetMetadata(target_zip) | 
| Doug Zongker | 73ef825 | 2009-07-23 15:12:53 -0700 | [diff] [blame] | 659 |   Item.Get("system").SetPermissions(temp_script) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 660 |  | 
 | 661 |   # Note that this call will mess up the tree of Items, so make sure | 
 | 662 |   # we're done with it. | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 663 |   (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 664 |   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) | 
 | 665 |  | 
 | 666 |   # Delete all the symlinks in source that aren't in target.  This | 
 | 667 |   # needs to happen before verbatim files are unpacked, in case a | 
 | 668 |   # symlink in the source is replaced by a real file in the target. | 
 | 669 |   to_delete = [] | 
 | 670 |   for dest, link in source_symlinks: | 
 | 671 |     if link not in target_symlinks_d: | 
 | 672 |       to_delete.append(link) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 673 |   script.DeleteFiles(to_delete) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 674 |  | 
 | 675 |   if verbatim_targets: | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 676 |     script.Print("Unpacking new files...") | 
 | 677 |     script.UnpackPackageDir("system", "/system") | 
 | 678 |  | 
| Doug Zongker | 4226539 | 2010-02-12 10:21:00 -0800 | [diff] [blame] | 679 |   if updating_recovery: | 
 | 680 |     script.Print("Unpacking new recovery...") | 
 | 681 |     script.UnpackPackageDir("recovery", "/system") | 
 | 682 |  | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 683 |   script.Print("Symlinks and permissions...") | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 684 |  | 
 | 685 |   # Create all the symlinks that don't already exist, or point to | 
 | 686 |   # somewhere different than what we want.  Delete each symlink before | 
 | 687 |   # creating it, since the 'symlink' command won't overwrite. | 
 | 688 |   to_create = [] | 
 | 689 |   for dest, link in target_symlinks: | 
 | 690 |     if link in source_symlinks_d: | 
 | 691 |       if dest != source_symlinks_d[link]: | 
 | 692 |         to_create.append((dest, link)) | 
 | 693 |     else: | 
 | 694 |       to_create.append((dest, link)) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 695 |   script.DeleteFiles([i[1] for i in to_create]) | 
 | 696 |   script.MakeSymlinks(to_create) | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 697 |   if OPTIONS.aslr_mode: | 
 | 698 |     script.RetouchBinaries(target_retouch_files) | 
 | 699 |   else: | 
 | 700 |     script.UndoRetouchBinaries(target_retouch_files) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 701 |  | 
 | 702 |   # Now that the symlinks are created, we can set all the | 
 | 703 |   # permissions. | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 704 |   script.AppendScript(temp_script) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 705 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 706 |   # Do device-specific installation (eg, write radio image). | 
| Doug Zongker | 05d3dea | 2009-06-22 11:32:31 -0700 | [diff] [blame] | 707 |   device_specific.IncrementalOTA_InstallEnd() | 
 | 708 |  | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 709 |   if OPTIONS.extra_script is not None: | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 710 |     script.AppendExtra(OPTIONS.extra_script) | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 711 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 712 |   script.AddToZip(target_zip, output_zip) | 
| Doug Zongker | 2ea2106 | 2010-04-28 16:05:21 -0700 | [diff] [blame] | 713 |   WriteMetadata(metadata, output_zip) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 714 |  | 
 | 715 |  | 
 | 716 | def main(argv): | 
 | 717 |  | 
 | 718 |   def option_handler(o, a): | 
 | 719 |     if o in ("-b", "--board_config"): | 
| Doug Zongker | fdd8e69 | 2009-08-03 17:27:48 -0700 | [diff] [blame] | 720 |       pass   # deprecated | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 721 |     elif o in ("-k", "--package_key"): | 
 | 722 |       OPTIONS.package_key = a | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 723 |     elif o in ("-i", "--incremental_from"): | 
 | 724 |       OPTIONS.incremental_source = a | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 725 |     elif o in ("-w", "--wipe_user_data"): | 
 | 726 |       OPTIONS.wipe_user_data = True | 
| Doug Zongker | 962069c | 2009-04-23 11:41:58 -0700 | [diff] [blame] | 727 |     elif o in ("-n", "--no_prereq"): | 
 | 728 |       OPTIONS.omit_prereq = True | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 729 |     elif o in ("-e", "--extra_script"): | 
 | 730 |       OPTIONS.extra_script = a | 
| Hristo Bojinov | dafb042 | 2010-08-26 14:35:16 -0700 | [diff] [blame] | 731 |     elif o in ("-a", "--aslr_mode"): | 
 | 732 |       if a in ("on", "On", "true", "True", "yes", "Yes"): | 
 | 733 |         OPTIONS.aslr_mode = True | 
 | 734 |       else: | 
 | 735 |         OPTIONS.aslr_mode = False | 
| Doug Zongker | 761e642 | 2009-09-25 10:45:39 -0700 | [diff] [blame] | 736 |     elif o in ("--worker_threads"): | 
 | 737 |       OPTIONS.worker_threads = int(a) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 738 |     else: | 
 | 739 |       return False | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 740 |     return True | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 741 |  | 
 | 742 |   args = common.ParseOptions(argv, __doc__, | 
| Hristo Bojinov | dafb042 | 2010-08-26 14:35:16 -0700 | [diff] [blame] | 743 |                              extra_opts="b:k:i:d:wne:a:", | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 744 |                              extra_long_opts=["board_config=", | 
 | 745 |                                               "package_key=", | 
| Doug Zongker | dbfaae5 | 2009-04-21 17:12:54 -0700 | [diff] [blame] | 746 |                                               "incremental_from=", | 
| Doug Zongker | 962069c | 2009-04-23 11:41:58 -0700 | [diff] [blame] | 747 |                                               "wipe_user_data", | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 748 |                                               "no_prereq", | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 749 |                                               "extra_script=", | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 750 |                                               "worker_threads=", | 
| Doug Zongker | c60c1ba | 2010-09-03 13:22:38 -0700 | [diff] [blame] | 751 |                                               "aslr_mode=", | 
 | 752 |                                               ], | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 753 |                              extra_option_handler=option_handler) | 
 | 754 |  | 
 | 755 |   if len(args) != 2: | 
 | 756 |     common.Usage(__doc__) | 
 | 757 |     sys.exit(1) | 
 | 758 |  | 
| Doug Zongker | 1c390a2 | 2009-05-14 19:06:36 -0700 | [diff] [blame] | 759 |   if OPTIONS.extra_script is not None: | 
 | 760 |     OPTIONS.extra_script = open(OPTIONS.extra_script).read() | 
 | 761 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 762 |   print "unzipping target target-files..." | 
 | 763 |   OPTIONS.input_tmp = common.UnzipTemp(args[0]) | 
| Doug Zongker | fdd8e69 | 2009-08-03 17:27:48 -0700 | [diff] [blame] | 764 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 765 |   OPTIONS.target_tmp = OPTIONS.input_tmp | 
 | 766 |   input_zip = zipfile.ZipFile(args[0], "r") | 
| Doug Zongker | 3797473 | 2010-09-16 17:44:38 -0700 | [diff] [blame] | 767 |   OPTIONS.info_dict = common.LoadInfoDict(input_zip) | 
 | 768 |   if OPTIONS.verbose: | 
 | 769 |     print "--- target info ---" | 
 | 770 |     common.DumpInfoDict(OPTIONS.info_dict) | 
 | 771 |  | 
 | 772 |   if OPTIONS.device_specific is None: | 
 | 773 |     OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) | 
 | 774 |   if OPTIONS.device_specific is not None: | 
 | 775 |     OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) | 
 | 776 |     print "using device-specific extensions in", OPTIONS.device_specific | 
 | 777 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 778 |   if OPTIONS.package_key: | 
 | 779 |     temp_zip_file = tempfile.NamedTemporaryFile() | 
 | 780 |     output_zip = zipfile.ZipFile(temp_zip_file, "w", | 
 | 781 |                                  compression=zipfile.ZIP_DEFLATED) | 
 | 782 |   else: | 
 | 783 |     output_zip = zipfile.ZipFile(args[1], "w", | 
 | 784 |                                  compression=zipfile.ZIP_DEFLATED) | 
 | 785 |  | 
 | 786 |   if OPTIONS.incremental_source is None: | 
| Doug Zongker | c77a9ad | 2010-09-16 11:28:43 -0700 | [diff] [blame] | 787 |     WriteFullOTAPackage(input_zip, output_zip) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 788 |   else: | 
 | 789 |     print "unzipping source target-files..." | 
 | 790 |     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source) | 
 | 791 |     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") | 
| Doug Zongker | 3797473 | 2010-09-16 17:44:38 -0700 | [diff] [blame] | 792 |     OPTIONS.target_info_dict = OPTIONS.info_dict | 
 | 793 |     OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) | 
 | 794 |     if OPTIONS.verbose: | 
 | 795 |       print "--- source info ---" | 
 | 796 |       common.DumpInfoDict(OPTIONS.source_info_dict) | 
| Doug Zongker | c77a9ad | 2010-09-16 11:28:43 -0700 | [diff] [blame] | 797 |     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 798 |  | 
 | 799 |   output_zip.close() | 
 | 800 |   if OPTIONS.package_key: | 
 | 801 |     SignOutput(temp_zip_file.name, args[1]) | 
 | 802 |     temp_zip_file.close() | 
 | 803 |  | 
 | 804 |   common.Cleanup() | 
 | 805 |  | 
 | 806 |   print "done." | 
 | 807 |  | 
 | 808 |  | 
 | 809 | if __name__ == '__main__': | 
 | 810 |   try: | 
 | 811 |     main(sys.argv[1:]) | 
 | 812 |   except common.ExternalError, e: | 
 | 813 |     print | 
 | 814 |     print "   ERROR: %s" % (e,) | 
 | 815 |     print | 
 | 816 |     sys.exit(1) |