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