ota_from_target_files.py revision c494d7cee85d980647ca915ea64355b71fe817eb
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 not m: 292 raise ExternalError("failed to find required bootloaders in " 293 "android-info.txt") 294 bootloaders = m.group(1).split("|") 295 script.AssertSomeBootloader(*bootloaders) 296 297 298def WriteFullOTAPackage(input_zip, output_zip): 299 if OPTIONS.script_mode in ("amend", "auto"): 300 script = amend_generator.AmendGenerator() 301 else: 302 # TODO: how to determine this? We don't know what version it will 303 # be installed on top of. For now, we expect the API just won't 304 # change very often. 305 script = edify_generator.EdifyGenerator(1) 306 307 if not OPTIONS.omit_prereq: 308 ts = GetBuildProp("ro.build.date.utc", input_zip) 309 script.AssertOlderBuild(ts) 310 311 AppendAssertions(script, input_zip) 312 313 script.ShowProgress(0.1, 0) 314 315 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image")) 316 script.WriteFirmwareImage("radio", "radio.img") 317 script.ShowProgress(0.5, 0) 318 319 if OPTIONS.wipe_user_data: 320 script.FormatPartition("userdata") 321 322 script.FormatPartition("system") 323 script.Mount("MTD", "system", "/system") 324 script.UnpackPackageDir("system", "/system") 325 326 symlinks = CopySystemFiles(input_zip, output_zip) 327 script.MakeSymlinks(symlinks) 328 329 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"), 330 "system/recovery.img", output_zip) 331 Item.Get("system/recovery.img", dir=False) 332 333 FixPermissions(script) 334 335 common.AddBoot(output_zip) 336 script.ShowProgress(0.2, 0) 337 338 script.WriteRawImage("boot", "boot.img") 339 script.ShowProgress(0.2, 10) 340 341 if OPTIONS.extra_script is not None: 342 script.AppendExtra(OPTIONS.extra_script) 343 344 script.AddToZip(input_zip, output_zip) 345 346 347class File(object): 348 def __init__(self, name, data): 349 self.name = name 350 self.data = data 351 self.size = len(data) 352 self.sha1 = sha.sha(data).hexdigest() 353 354 def WriteToTemp(self): 355 t = tempfile.NamedTemporaryFile() 356 t.write(self.data) 357 t.flush() 358 return t 359 360 def AddToZip(self, z): 361 common.ZipWriteStr(z, self.name, self.data) 362 363 364def LoadSystemFiles(z): 365 """Load all the files from SYSTEM/... in a given target-files 366 ZipFile, and return a dict of {filename: File object}.""" 367 out = {} 368 for info in z.infolist(): 369 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 370 fn = "system/" + info.filename[7:] 371 data = z.read(info.filename) 372 out[fn] = File(fn, data) 373 return out 374 375 376def Difference(tf, sf, diff_program): 377 """Return the patch (as a string of data) needed to turn sf into tf. 378 diff_program is the name of an external program (or list, if 379 additional arguments are desired) to run to generate the diff. 380 """ 381 382 ttemp = tf.WriteToTemp() 383 stemp = sf.WriteToTemp() 384 385 ext = os.path.splitext(tf.name)[1] 386 387 try: 388 ptemp = tempfile.NamedTemporaryFile() 389 if isinstance(diff_program, list): 390 cmd = copy.copy(diff_program) 391 else: 392 cmd = [diff_program] 393 cmd.append(stemp.name) 394 cmd.append(ttemp.name) 395 cmd.append(ptemp.name) 396 p = common.Run(cmd) 397 _, err = p.communicate() 398 if err or p.returncode != 0: 399 print "WARNING: failure running %s:\n%s\n" % (diff_program, err) 400 return None 401 diff = ptemp.read() 402 finally: 403 ptemp.close() 404 stemp.close() 405 ttemp.close() 406 407 return diff 408 409 410def GetBuildProp(property, z): 411 """Return the fingerprint of the build of a given target-files 412 ZipFile object.""" 413 bp = z.read("SYSTEM/build.prop") 414 if not property: 415 return bp 416 m = re.search(re.escape(property) + r"=(.*)\n", bp) 417 if not m: 418 raise ExternalException("couldn't find %s in build.prop" % (property,)) 419 return m.group(1).strip() 420 421 422def GetRecoveryAPIVersion(zip): 423 """Returns the version of the recovery API. Version 0 is the older 424 amend code (no separate binary).""" 425 try: 426 version = zip.read("META/recovery-api-version.txt") 427 return int(version) 428 except KeyError: 429 try: 430 # version one didn't have the recovery-api-version.txt file, but 431 # it did include an updater binary. 432 zip.getinfo("OTA/bin/updater") 433 return 1 434 except KeyError: 435 return 0 436 437def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 438 source_version = GetRecoveryAPIVersion(source_zip) 439 440 if OPTIONS.script_mode == 'amend': 441 script = amend_generator.AmendGenerator() 442 elif OPTIONS.script_mode == 'edify': 443 if source_version == 0: 444 print ("WARNING: generating edify script for a source that " 445 "can't install it.") 446 script = edify_generator.EdifyGenerator(source_version) 447 elif OPTIONS.script_mode == 'auto': 448 if source_version > 0: 449 script = edify_generator.EdifyGenerator(source_version) 450 else: 451 script = amend_generator.AmendGenerator() 452 else: 453 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) 454 455 print "Loading target..." 456 target_data = LoadSystemFiles(target_zip) 457 print "Loading source..." 458 source_data = LoadSystemFiles(source_zip) 459 460 verbatim_targets = [] 461 patch_list = [] 462 largest_source_size = 0 463 for fn in sorted(target_data.keys()): 464 tf = target_data[fn] 465 sf = source_data.get(fn, None) 466 467 if sf is None or fn in OPTIONS.require_verbatim: 468 # This file should be included verbatim 469 if fn in OPTIONS.prohibit_verbatim: 470 raise ExternalError("\"%s\" must be sent verbatim" % (fn,)) 471 print "send", fn, "verbatim" 472 tf.AddToZip(output_zip) 473 verbatim_targets.append((fn, tf.size)) 474 elif tf.sha1 != sf.sha1: 475 # File is different; consider sending as a patch 476 diff_method = "bsdiff" 477 if tf.name.endswith(".gz"): 478 diff_method = "imgdiff" 479 d = Difference(tf, sf, diff_method) 480 if d is not None: 481 print fn, tf.size, len(d), (float(len(d)) / tf.size) 482 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 483 # patch is almost as big as the file; don't bother patching 484 tf.AddToZip(output_zip) 485 verbatim_targets.append((fn, tf.size)) 486 else: 487 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d) 488 patch_list.append((fn, tf, sf, tf.size)) 489 largest_source_size = max(largest_source_size, sf.size) 490 else: 491 # Target file identical to source. 492 pass 493 494 total_verbatim_size = sum([i[1] for i in verbatim_targets]) 495 total_patched_size = sum([i[3] for i in patch_list]) 496 497 source_fp = GetBuildProp("ro.build.fingerprint", source_zip) 498 target_fp = GetBuildProp("ro.build.fingerprint", target_zip) 499 500 script.Mount("MTD", "system", "/system") 501 script.AssertSomeFingerprint(source_fp, target_fp) 502 503 source_boot = File("/tmp/boot.img", 504 common.BuildBootableImage( 505 os.path.join(OPTIONS.source_tmp, "BOOT"))) 506 target_boot = File("/tmp/boot.img", 507 common.BuildBootableImage( 508 os.path.join(OPTIONS.target_tmp, "BOOT"))) 509 updating_boot = (source_boot.data != target_boot.data) 510 511 source_recovery = File("system/recovery.img", 512 common.BuildBootableImage( 513 os.path.join(OPTIONS.source_tmp, "RECOVERY"))) 514 target_recovery = File("system/recovery.img", 515 common.BuildBootableImage( 516 os.path.join(OPTIONS.target_tmp, "RECOVERY"))) 517 updating_recovery = (source_recovery.data != target_recovery.data) 518 519 source_radio = source_zip.read("RADIO/image") 520 target_radio = target_zip.read("RADIO/image") 521 updating_radio = (source_radio != target_radio) 522 523 # The last 0.1 is reserved for creating symlinks, fixing 524 # permissions, and writing the boot image (if necessary). 525 progress_bar_total = 1.0 526 if updating_boot: 527 progress_bar_total -= 0.1 528 if updating_radio: 529 progress_bar_total -= 0.3 530 531 AppendAssertions(script, target_zip) 532 533 script.Print("Verifying current system...") 534 535 pb_verify = progress_bar_total * 0.3 * \ 536 (total_patched_size / 537 float(total_patched_size+total_verbatim_size+1)) 538 539 for i, (fn, tf, sf, size) in enumerate(patch_list): 540 if i % 5 == 0: 541 next_sizes = sum([i[3] for i in patch_list[i:i+5]]) 542 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1) 543 544 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 545 546 if updating_recovery: 547 d = Difference(target_recovery, source_recovery, "imgdiff") 548 print "recovery target: %d source: %d diff: %d" % ( 549 target_recovery.size, source_recovery.size, len(d)) 550 551 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d) 552 553 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" % 554 (source_recovery.size, source_recovery.sha1, 555 target_recovery.size, target_recovery.sha1)) 556 557 if updating_boot: 558 d = Difference(target_boot, source_boot, "imgdiff") 559 print "boot target: %d source: %d diff: %d" % ( 560 target_boot.size, source_boot.size, len(d)) 561 562 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 563 564 script.PatchCheck("MTD:boot:%d:%s:%d:%s" % 565 (source_boot.size, source_boot.sha1, 566 target_boot.size, target_boot.sha1)) 567 568 if patch_list or updating_recovery or updating_boot: 569 script.CacheFreeSpaceCheck(largest_source_size) 570 script.Print("Unpacking patches...") 571 script.UnpackPackageDir("patch", "/tmp/patchtmp") 572 573 script.Comment("---- start making changes here ----") 574 575 if OPTIONS.wipe_user_data: 576 script.Print("Erasing user data...") 577 script.FormatPartition("userdata") 578 579 script.Print("Removing unneeded files...") 580 script.DeleteFiles(["/"+i[0] for i in verbatim_targets]) 581 582 if updating_boot: 583 # Produce the boot image by applying a patch to the current 584 # contents of the boot partition, and write it back to the 585 # partition. 586 script.Print("Patching boot image...") 587 script.ApplyPatch("MTD:boot:%d:%s:%d:%s" 588 % (source_boot.size, source_boot.sha1, 589 target_boot.size, target_boot.sha1), 590 "-", 591 target_boot.size, target_boot.sha1, 592 source_boot.sha1, "/tmp/patchtmp/boot.img.p") 593 print "boot image changed; including." 594 else: 595 print "boot image unchanged; skipping." 596 597 if updating_recovery: 598 # Produce /system/recovery.img by applying a patch to the current 599 # contents of the recovery partition. 600 script.Print("Patching recovery image...") 601 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s" 602 % (source_recovery.size, source_recovery.sha1, 603 target_recovery.size, target_recovery.sha1), 604 "/system/recovery.img", 605 target_recovery.size, target_recovery.sha1, 606 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p") 607 print "recovery image changed; including." 608 else: 609 print "recovery image unchanged; skipping." 610 611 if updating_radio: 612 script.ShowProgress(0.3, 10) 613 script.Print("Writing radio image...") 614 script.WriteFirmwareImage("radio", "radio.img") 615 common.ZipWriteStr(output_zip, "radio.img", target_radio) 616 print "radio image changed; including." 617 else: 618 print "radio image unchanged; skipping." 619 620 script.Print("Patching system files...") 621 pb_apply = progress_bar_total * 0.7 * \ 622 (total_patched_size / 623 float(total_patched_size+total_verbatim_size+1)) 624 for i, (fn, tf, sf, size) in enumerate(patch_list): 625 if i % 5 == 0: 626 next_sizes = sum([i[3] for i in patch_list[i:i+5]]) 627 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1) 628 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, 629 sf.sha1, "/tmp/patchtmp/"+fn+".p") 630 631 target_symlinks = CopySystemFiles(target_zip, None) 632 633 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 634 temp_script = script.MakeTemporary() 635 FixPermissions(temp_script) 636 637 # Note that this call will mess up the tree of Items, so make sure 638 # we're done with it. 639 source_symlinks = CopySystemFiles(source_zip, None) 640 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 641 642 # Delete all the symlinks in source that aren't in target. This 643 # needs to happen before verbatim files are unpacked, in case a 644 # symlink in the source is replaced by a real file in the target. 645 to_delete = [] 646 for dest, link in source_symlinks: 647 if link not in target_symlinks_d: 648 to_delete.append(link) 649 script.DeleteFiles(to_delete) 650 651 if verbatim_targets: 652 pb_verbatim = progress_bar_total * \ 653 (total_verbatim_size / 654 float(total_patched_size+total_verbatim_size+1)) 655 script.ShowProgress(pb_verbatim, 5) 656 script.Print("Unpacking new files...") 657 script.UnpackPackageDir("system", "/system") 658 659 script.Print("Finishing up...") 660 661 # Create all the symlinks that don't already exist, or point to 662 # somewhere different than what we want. Delete each symlink before 663 # creating it, since the 'symlink' command won't overwrite. 664 to_create = [] 665 for dest, link in target_symlinks: 666 if link in source_symlinks_d: 667 if dest != source_symlinks_d[link]: 668 to_create.append((dest, link)) 669 else: 670 to_create.append((dest, link)) 671 script.DeleteFiles([i[1] for i in to_create]) 672 script.MakeSymlinks(to_create) 673 674 # Now that the symlinks are created, we can set all the 675 # permissions. 676 script.AppendScript(temp_script) 677 678 if OPTIONS.extra_script is not None: 679 scirpt.AppendExtra(OPTIONS.extra_script) 680 681 script.AddToZip(target_zip, output_zip) 682 683 684def main(argv): 685 686 def option_handler(o, a): 687 if o in ("-b", "--board_config"): 688 common.LoadBoardConfig(a) 689 elif o in ("-k", "--package_key"): 690 OPTIONS.package_key = a 691 elif o in ("-i", "--incremental_from"): 692 OPTIONS.incremental_source = a 693 elif o in ("-w", "--wipe_user_data"): 694 OPTIONS.wipe_user_data = True 695 elif o in ("-n", "--no_prereq"): 696 OPTIONS.omit_prereq = True 697 elif o in ("-e", "--extra_script"): 698 OPTIONS.extra_script = a 699 elif o in ("-m", "--script_mode"): 700 OPTIONS.script_mode = a 701 else: 702 return False 703 return True 704 705 args = common.ParseOptions(argv, __doc__, 706 extra_opts="b:k:i:d:wne:m:", 707 extra_long_opts=["board_config=", 708 "package_key=", 709 "incremental_from=", 710 "wipe_user_data", 711 "no_prereq", 712 "extra_script=", 713 "script_mode="], 714 extra_option_handler=option_handler) 715 716 if len(args) != 2: 717 common.Usage(__doc__) 718 sys.exit(1) 719 720 if not OPTIONS.max_image_size: 721 print 722 print " WARNING: No board config specified; will not check image" 723 print " sizes against limits. Use -b to make sure the generated" 724 print " images don't exceed partition sizes." 725 print 726 727 if OPTIONS.script_mode not in ("amend", "edify", "auto"): 728 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) 729 730 if OPTIONS.extra_script is not None: 731 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 732 733 print "unzipping target target-files..." 734 OPTIONS.input_tmp = common.UnzipTemp(args[0]) 735 OPTIONS.target_tmp = OPTIONS.input_tmp 736 input_zip = zipfile.ZipFile(args[0], "r") 737 if OPTIONS.package_key: 738 temp_zip_file = tempfile.NamedTemporaryFile() 739 output_zip = zipfile.ZipFile(temp_zip_file, "w", 740 compression=zipfile.ZIP_DEFLATED) 741 else: 742 output_zip = zipfile.ZipFile(args[1], "w", 743 compression=zipfile.ZIP_DEFLATED) 744 745 if OPTIONS.incremental_source is None: 746 WriteFullOTAPackage(input_zip, output_zip) 747 else: 748 print "unzipping source target-files..." 749 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source) 750 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") 751 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 752 753 output_zip.close() 754 if OPTIONS.package_key: 755 SignOutput(temp_zip_file.name, args[1]) 756 temp_zip_file.close() 757 758 common.Cleanup() 759 760 print "done." 761 762 763if __name__ == '__main__': 764 try: 765 main(sys.argv[1:]) 766 except common.ExternalError, e: 767 print 768 print " ERROR: %s" % (e,) 769 print 770 sys.exit(1) 771