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