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