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