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