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