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