ota_from_target_files.py revision 05d3dea519688b61d86e30c2d4b99ff494aeca73
1#!/usr/bin/env python 2# 3# Copyright (C) 2008 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Given a target-files zipfile, produces an OTA package that installs 19that build. An incremental OTA is produced if -i is given, otherwise 20a full OTA is produced. 21 22Usage: ota_from_target_files [flags] input_target_files output_ota_package 23 24 -b (--board_config) <file> 25 Specifies a BoardConfig.mk file containing image max sizes 26 against which the generated image files are checked. 27 28 -k (--package_key) <key> 29 Key to use to sign the package (default is 30 "build/target/product/security/testkey"). 31 32 -i (--incremental_from) <file> 33 Generate an incremental OTA using the given target-files zip as 34 the starting build. 35 36 -w (--wipe_user_data) 37 Generate an OTA package that will wipe the user data partition 38 when installed. 39 40 -n (--no_prereq) 41 Omit the timestamp prereq check normally included at the top of 42 the build scripts (used for developer OTA packages which 43 legitimately need to go back and forth). 44 45 -e (--extra_script) <file> 46 Insert the contents of file at the end of the update script. 47 48 -m (--script_mode) <mode> 49 Specify 'amend' or 'edify' scripts, or 'auto' to pick 50 automatically (this is the default). 51 52""" 53 54import sys 55 56if sys.hexversion < 0x02040000: 57 print >> sys.stderr, "Python 2.4 or newer is required." 58 sys.exit(1) 59 60import copy 61import os 62import re 63import sha 64import subprocess 65import tempfile 66import time 67import zipfile 68 69import common 70import amend_generator 71import edify_generator 72 73OPTIONS = common.OPTIONS 74OPTIONS.package_key = "build/target/product/security/testkey" 75OPTIONS.incremental_source = None 76OPTIONS.require_verbatim = set() 77OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 78OPTIONS.patch_threshold = 0.95 79OPTIONS.wipe_user_data = False 80OPTIONS.omit_prereq = False 81OPTIONS.extra_script = None 82OPTIONS.script_mode = 'auto' 83 84def MostPopularKey(d, default): 85 """Given a dict, return the key corresponding to the largest 86 value. Returns 'default' if the dict is empty.""" 87 x = [(v, k) for (k, v) in d.iteritems()] 88 if not x: return default 89 x.sort() 90 return x[-1][1] 91 92 93def IsSymlink(info): 94 """Return true if the zipfile.ZipInfo object passed in represents a 95 symlink.""" 96 return (info.external_attr >> 16) == 0120777 97 98 99 100class Item: 101 """Items represent the metadata (user, group, mode) of files and 102 directories in the system image.""" 103 ITEMS = {} 104 def __init__(self, name, dir=False): 105 self.name = name 106 self.uid = None 107 self.gid = None 108 self.mode = None 109 self.dir = dir 110 111 if name: 112 self.parent = Item.Get(os.path.dirname(name), dir=True) 113 self.parent.children.append(self) 114 else: 115 self.parent = None 116 if dir: 117 self.children = [] 118 119 def Dump(self, indent=0): 120 if self.uid is not None: 121 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) 122 else: 123 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) 124 if self.dir: 125 print "%s%s" % (" "*indent, self.descendants) 126 print "%s%s" % (" "*indent, self.best_subtree) 127 for i in self.children: 128 i.Dump(indent=indent+1) 129 130 @classmethod 131 def Get(cls, name, dir=False): 132 if name not in cls.ITEMS: 133 cls.ITEMS[name] = Item(name, dir=dir) 134 return cls.ITEMS[name] 135 136 @classmethod 137 def GetMetadata(cls): 138 """Run the external 'fs_config' program to determine the desired 139 uid, gid, and mode for every Item object.""" 140 p = common.Run(["fs_config"], stdin=subprocess.PIPE, 141 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 142 suffix = { False: "", True: "/" } 143 input = "".join(["%s%s\n" % (i.name, suffix[i.dir]) 144 for i in cls.ITEMS.itervalues() if i.name]) 145 output, error = p.communicate(input) 146 assert not error 147 148 for line in output.split("\n"): 149 if not line: continue 150 name, uid, gid, mode = line.split() 151 i = cls.ITEMS[name] 152 i.uid = int(uid) 153 i.gid = int(gid) 154 i.mode = int(mode, 8) 155 if i.dir: 156 i.children.sort(key=lambda i: i.name) 157 158 def CountChildMetadata(self): 159 """Count up the (uid, gid, mode) tuples for all children and 160 determine the best strategy for using set_perm_recursive and 161 set_perm to correctly chown/chmod all the files to their desired 162 values. Recursively calls itself for all descendants. 163 164 Returns a dict of {(uid, gid, dmode, fmode): count} counting up 165 all descendants of this node. (dmode or fmode may be None.) Also 166 sets the best_subtree of each directory Item to the (uid, gid, 167 dmode, fmode) tuple that will match the most descendants of that 168 Item. 169 """ 170 171 assert self.dir 172 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1} 173 for i in self.children: 174 if i.dir: 175 for k, v in i.CountChildMetadata().iteritems(): 176 d[k] = d.get(k, 0) + v 177 else: 178 k = (i.uid, i.gid, None, i.mode) 179 d[k] = d.get(k, 0) + 1 180 181 # Find the (uid, gid, dmode, fmode) tuple that matches the most 182 # descendants. 183 184 # First, find the (uid, gid) pair that matches the most 185 # descendants. 186 ug = {} 187 for (uid, gid, _, _), count in d.iteritems(): 188 ug[(uid, gid)] = ug.get((uid, gid), 0) + count 189 ug = MostPopularKey(ug, (0, 0)) 190 191 # Now find the dmode and fmode that match the most descendants 192 # with that (uid, gid), and choose those. 193 best_dmode = (0, 0755) 194 best_fmode = (0, 0644) 195 for k, count in d.iteritems(): 196 if k[:2] != ug: continue 197 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) 198 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) 199 self.best_subtree = ug + (best_dmode[1], best_fmode[1]) 200 201 return d 202 203 def SetPermissions(self, script): 204 """Append set_perm/set_perm_recursive commands to 'script' to 205 set all permissions, users, and groups for the tree of files 206 rooted at 'self'.""" 207 208 self.CountChildMetadata() 209 210 def recurse(item, current): 211 # current is the (uid, gid, dmode, fmode) tuple that the current 212 # item (and all its children) have already been set to. We only 213 # need to issue set_perm/set_perm_recursive commands if we're 214 # supposed to be something different. 215 if item.dir: 216 if current != item.best_subtree: 217 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) 218 current = item.best_subtree 219 220 if item.uid != current[0] or item.gid != current[1] or \ 221 item.mode != current[2]: 222 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) 223 224 for i in item.children: 225 recurse(i, current) 226 else: 227 if item.uid != current[0] or item.gid != current[1] or \ 228 item.mode != current[3]: 229 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) 230 231 recurse(self, (-1, -1, -1, -1)) 232 233 234def CopySystemFiles(input_zip, output_zip=None, 235 substitute=None): 236 """Copies files underneath system/ in the input zip to the output 237 zip. Populates the Item class with their metadata, and returns a 238 list of symlinks. output_zip may be None, in which case the copy is 239 skipped (but the other side effects still happen). substitute is an 240 optional dict of {output filename: contents} to be output instead of 241 certain input files. 242 """ 243 244 symlinks = [] 245 246 for info in input_zip.infolist(): 247 if info.filename.startswith("SYSTEM/"): 248 basefilename = info.filename[7:] 249 if IsSymlink(info): 250 symlinks.append((input_zip.read(info.filename), 251 "/system/" + basefilename)) 252 else: 253 info2 = copy.copy(info) 254 fn = info2.filename = "system/" + basefilename 255 if substitute and fn in substitute and substitute[fn] is None: 256 continue 257 if output_zip is not None: 258 if substitute and fn in substitute: 259 data = substitute[fn] 260 else: 261 data = input_zip.read(info.filename) 262 output_zip.writestr(info2, data) 263 if fn.endswith("/"): 264 Item.Get(fn[:-1], dir=True) 265 else: 266 Item.Get(fn, dir=False) 267 268 symlinks.sort() 269 return symlinks 270 271 272def SignOutput(temp_zip_name, output_zip_name): 273 key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 274 pw = key_passwords[OPTIONS.package_key] 275 276 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw) 277 278 279def FixPermissions(script): 280 Item.GetMetadata() 281 root = Item.Get("system") 282 root.SetPermissions(script) 283 284 285def AppendAssertions(script, input_zip): 286 device = GetBuildProp("ro.product.device", input_zip) 287 script.AssertDevice(device) 288 289 info = input_zip.read("OTA/android-info.txt") 290 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info) 291 if m: 292 bootloaders = m.group(1).split("|") 293 script.AssertSomeBootloader(*bootloaders) 294 295 296def WriteFullOTAPackage(input_zip, output_zip): 297 if OPTIONS.script_mode in ("amend", "auto"): 298 script = amend_generator.AmendGenerator() 299 else: 300 # TODO: how to determine this? We don't know what version it will 301 # be installed on top of. For now, we expect the API just won't 302 # change very often. 303 script = edify_generator.EdifyGenerator(1) 304 305 device_specific = common.DeviceSpecificParams( 306 input_zip=input_zip, 307 output_zip=output_zip, 308 script=script, 309 input_tmp=OPTIONS.input_tmp) 310 311 if not OPTIONS.omit_prereq: 312 ts = GetBuildProp("ro.build.date.utc", input_zip) 313 script.AssertOlderBuild(ts) 314 315 AppendAssertions(script, input_zip) 316 device_specific.FullOTA_Assertions() 317 318 script.ShowProgress(0.5, 0) 319 320 if OPTIONS.wipe_user_data: 321 script.FormatPartition("userdata") 322 323 script.FormatPartition("system") 324 script.Mount("MTD", "system", "/system") 325 script.UnpackPackageDir("system", "/system") 326 327 symlinks = CopySystemFiles(input_zip, output_zip) 328 script.MakeSymlinks(symlinks) 329 330 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"), 331 "system/recovery.img", output_zip) 332 Item.Get("system/recovery.img", dir=False) 333 334 FixPermissions(script) 335 336 common.AddBoot(output_zip) 337 338 script.ShowProgress(0.2, 10) 339 script.WriteRawImage("boot", "boot.img") 340 341 script.ShowProgress(0.1, 0) 342 device_specific.FullOTA_InstallEnd() 343 344 if OPTIONS.extra_script is not None: 345 script.AppendExtra(OPTIONS.extra_script) 346 347 script.AddToZip(input_zip, output_zip) 348 349 350class File(object): 351 def __init__(self, name, data): 352 self.name = name 353 self.data = data 354 self.size = len(data) 355 self.sha1 = sha.sha(data).hexdigest() 356 357 def WriteToTemp(self): 358 t = tempfile.NamedTemporaryFile() 359 t.write(self.data) 360 t.flush() 361 return t 362 363 def AddToZip(self, z): 364 common.ZipWriteStr(z, self.name, self.data) 365 366 367def LoadSystemFiles(z): 368 """Load all the files from SYSTEM/... in a given target-files 369 ZipFile, and return a dict of {filename: File object}.""" 370 out = {} 371 for info in z.infolist(): 372 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 373 fn = "system/" + info.filename[7:] 374 data = z.read(info.filename) 375 out[fn] = File(fn, data) 376 return out 377 378 379def Difference(tf, sf, diff_program): 380 """Return the patch (as a string of data) needed to turn sf into tf. 381 diff_program is the name of an external program (or list, if 382 additional arguments are desired) to run to generate the diff. 383 """ 384 385 ttemp = tf.WriteToTemp() 386 stemp = sf.WriteToTemp() 387 388 ext = os.path.splitext(tf.name)[1] 389 390 try: 391 ptemp = tempfile.NamedTemporaryFile() 392 if isinstance(diff_program, list): 393 cmd = copy.copy(diff_program) 394 else: 395 cmd = [diff_program] 396 cmd.append(stemp.name) 397 cmd.append(ttemp.name) 398 cmd.append(ptemp.name) 399 p = common.Run(cmd) 400 _, err = p.communicate() 401 if err or p.returncode != 0: 402 print "WARNING: failure running %s:\n%s\n" % (diff_program, err) 403 return None 404 diff = ptemp.read() 405 finally: 406 ptemp.close() 407 stemp.close() 408 ttemp.close() 409 410 return diff 411 412 413def GetBuildProp(property, z): 414 """Return the fingerprint of the build of a given target-files 415 ZipFile object.""" 416 bp = z.read("SYSTEM/build.prop") 417 if not property: 418 return bp 419 m = re.search(re.escape(property) + r"=(.*)\n", bp) 420 if not m: 421 raise ExternalException("couldn't find %s in build.prop" % (property,)) 422 return m.group(1).strip() 423 424 425def GetRecoveryAPIVersion(zip): 426 """Returns the version of the recovery API. Version 0 is the older 427 amend code (no separate binary).""" 428 try: 429 version = zip.read("META/recovery-api-version.txt") 430 return int(version) 431 except KeyError: 432 try: 433 # version one didn't have the recovery-api-version.txt file, but 434 # it did include an updater binary. 435 zip.getinfo("OTA/bin/updater") 436 return 1 437 except KeyError: 438 return 0 439 440def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 441 source_version = GetRecoveryAPIVersion(source_zip) 442 443 if OPTIONS.script_mode == 'amend': 444 script = amend_generator.AmendGenerator() 445 elif OPTIONS.script_mode == 'edify': 446 if source_version == 0: 447 print ("WARNING: generating edify script for a source that " 448 "can't install it.") 449 script = edify_generator.EdifyGenerator(source_version) 450 elif OPTIONS.script_mode == 'auto': 451 if source_version > 0: 452 script = edify_generator.EdifyGenerator(source_version) 453 else: 454 script = amend_generator.AmendGenerator() 455 else: 456 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) 457 458 device_specific = common.DeviceSpecificParams( 459 source_zip=source_zip, 460 target_zip=target_zip, 461 output_zip=output_zip, 462 script=script) 463 464 print "Loading target..." 465 target_data = LoadSystemFiles(target_zip) 466 print "Loading source..." 467 source_data = LoadSystemFiles(source_zip) 468 469 verbatim_targets = [] 470 patch_list = [] 471 largest_source_size = 0 472 for fn in sorted(target_data.keys()): 473 tf = target_data[fn] 474 sf = source_data.get(fn, None) 475 476 if sf is None or fn in OPTIONS.require_verbatim: 477 # This file should be included verbatim 478 if fn in OPTIONS.prohibit_verbatim: 479 raise ExternalError("\"%s\" must be sent verbatim" % (fn,)) 480 print "send", fn, "verbatim" 481 tf.AddToZip(output_zip) 482 verbatim_targets.append((fn, tf.size)) 483 elif tf.sha1 != sf.sha1: 484 # File is different; consider sending as a patch 485 diff_method = "bsdiff" 486 if tf.name.endswith(".gz"): 487 diff_method = "imgdiff" 488 d = Difference(tf, sf, diff_method) 489 if d is not None: 490 print fn, tf.size, len(d), (float(len(d)) / tf.size) 491 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 492 # patch is almost as big as the file; don't bother patching 493 tf.AddToZip(output_zip) 494 verbatim_targets.append((fn, tf.size)) 495 else: 496 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d) 497 patch_list.append((fn, tf, sf, tf.size)) 498 largest_source_size = max(largest_source_size, sf.size) 499 else: 500 # Target file identical to source. 501 pass 502 503 total_verbatim_size = sum([i[1] for i in verbatim_targets]) 504 total_patched_size = sum([i[3] for i in patch_list]) 505 506 source_fp = GetBuildProp("ro.build.fingerprint", source_zip) 507 target_fp = GetBuildProp("ro.build.fingerprint", target_zip) 508 509 script.Mount("MTD", "system", "/system") 510 script.AssertSomeFingerprint(source_fp, target_fp) 511 512 source_boot = File("/tmp/boot.img", 513 common.BuildBootableImage( 514 os.path.join(OPTIONS.source_tmp, "BOOT"))) 515 target_boot = File("/tmp/boot.img", 516 common.BuildBootableImage( 517 os.path.join(OPTIONS.target_tmp, "BOOT"))) 518 updating_boot = (source_boot.data != target_boot.data) 519 520 source_recovery = File("system/recovery.img", 521 common.BuildBootableImage( 522 os.path.join(OPTIONS.source_tmp, "RECOVERY"))) 523 target_recovery = File("system/recovery.img", 524 common.BuildBootableImage( 525 os.path.join(OPTIONS.target_tmp, "RECOVERY"))) 526 updating_recovery = (source_recovery.data != target_recovery.data) 527 528 # We reserve the last 0.3 of the progress bar for the 529 # device-specific IncrementalOTA_InstallEnd() call at the end, which 530 # will typically install a radio image. 531 progress_bar_total = 0.7 532 if updating_boot: 533 progress_bar_total -= 0.1 534 535 AppendAssertions(script, target_zip) 536 device_specific.IncrementalOTA_Assertions() 537 538 script.Print("Verifying current system...") 539 540 pb_verify = progress_bar_total * 0.3 * \ 541 (total_patched_size / 542 float(total_patched_size+total_verbatim_size+1)) 543 544 for i, (fn, tf, sf, size) in enumerate(patch_list): 545 if i % 5 == 0: 546 next_sizes = sum([i[3] for i in patch_list[i:i+5]]) 547 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1) 548 549 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 550 551 if updating_recovery: 552 d = Difference(target_recovery, source_recovery, "imgdiff") 553 print "recovery target: %d source: %d diff: %d" % ( 554 target_recovery.size, source_recovery.size, len(d)) 555 556 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d) 557 558 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" % 559 (source_recovery.size, source_recovery.sha1, 560 target_recovery.size, target_recovery.sha1)) 561 562 if updating_boot: 563 d = Difference(target_boot, source_boot, "imgdiff") 564 print "boot target: %d source: %d diff: %d" % ( 565 target_boot.size, source_boot.size, len(d)) 566 567 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 568 569 script.PatchCheck("MTD:boot:%d:%s:%d:%s" % 570 (source_boot.size, source_boot.sha1, 571 target_boot.size, target_boot.sha1)) 572 573 if patch_list or updating_recovery or updating_boot: 574 script.CacheFreeSpaceCheck(largest_source_size) 575 script.Print("Unpacking patches...") 576 script.UnpackPackageDir("patch", "/tmp/patchtmp") 577 578 device_specific.IncrementalOTA_VerifyEnd() 579 580 script.Comment("---- start making changes here ----") 581 582 if OPTIONS.wipe_user_data: 583 script.Print("Erasing user data...") 584 script.FormatPartition("userdata") 585 586 script.Print("Removing unneeded files...") 587 script.DeleteFiles(["/"+i[0] for i in verbatim_targets]) 588 589 if updating_boot: 590 # Produce the boot image by applying a patch to the current 591 # contents of the boot partition, and write it back to the 592 # partition. 593 script.Print("Patching boot image...") 594 script.ApplyPatch("MTD:boot:%d:%s:%d:%s" 595 % (source_boot.size, source_boot.sha1, 596 target_boot.size, target_boot.sha1), 597 "-", 598 target_boot.size, target_boot.sha1, 599 source_boot.sha1, "/tmp/patchtmp/boot.img.p") 600 print "boot image changed; including." 601 else: 602 print "boot image unchanged; skipping." 603 604 if updating_recovery: 605 # Produce /system/recovery.img by applying a patch to the current 606 # contents of the recovery partition. 607 script.Print("Patching recovery image...") 608 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s" 609 % (source_recovery.size, source_recovery.sha1, 610 target_recovery.size, target_recovery.sha1), 611 "/system/recovery.img", 612 target_recovery.size, target_recovery.sha1, 613 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p") 614 print "recovery image changed; including." 615 else: 616 print "recovery image unchanged; skipping." 617 618 script.Print("Patching system files...") 619 pb_apply = progress_bar_total * 0.7 * \ 620 (total_patched_size / 621 float(total_patched_size+total_verbatim_size+1)) 622 for i, (fn, tf, sf, size) in enumerate(patch_list): 623 if i % 5 == 0: 624 next_sizes = sum([i[3] for i in patch_list[i:i+5]]) 625 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1) 626 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, 627 sf.sha1, "/tmp/patchtmp/"+fn+".p") 628 629 target_symlinks = CopySystemFiles(target_zip, None) 630 631 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 632 temp_script = script.MakeTemporary() 633 FixPermissions(temp_script) 634 635 # Note that this call will mess up the tree of Items, so make sure 636 # we're done with it. 637 source_symlinks = CopySystemFiles(source_zip, None) 638 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 639 640 # Delete all the symlinks in source that aren't in target. This 641 # needs to happen before verbatim files are unpacked, in case a 642 # symlink in the source is replaced by a real file in the target. 643 to_delete = [] 644 for dest, link in source_symlinks: 645 if link not in target_symlinks_d: 646 to_delete.append(link) 647 script.DeleteFiles(to_delete) 648 649 if verbatim_targets: 650 pb_verbatim = progress_bar_total * \ 651 (total_verbatim_size / 652 float(total_patched_size+total_verbatim_size+1)) 653 script.ShowProgress(pb_verbatim, 5) 654 script.Print("Unpacking new files...") 655 script.UnpackPackageDir("system", "/system") 656 657 script.Print("Symlinks and permissions...") 658 659 # Create all the symlinks that don't already exist, or point to 660 # somewhere different than what we want. Delete each symlink before 661 # creating it, since the 'symlink' command won't overwrite. 662 to_create = [] 663 for dest, link in target_symlinks: 664 if link in source_symlinks_d: 665 if dest != source_symlinks_d[link]: 666 to_create.append((dest, link)) 667 else: 668 to_create.append((dest, link)) 669 script.DeleteFiles([i[1] for i in to_create]) 670 script.MakeSymlinks(to_create) 671 672 # Now that the symlinks are created, we can set all the 673 # permissions. 674 script.AppendScript(temp_script) 675 676 # Write the radio image, if necessary. 677 script.ShowProgress(0.3, 10) 678 device_specific.IncrementalOTA_InstallEnd() 679 680 if OPTIONS.extra_script is not None: 681 scirpt.AppendExtra(OPTIONS.extra_script) 682 683 script.AddToZip(target_zip, output_zip) 684 685 686def main(argv): 687 688 def option_handler(o, a): 689 if o in ("-b", "--board_config"): 690 common.LoadBoardConfig(a) 691 elif o in ("-k", "--package_key"): 692 OPTIONS.package_key = a 693 elif o in ("-i", "--incremental_from"): 694 OPTIONS.incremental_source = a 695 elif o in ("-w", "--wipe_user_data"): 696 OPTIONS.wipe_user_data = True 697 elif o in ("-n", "--no_prereq"): 698 OPTIONS.omit_prereq = True 699 elif o in ("-e", "--extra_script"): 700 OPTIONS.extra_script = a 701 elif o in ("-m", "--script_mode"): 702 OPTIONS.script_mode = a 703 else: 704 return False 705 return True 706 707 args = common.ParseOptions(argv, __doc__, 708 extra_opts="b:k:i:d:wne:m:", 709 extra_long_opts=["board_config=", 710 "package_key=", 711 "incremental_from=", 712 "wipe_user_data", 713 "no_prereq", 714 "extra_script=", 715 "script_mode="], 716 extra_option_handler=option_handler) 717 718 if len(args) != 2: 719 common.Usage(__doc__) 720 sys.exit(1) 721 722 if not OPTIONS.max_image_size: 723 print 724 print " WARNING: No board config specified; will not check image" 725 print " sizes against limits. Use -b to make sure the generated" 726 print " images don't exceed partition sizes." 727 print 728 729 if OPTIONS.script_mode not in ("amend", "edify", "auto"): 730 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) 731 732 if OPTIONS.extra_script is not None: 733 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 734 735 print "unzipping target target-files..." 736 OPTIONS.input_tmp = common.UnzipTemp(args[0]) 737 OPTIONS.target_tmp = OPTIONS.input_tmp 738 input_zip = zipfile.ZipFile(args[0], "r") 739 if OPTIONS.package_key: 740 temp_zip_file = tempfile.NamedTemporaryFile() 741 output_zip = zipfile.ZipFile(temp_zip_file, "w", 742 compression=zipfile.ZIP_DEFLATED) 743 else: 744 output_zip = zipfile.ZipFile(args[1], "w", 745 compression=zipfile.ZIP_DEFLATED) 746 747 if OPTIONS.incremental_source is None: 748 WriteFullOTAPackage(input_zip, output_zip) 749 else: 750 print "unzipping source target-files..." 751 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source) 752 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") 753 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 754 755 output_zip.close() 756 if OPTIONS.package_key: 757 SignOutput(temp_zip_file.name, args[1]) 758 temp_zip_file.close() 759 760 common.Cleanup() 761 762 print "done." 763 764 765if __name__ == '__main__': 766 try: 767 main(sys.argv[1:]) 768 except common.ExternalError, e: 769 print 770 print " ERROR: %s" % (e,) 771 print 772 sys.exit(1) 773