ota_from_target_files.py revision 048e7ca15f6391681490ce564bc71194adf146aa
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""" 49 50import sys 51 52if sys.hexversion < 0x02040000: 53 print >> sys.stderr, "Python 2.4 or newer is required." 54 sys.exit(1) 55 56import copy 57import os 58import re 59import sha 60import subprocess 61import tempfile 62import time 63import zipfile 64 65import common 66 67OPTIONS = common.OPTIONS 68OPTIONS.package_key = "build/target/product/security/testkey" 69OPTIONS.incremental_source = None 70OPTIONS.require_verbatim = set() 71OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 72OPTIONS.patch_threshold = 0.95 73OPTIONS.wipe_user_data = False 74OPTIONS.omit_prereq = False 75OPTIONS.extra_script = None 76 77def MostPopularKey(d, default): 78 """Given a dict, return the key corresponding to the largest 79 value. Returns 'default' if the dict is empty.""" 80 x = [(v, k) for (k, v) in d.iteritems()] 81 if not x: return default 82 x.sort() 83 return x[-1][1] 84 85 86def IsSymlink(info): 87 """Return true if the zipfile.ZipInfo object passed in represents a 88 symlink.""" 89 return (info.external_attr >> 16) == 0120777 90 91 92 93class Item: 94 """Items represent the metadata (user, group, mode) of files and 95 directories in the system image.""" 96 ITEMS = {} 97 def __init__(self, name, dir=False): 98 self.name = name 99 self.uid = None 100 self.gid = None 101 self.mode = None 102 self.dir = dir 103 104 if name: 105 self.parent = Item.Get(os.path.dirname(name), dir=True) 106 self.parent.children.append(self) 107 else: 108 self.parent = None 109 if dir: 110 self.children = [] 111 112 def Dump(self, indent=0): 113 if self.uid is not None: 114 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) 115 else: 116 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) 117 if self.dir: 118 print "%s%s" % (" "*indent, self.descendants) 119 print "%s%s" % (" "*indent, self.best_subtree) 120 for i in self.children: 121 i.Dump(indent=indent+1) 122 123 @classmethod 124 def Get(cls, name, dir=False): 125 if name not in cls.ITEMS: 126 cls.ITEMS[name] = Item(name, dir=dir) 127 return cls.ITEMS[name] 128 129 @classmethod 130 def GetMetadata(cls): 131 """Run the external 'fs_config' program to determine the desired 132 uid, gid, and mode for every Item object.""" 133 p = common.Run(["fs_config"], stdin=subprocess.PIPE, 134 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 135 suffix = { False: "", True: "/" } 136 input = "".join(["%s%s\n" % (i.name, suffix[i.dir]) 137 for i in cls.ITEMS.itervalues() if i.name]) 138 output, error = p.communicate(input) 139 assert not error 140 141 for line in output.split("\n"): 142 if not line: continue 143 name, uid, gid, mode = line.split() 144 i = cls.ITEMS[name] 145 i.uid = int(uid) 146 i.gid = int(gid) 147 i.mode = int(mode, 8) 148 if i.dir: 149 i.children.sort(key=lambda i: i.name) 150 151 def CountChildMetadata(self): 152 """Count up the (uid, gid, mode) tuples for all children and 153 determine the best strategy for using set_perm_recursive and 154 set_perm to correctly chown/chmod all the files to their desired 155 values. Recursively calls itself for all descendants. 156 157 Returns a dict of {(uid, gid, dmode, fmode): count} counting up 158 all descendants of this node. (dmode or fmode may be None.) Also 159 sets the best_subtree of each directory Item to the (uid, gid, 160 dmode, fmode) tuple that will match the most descendants of that 161 Item. 162 """ 163 164 assert self.dir 165 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1} 166 for i in self.children: 167 if i.dir: 168 for k, v in i.CountChildMetadata().iteritems(): 169 d[k] = d.get(k, 0) + v 170 else: 171 k = (i.uid, i.gid, None, i.mode) 172 d[k] = d.get(k, 0) + 1 173 174 # Find the (uid, gid, dmode, fmode) tuple that matches the most 175 # descendants. 176 177 # First, find the (uid, gid) pair that matches the most 178 # descendants. 179 ug = {} 180 for (uid, gid, _, _), count in d.iteritems(): 181 ug[(uid, gid)] = ug.get((uid, gid), 0) + count 182 ug = MostPopularKey(ug, (0, 0)) 183 184 # Now find the dmode and fmode that match the most descendants 185 # with that (uid, gid), and choose those. 186 best_dmode = (0, 0755) 187 best_fmode = (0, 0644) 188 for k, count in d.iteritems(): 189 if k[:2] != ug: continue 190 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) 191 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) 192 self.best_subtree = ug + (best_dmode[1], best_fmode[1]) 193 194 return d 195 196 def SetPermissions(self, script, renamer=lambda x: x): 197 """Append set_perm/set_perm_recursive commands to 'script' to 198 set all permissions, users, and groups for the tree of files 199 rooted at 'self'. 'renamer' turns the filenames stored in the 200 tree of Items into the strings used in the script.""" 201 202 self.CountChildMetadata() 203 204 def recurse(item, current): 205 # current is the (uid, gid, dmode, fmode) tuple that the current 206 # item (and all its children) have already been set to. We only 207 # need to issue set_perm/set_perm_recursive commands if we're 208 # supposed to be something different. 209 if item.dir: 210 if current != item.best_subtree: 211 script.append("set_perm_recursive %d %d 0%o 0%o %s" % 212 (item.best_subtree + (renamer(item.name),))) 213 current = item.best_subtree 214 215 if item.uid != current[0] or item.gid != current[1] or \ 216 item.mode != current[2]: 217 script.append("set_perm %d %d 0%o %s" % 218 (item.uid, item.gid, item.mode, renamer(item.name))) 219 220 for i in item.children: 221 recurse(i, current) 222 else: 223 if item.uid != current[0] or item.gid != current[1] or \ 224 item.mode != current[3]: 225 script.append("set_perm %d %d 0%o %s" % 226 (item.uid, item.gid, item.mode, renamer(item.name))) 227 228 recurse(self, (-1, -1, -1, -1)) 229 230 231def CopySystemFiles(input_zip, output_zip=None, 232 substitute=None): 233 """Copies files underneath system/ in the input zip to the output 234 zip. Populates the Item class with their metadata, and returns a 235 list of symlinks. output_zip may be None, in which case the copy is 236 skipped (but the other side effects still happen). substitute is an 237 optional dict of {output filename: contents} to be output instead of 238 certain input files. 239 """ 240 241 symlinks = [] 242 243 for info in input_zip.infolist(): 244 if info.filename.startswith("SYSTEM/"): 245 basefilename = info.filename[7:] 246 if IsSymlink(info): 247 symlinks.append((input_zip.read(info.filename), 248 "SYSTEM:" + basefilename)) 249 else: 250 info2 = copy.copy(info) 251 fn = info2.filename = "system/" + basefilename 252 if substitute and fn in substitute and substitute[fn] is None: 253 continue 254 if output_zip is not None: 255 if substitute and fn in substitute: 256 data = substitute[fn] 257 else: 258 data = input_zip.read(info.filename) 259 output_zip.writestr(info2, data) 260 if fn.endswith("/"): 261 Item.Get(fn[:-1], dir=True) 262 else: 263 Item.Get(fn, dir=False) 264 265 symlinks.sort() 266 return symlinks 267 268 269def AddScript(script, output_zip): 270 common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-script", 271 "\n".join(script) + "\n") 272 273 274def SignOutput(temp_zip_name, output_zip_name): 275 key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 276 pw = key_passwords[OPTIONS.package_key] 277 278 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw) 279 280 281def SubstituteRoot(s): 282 if s == "system": return "SYSTEM:" 283 assert s.startswith("system/") 284 return "SYSTEM:" + s[7:] 285 286def FixPermissions(script): 287 Item.GetMetadata() 288 root = Item.Get("system") 289 root.SetPermissions(script, renamer=SubstituteRoot) 290 291def DeleteFiles(script, to_delete): 292 line = [] 293 t = 0 294 for i in to_delete: 295 line.append(i) 296 t += len(i) + 1 297 if t > 80: 298 script.append("delete " + " ".join(line)) 299 line = [] 300 t = 0 301 if line: 302 script.append("delete " + " ".join(line)) 303 304def AppendAssertions(script, input_zip): 305 script.append('assert compatible_with("0.2") == "true"') 306 307 device = GetBuildProp("ro.product.device", input_zip) 308 script.append('assert getprop("ro.product.device") == "%s" || ' 309 'getprop("ro.build.product") == "%s"' % (device, device)) 310 311 info = input_zip.read("OTA/android-info.txt") 312 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info) 313 if not m: 314 raise ExternalError("failed to find required bootloaders in " 315 "android-info.txt") 316 bootloaders = m.group(1).split("|") 317 script.append("assert " + 318 " || ".join(['getprop("ro.bootloader") == "%s"' % (b,) 319 for b in bootloaders])) 320 321 322def IncludeBinary(name, input_zip, output_zip, input_path=None): 323 try: 324 if input_path is not None: 325 data = open(input_path).read() 326 else: 327 data = input_zip.read(os.path.join("OTA/bin", name)) 328 common.ZipWriteStr(output_zip, name, data, perms=0755) 329 except IOError: 330 raise ExternalError('unable to include device binary "%s"' % (name,)) 331 332 333def WriteFullOTAPackage(input_zip, output_zip): 334 script = [] 335 336 if not OPTIONS.omit_prereq: 337 ts = GetBuildProp("ro.build.date.utc", input_zip) 338 script.append("run_program PACKAGE:check_prereq %s" % (ts,)) 339 IncludeBinary("check_prereq", input_zip, output_zip) 340 341 AppendAssertions(script, input_zip) 342 343 script.append("format BOOT:") 344 script.append("show_progress 0.1 0") 345 346 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image")) 347 script.append("write_radio_image PACKAGE:radio.img") 348 script.append("show_progress 0.5 0") 349 350 if OPTIONS.wipe_user_data: 351 script.append("format DATA:") 352 353 script.append("format SYSTEM:") 354 script.append("copy_dir PACKAGE:system SYSTEM:") 355 356 symlinks = CopySystemFiles(input_zip, output_zip) 357 script.extend(["symlink %s %s" % s for s in symlinks]) 358 359 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"), 360 "system/recovery.img", output_zip) 361 Item.Get("system/recovery.img", dir=False) 362 363 FixPermissions(script) 364 365 common.AddBoot(output_zip) 366 script.append("show_progress 0.2 0") 367 script.append("write_raw_image PACKAGE:boot.img BOOT:") 368 script.append("show_progress 0.2 10") 369 370 if OPTIONS.extra_script is not None: 371 script.append(OPTIONS.extra_script) 372 373 AddScript(script, output_zip) 374 375 376class File(object): 377 def __init__(self, name, data): 378 self.name = name 379 self.data = data 380 self.size = len(data) 381 self.sha1 = sha.sha(data).hexdigest() 382 383 def WriteToTemp(self): 384 t = tempfile.NamedTemporaryFile() 385 t.write(self.data) 386 t.flush() 387 return t 388 389 def AddToZip(self, z): 390 common.ZipWriteStr(z, self.name, self.data) 391 392 393def LoadSystemFiles(z): 394 """Load all the files from SYSTEM/... in a given target-files 395 ZipFile, and return a dict of {filename: File object}.""" 396 out = {} 397 for info in z.infolist(): 398 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 399 fn = "system/" + info.filename[7:] 400 data = z.read(info.filename) 401 out[fn] = File(fn, data) 402 return out 403 404 405def Difference(tf, sf, diff_program): 406 """Return the patch (as a string of data) needed to turn sf into tf. 407 diff_program is the name of an external program (or list, if 408 additional arguments are desired) to run to generate the diff. 409 """ 410 411 ttemp = tf.WriteToTemp() 412 stemp = sf.WriteToTemp() 413 414 ext = os.path.splitext(tf.name)[1] 415 416 try: 417 ptemp = tempfile.NamedTemporaryFile() 418 if isinstance(diff_program, list): 419 cmd = copy.copy(diff_program) 420 else: 421 cmd = [diff_program] 422 cmd.append(stemp.name) 423 cmd.append(ttemp.name) 424 cmd.append(ptemp.name) 425 p = common.Run(cmd) 426 _, err = p.communicate() 427 if err or p.returncode != 0: 428 print "WARNING: failure running %s:\n%s\n" % (diff_program, err) 429 return None 430 diff = ptemp.read() 431 finally: 432 ptemp.close() 433 stemp.close() 434 ttemp.close() 435 436 return diff 437 438 439def GetBuildProp(property, z): 440 """Return the fingerprint of the build of a given target-files 441 ZipFile object.""" 442 bp = z.read("SYSTEM/build.prop") 443 if not property: 444 return bp 445 m = re.search(re.escape(property) + r"=(.*)\n", bp) 446 if not m: 447 raise ExternalException("couldn't find %s in build.prop" % (property,)) 448 return m.group(1).strip() 449 450 451def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 452 script = [] 453 454 print "Loading target..." 455 target_data = LoadSystemFiles(target_zip) 456 print "Loading source..." 457 source_data = LoadSystemFiles(source_zip) 458 459 verbatim_targets = [] 460 patch_list = [] 461 largest_source_size = 0 462 for fn in sorted(target_data.keys()): 463 tf = target_data[fn] 464 sf = source_data.get(fn, None) 465 466 if sf is None or fn in OPTIONS.require_verbatim: 467 # This file should be included verbatim 468 if fn in OPTIONS.prohibit_verbatim: 469 raise ExternalError("\"%s\" must be sent verbatim" % (fn,)) 470 print "send", fn, "verbatim" 471 tf.AddToZip(output_zip) 472 verbatim_targets.append((fn, tf.size)) 473 elif tf.sha1 != sf.sha1: 474 # File is different; consider sending as a patch 475 diff_method = "bsdiff" 476 if tf.name.endswith(".gz"): 477 diff_method = "imgdiff" 478 d = Difference(tf, sf, diff_method) 479 if d is not None: 480 print fn, tf.size, len(d), (float(len(d)) / tf.size) 481 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 482 # patch is almost as big as the file; don't bother patching 483 tf.AddToZip(output_zip) 484 verbatim_targets.append((fn, tf.size)) 485 else: 486 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d) 487 patch_list.append((fn, tf, sf, tf.size)) 488 largest_source_size = max(largest_source_size, sf.size) 489 else: 490 # Target file identical to source. 491 pass 492 493 total_verbatim_size = sum([i[1] for i in verbatim_targets]) 494 total_patched_size = sum([i[3] for i in patch_list]) 495 496 source_fp = GetBuildProp("ro.build.fingerprint", source_zip) 497 target_fp = GetBuildProp("ro.build.fingerprint", target_zip) 498 499 script.append(('assert file_contains("SYSTEM:build.prop", ' 500 '"ro.build.fingerprint=%s") == "true" || ' 501 'file_contains("SYSTEM:build.prop", ' 502 '"ro.build.fingerprint=%s") == "true"') % 503 (source_fp, target_fp)) 504 505 source_boot = File("/tmp/boot.img", 506 common.BuildBootableImage( 507 os.path.join(OPTIONS.source_tmp, "BOOT"))) 508 target_boot = File("/tmp/boot.img", 509 common.BuildBootableImage( 510 os.path.join(OPTIONS.target_tmp, "BOOT"))) 511 updating_boot = (source_boot.data != target_boot.data) 512 513 source_recovery = File("system/recovery.img", 514 common.BuildBootableImage( 515 os.path.join(OPTIONS.source_tmp, "RECOVERY"))) 516 target_recovery = File("system/recovery.img", 517 common.BuildBootableImage( 518 os.path.join(OPTIONS.target_tmp, "RECOVERY"))) 519 updating_recovery = (source_recovery.data != target_recovery.data) 520 521 source_radio = source_zip.read("RADIO/image") 522 target_radio = target_zip.read("RADIO/image") 523 updating_radio = (source_radio != target_radio) 524 525 # The last 0.1 is reserved for creating symlinks, fixing 526 # permissions, and writing the boot image (if necessary). 527 progress_bar_total = 1.0 528 if updating_boot: 529 progress_bar_total -= 0.1 530 if updating_radio: 531 progress_bar_total -= 0.3 532 533 AppendAssertions(script, target_zip) 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.append("show_progress %f 1" % 543 (next_sizes * pb_verify / (total_patched_size+1),)) 544 script.append("run_program PACKAGE:applypatch -c /%s %s %s" % 545 (fn, tf.sha1, sf.sha1)) 546 547 if updating_recovery: 548 d = Difference(target_recovery, source_recovery, "imgdiff") 549 print "recovery target: %d source: %d diff: %d" % ( 550 target_recovery.size, source_recovery.size, len(d)) 551 552 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d) 553 554 script.append(("run_program PACKAGE:applypatch -c " 555 "MTD:recovery:%d:%s:%d:%s") % 556 (source_recovery.size, source_recovery.sha1, 557 target_recovery.size, target_recovery.sha1)) 558 559 if updating_boot: 560 d = Difference(target_boot, source_boot, "imgdiff") 561 print "boot target: %d source: %d diff: %d" % ( 562 target_boot.size, source_boot.size, len(d)) 563 564 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 565 566 script.append(("run_program PACKAGE:applypatch -c " 567 "MTD:boot:%d:%s:%d:%s") % 568 (source_boot.size, source_boot.sha1, 569 target_boot.size, target_boot.sha1)) 570 571 if patch_list or updating_recovery or updating_boot: 572 script.append("run_program PACKAGE:applypatch -s %d" % 573 (largest_source_size,)) 574 script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp") 575 IncludeBinary("applypatch", target_zip, output_zip) 576 577 script.append("\n# ---- start making changes here\n") 578 579 if OPTIONS.wipe_user_data: 580 script.append("format DATA:") 581 582 DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets]) 583 584 if updating_boot: 585 # Produce the boot image by applying a patch to the current 586 # contents of the boot partition, and write it back to the 587 # partition. 588 script.append(("run_program PACKAGE:applypatch " 589 "MTD:boot:%d:%s:%d:%s - " 590 "%s %d %s:/tmp/patchtmp/boot.img.p") 591 % (source_boot.size, source_boot.sha1, 592 target_boot.size, target_boot.sha1, 593 target_boot.sha1, 594 target_boot.size, 595 source_boot.sha1)) 596 print "boot image changed; including." 597 else: 598 print "boot image unchanged; skipping." 599 600 if updating_recovery: 601 # Produce /system/recovery.img by applying a patch to the current 602 # contents of the recovery partition. 603 script.append(("run_program PACKAGE:applypatch MTD:recovery:%d:%s:%d:%s " 604 "/system/recovery.img %s %d %s:/tmp/patchtmp/recovery.img.p") 605 % (source_recovery.size, source_recovery.sha1, 606 target_recovery.size, target_recovery.sha1, 607 target_recovery.sha1, 608 target_recovery.size, 609 source_recovery.sha1)) 610 print "recovery image changed; including." 611 else: 612 print "recovery image unchanged; skipping." 613 614 if updating_radio: 615 script.append("show_progress 0.3 10") 616 script.append("write_radio_image PACKAGE:radio.img") 617 common.ZipWriteStr(output_zip, "radio.img", target_radio) 618 print "radio image changed; including." 619 else: 620 print "radio image unchanged; skipping." 621 622 pb_apply = progress_bar_total * 0.7 * \ 623 (total_patched_size / 624 float(total_patched_size+total_verbatim_size+1)) 625 for i, (fn, tf, sf, size) in enumerate(patch_list): 626 if i % 5 == 0: 627 next_sizes = sum([i[3] for i in patch_list[i:i+5]]) 628 script.append("show_progress %f 1" % 629 (next_sizes * pb_apply / (total_patched_size+1),)) 630 script.append(("run_program PACKAGE:applypatch " 631 "/%s - %s %d %s:/tmp/patchtmp/%s.p") % 632 (fn, tf.sha1, tf.size, sf.sha1, fn)) 633 634 target_symlinks = CopySystemFiles(target_zip, None) 635 636 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 637 temp_script = [] 638 FixPermissions(temp_script) 639 640 # Note that this call will mess up the tree of Items, so make sure 641 # we're done with it. 642 source_symlinks = CopySystemFiles(source_zip, None) 643 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 644 645 # Delete all the symlinks in source that aren't in target. This 646 # needs to happen before verbatim files are unpacked, in case a 647 # symlink in the source is replaced by a real file in the target. 648 to_delete = [] 649 for dest, link in source_symlinks: 650 if link not in target_symlinks_d: 651 to_delete.append(link) 652 DeleteFiles(script, to_delete) 653 654 if verbatim_targets: 655 pb_verbatim = progress_bar_total * \ 656 (total_verbatim_size / 657 float(total_patched_size+total_verbatim_size+1)) 658 script.append("show_progress %f 5" % (pb_verbatim,)) 659 script.append("copy_dir PACKAGE:system SYSTEM:") 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 DeleteFiles(script, [i[1] for i in to_create]) 672 script.extend(["symlink %s %s" % s for s in to_create]) 673 674 # Now that the symlinks are created, we can set all the 675 # permissions. 676 script.extend(temp_script) 677 678 if OPTIONS.extra_script is not None: 679 script.append(OPTIONS.extra_script) 680 681 AddScript(script, 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 else: 700 return False 701 return True 702 703 args = common.ParseOptions(argv, __doc__, 704 extra_opts="b:k:i:d:wne:", 705 extra_long_opts=["board_config=", 706 "package_key=", 707 "incremental_from=", 708 "wipe_user_data", 709 "no_prereq", 710 "extra_script="], 711 extra_option_handler=option_handler) 712 713 if len(args) != 2: 714 common.Usage(__doc__) 715 sys.exit(1) 716 717 if not OPTIONS.max_image_size: 718 print 719 print " WARNING: No board config specified; will not check image" 720 print " sizes against limits. Use -b to make sure the generated" 721 print " images don't exceed partition sizes." 722 print 723 724 if OPTIONS.extra_script is not None: 725 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 726 727 print "unzipping target target-files..." 728 OPTIONS.input_tmp = common.UnzipTemp(args[0]) 729 OPTIONS.target_tmp = OPTIONS.input_tmp 730 input_zip = zipfile.ZipFile(args[0], "r") 731 if OPTIONS.package_key: 732 temp_zip_file = tempfile.NamedTemporaryFile() 733 output_zip = zipfile.ZipFile(temp_zip_file, "w", 734 compression=zipfile.ZIP_DEFLATED) 735 else: 736 output_zip = zipfile.ZipFile(args[1], "w", 737 compression=zipfile.ZIP_DEFLATED) 738 739 if OPTIONS.incremental_source is None: 740 WriteFullOTAPackage(input_zip, output_zip) 741 else: 742 print "unzipping source target-files..." 743 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source) 744 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") 745 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 746 747 output_zip.close() 748 if OPTIONS.package_key: 749 SignOutput(temp_zip_file.name, args[1]) 750 temp_zip_file.close() 751 752 common.Cleanup() 753 754 print "done." 755 756 757if __name__ == '__main__': 758 try: 759 main(sys.argv[1:]) 760 except common.ExternalError, e: 761 print 762 print " ERROR: %s" % (e,) 763 print 764 sys.exit(1) 765