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