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 sh = """#!/system/bin/sh 340 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then 341 log -t recovery "Installing new recovery image" 342 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 343 else 344 log -t recovery "Recovery image already installed" 345 fi 346 """ % { 'boot_size': boot_img.size, 347 'boot_sha1': boot_img.sha1, 348 'recovery_size': recovery_img.size, 349 'recovery_sha1': recovery_img.sha1, 350 'boot_type': boot_type, 351 'boot_device': boot_device, 352 'recovery_type': recovery_type, 353 'recovery_device': recovery_device, 354 } 355 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) 356 return Item.Get("system/etc/install-recovery.sh", dir=False) 357 358 359 def WriteFullOTAPackage(input_zip, output_zip): 360 # TODO: how to determine this? We don't know what version it will 361 # be installed on top of. For now, we expect the API just won't 362 # change very often. 363 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 364 365 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip), 366 "pre-device": GetBuildProp("ro.product.device", input_zip), 367 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip), 368 } 369 370 device_specific = common.DeviceSpecificParams( 371 input_zip=input_zip, 372 input_version=OPTIONS.info_dict["recovery_api_version"], 373 output_zip=output_zip, 374 script=script, 375 input_tmp=OPTIONS.input_tmp, 376 metadata=metadata, 377 info_dict=OPTIONS.info_dict) 378 379 if not OPTIONS.omit_prereq: 380 ts = GetBuildProp("ro.build.date.utc", input_zip) 381 script.AssertOlderBuild(ts) 382 383 AppendAssertions(script, input_zip) 384 device_specific.FullOTA_Assertions() 385 386 script.ShowProgress(0.5, 0) 387 388 if OPTIONS.wipe_user_data: 389 script.FormatPartition("/data") 390 391 script.FormatPartition("/system") 392 script.Mount("/system") 393 script.UnpackPackageDir("recovery", "/system") 394 script.UnpackPackageDir("system", "/system") 395 396 (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip) 397 script.MakeSymlinks(symlinks) 398 if OPTIONS.aslr_mode: 399 script.RetouchBinaries(retouch_files) 400 else: 401 script.UndoRetouchBinaries(retouch_files) 402 403 boot_img = common.GetBootableImage("boot.img", "boot.img", 404 OPTIONS.input_tmp, "BOOT") 405 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 406 OPTIONS.input_tmp, "RECOVERY") 407 MakeRecoveryPatch(output_zip, recovery_img, boot_img) 408 409 Item.GetMetadata(input_zip) 410 Item.Get("system").SetPermissions(script) 411 412 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 413 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 414 script.ShowProgress(0.2, 0) 415 416 script.ShowProgress(0.2, 10) 417 script.WriteRawImage("/boot", "boot.img") 418 419 script.ShowProgress(0.1, 0) 420 device_specific.FullOTA_InstallEnd() 421 422 if OPTIONS.extra_script is not None: 423 script.AppendExtra(OPTIONS.extra_script) 424 425 script.UnmountAll() 426 script.AddToZip(input_zip, output_zip) 427 WriteMetadata(metadata, output_zip) 428 429 430 def WriteMetadata(metadata, output_zip): 431 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 432 "".join(["%s=%s\n" % kv 433 for kv in sorted(metadata.iteritems())])) 434 435 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 retouch_files = [] 443 for info in z.infolist(): 444 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 445 basefilename = info.filename[7:] 446 fn = "system/" + basefilename 447 data = z.read(info.filename) 448 out[fn] = common.File(fn, data) 449 if info.filename.startswith("SYSTEM/lib/") and IsRegular(info): 450 retouch_files.append(("/system/" + basefilename, 451 out[fn].sha1)) 452 return (out, retouch_files) 453 454 455 def GetBuildProp(property, z): 456 """Return the fingerprint of the build of a given target-files 457 ZipFile object.""" 458 bp = z.read("SYSTEM/build.prop") 459 if not property: 460 return bp 461 m = re.search(re.escape(property) + r"=(.*)\n", bp) 462 if not m: 463 raise common.ExternalError("couldn't find %s in build.prop" % (property,)) 464 return m.group(1).strip() 465 466 467 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 468 source_version = OPTIONS.source_info_dict["recovery_api_version"] 469 target_version = OPTIONS.target_info_dict["recovery_api_version"] 470 471 if source_version == 0: 472 print ("WARNING: generating edify script for a source that " 473 "can't install it.") 474 script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict) 475 476 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip), 477 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip), 478 } 479 480 device_specific = common.DeviceSpecificParams( 481 source_zip=source_zip, 482 source_version=source_version, 483 target_zip=target_zip, 484 target_version=target_version, 485 output_zip=output_zip, 486 script=script, 487 metadata=metadata, 488 info_dict=OPTIONS.info_dict) 489 490 print "Loading target..." 491 (target_data, target_retouch_files) = LoadSystemFiles(target_zip) 492 print "Loading source..." 493 (source_data, source_retouch_files) = LoadSystemFiles(source_zip) 494 495 verbatim_targets = [] 496 patch_list = [] 497 diffs = [] 498 largest_source_size = 0 499 for fn in sorted(target_data.keys()): 500 tf = target_data[fn] 501 assert fn == tf.name 502 sf = source_data.get(fn, None) 503 504 if sf is None or fn in OPTIONS.require_verbatim: 505 # This file should be included verbatim 506 if fn in OPTIONS.prohibit_verbatim: 507 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 508 print "send", fn, "verbatim" 509 tf.AddToZip(output_zip) 510 verbatim_targets.append((fn, tf.size)) 511 elif tf.sha1 != sf.sha1: 512 # File is different; consider sending as a patch 513 diffs.append(common.Difference(tf, sf)) 514 else: 515 # Target file identical to source. 516 pass 517 518 common.ComputeDifferences(diffs) 519 520 for diff in diffs: 521 tf, sf, d = diff.GetPatch() 522 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 523 # patch is almost as big as the file; don't bother patching 524 tf.AddToZip(output_zip) 525 verbatim_targets.append((tf.name, tf.size)) 526 else: 527 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d) 528 patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest())) 529 largest_source_size = max(largest_source_size, sf.size) 530 531 source_fp = GetBuildProp("ro.build.fingerprint", source_zip) 532 target_fp = GetBuildProp("ro.build.fingerprint", target_zip) 533 metadata["pre-build"] = source_fp 534 metadata["post-build"] = target_fp 535 536 script.Mount("/system") 537 script.AssertSomeFingerprint(source_fp, target_fp) 538 539 source_boot = common.GetBootableImage( 540 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT") 541 target_boot = common.GetBootableImage( 542 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 543 updating_boot = (source_boot.data != target_boot.data) 544 545 source_recovery = common.GetBootableImage( 546 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY") 547 target_recovery = common.GetBootableImage( 548 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 549 updating_recovery = (source_recovery.data != target_recovery.data) 550 551 # Here's how we divide up the progress bar: 552 # 0.1 for verifying the start state (PatchCheck calls) 553 # 0.8 for applying patches (ApplyPatch calls) 554 # 0.1 for unpacking verbatim files, symlinking, and doing the 555 # device-specific commands. 556 557 AppendAssertions(script, target_zip) 558 device_specific.IncrementalOTA_Assertions() 559 560 script.Print("Verifying current system...") 561 562 script.ShowProgress(0.1, 0) 563 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1) 564 if updating_boot: 565 total_verify_size += source_boot.size 566 so_far = 0 567 568 for fn, tf, sf, size, patch_sha in patch_list: 569 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 570 so_far += sf.size 571 script.SetProgress(so_far / total_verify_size) 572 573 if updating_boot: 574 d = common.Difference(target_boot, source_boot) 575 _, _, d = d.ComputePatch() 576 print "boot target: %d source: %d diff: %d" % ( 577 target_boot.size, source_boot.size, len(d)) 578 579 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 580 581 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 582 583 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 584 (boot_type, boot_device, 585 source_boot.size, source_boot.sha1, 586 target_boot.size, target_boot.sha1)) 587 so_far += source_boot.size 588 script.SetProgress(so_far / total_verify_size) 589 590 if patch_list or updating_recovery or updating_boot: 591 script.CacheFreeSpaceCheck(largest_source_size) 592 593 device_specific.IncrementalOTA_VerifyEnd() 594 595 script.Comment("---- start making changes here ----") 596 597 if OPTIONS.wipe_user_data: 598 script.Print("Erasing user data...") 599 script.FormatPartition("/data") 600 601 script.Print("Removing unneeded files...") 602 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + 603 ["/"+i for i in sorted(source_data) 604 if i not in target_data] + 605 ["/system/recovery.img"]) 606 607 script.ShowProgress(0.8, 0) 608 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) 609 if updating_boot: 610 total_patch_size += target_boot.size 611 so_far = 0 612 613 script.Print("Patching system files...") 614 deferred_patch_list = [] 615 for item in patch_list: 616 fn, tf, sf, size, _ = item 617 if tf.name == "system/build.prop": 618 deferred_patch_list.append(item) 619 continue 620 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 621 so_far += tf.size 622 script.SetProgress(so_far / total_patch_size) 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("%s:%s:%d:%s:%d:%s" 630 % (boot_type, boot_device, 631 source_boot.size, source_boot.sha1, 632 target_boot.size, target_boot.sha1), 633 "-", 634 target_boot.size, target_boot.sha1, 635 source_boot.sha1, "patch/boot.img.p") 636 so_far += target_boot.size 637 script.SetProgress(so_far / total_patch_size) 638 print "boot image changed; including." 639 else: 640 print "boot image unchanged; skipping." 641 642 if updating_recovery: 643 # Is it better to generate recovery as a patch from the current 644 # boot image, or from the previous recovery image? For large 645 # updates with significant kernel changes, probably the former. 646 # For small updates where the kernel hasn't changed, almost 647 # certainly the latter. We pick the first option. Future 648 # complicated schemes may let us effectively use both. 649 # 650 # A wacky possibility: as long as there is room in the boot 651 # partition, include the binaries and image files from recovery in 652 # the boot image (though not in the ramdisk) so they can be used 653 # as fodder for constructing the recovery image. 654 MakeRecoveryPatch(output_zip, target_recovery, target_boot) 655 script.DeleteFiles(["/system/recovery-from-boot.p", 656 "/system/etc/install-recovery.sh"]) 657 print "recovery image changed; including as patch from boot." 658 else: 659 print "recovery image unchanged; skipping." 660 661 script.ShowProgress(0.1, 10) 662 663 (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None) 664 665 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 666 temp_script = script.MakeTemporary() 667 Item.GetMetadata(target_zip) 668 Item.Get("system").SetPermissions(temp_script) 669 670 # Note that this call will mess up the tree of Items, so make sure 671 # we're done with it. 672 (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None) 673 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 674 675 # Delete all the symlinks in source that aren't in target. This 676 # needs to happen before verbatim files are unpacked, in case a 677 # symlink in the source is replaced by a real file in the target. 678 to_delete = [] 679 for dest, link in source_symlinks: 680 if link not in target_symlinks_d: 681 to_delete.append(link) 682 script.DeleteFiles(to_delete) 683 684 if verbatim_targets: 685 script.Print("Unpacking new files...") 686 script.UnpackPackageDir("system", "/system") 687 688 if updating_recovery: 689 script.Print("Unpacking new recovery...") 690 script.UnpackPackageDir("recovery", "/system") 691 692 script.Print("Symlinks and permissions...") 693 694 # Create all the symlinks that don't already exist, or point to 695 # somewhere different than what we want. Delete each symlink before 696 # creating it, since the 'symlink' command won't overwrite. 697 to_create = [] 698 for dest, link in target_symlinks: 699 if link in source_symlinks_d: 700 if dest != source_symlinks_d[link]: 701 to_create.append((dest, link)) 702 else: 703 to_create.append((dest, link)) 704 script.DeleteFiles([i[1] for i in to_create]) 705 script.MakeSymlinks(to_create) 706 if OPTIONS.aslr_mode: 707 script.RetouchBinaries(target_retouch_files) 708 else: 709 script.UndoRetouchBinaries(target_retouch_files) 710 711 # Now that the symlinks are created, we can set all the 712 # permissions. 713 script.AppendScript(temp_script) 714 715 # Do device-specific installation (eg, write radio image). 716 device_specific.IncrementalOTA_InstallEnd() 717 718 if OPTIONS.extra_script is not None: 719 script.AppendExtra(OPTIONS.extra_script) 720 721 # Patch the build.prop file last, so if something fails but the 722 # device can still come up, it appears to be the old build and will 723 # get set the OTA package again to retry. 724 script.Print("Patching remaining system files...") 725 for item in deferred_patch_list: 726 fn, tf, sf, size, _ = item 727 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 728 script.SetPermissions("/system/build.prop", 0, 0, 0644) 729 730 script.AddToZip(target_zip, output_zip) 731 WriteMetadata(metadata, output_zip) 732 733 734 def main(argv): 735 736 def option_handler(o, a): 737 if o in ("-b", "--board_config"): 738 pass # deprecated 739 elif o in ("-k", "--package_key"): 740 OPTIONS.package_key = a 741 elif o in ("-i", "--incremental_from"): 742 OPTIONS.incremental_source = a 743 elif o in ("-w", "--wipe_user_data"): 744 OPTIONS.wipe_user_data = True 745 elif o in ("-n", "--no_prereq"): 746 OPTIONS.omit_prereq = True 747 elif o in ("-e", "--extra_script"): 748 OPTIONS.extra_script = a 749 elif o in ("-a", "--aslr_mode"): 750 if a in ("on", "On", "true", "True", "yes", "Yes"): 751 OPTIONS.aslr_mode = True 752 else: 753 OPTIONS.aslr_mode = False 754 elif o in ("--worker_threads"): 755 OPTIONS.worker_threads = int(a) 756 else: 757 return False 758 return True 759 760 args = common.ParseOptions(argv, __doc__, 761 extra_opts="b:k:i:d:wne:a:", 762 extra_long_opts=["board_config=", 763 "package_key=", 764 "incremental_from=", 765 "wipe_user_data", 766 "no_prereq", 767 "extra_script=", 768 "worker_threads=", 769 "aslr_mode=", 770 ], 771 extra_option_handler=option_handler) 772 773 if len(args) != 2: 774 common.Usage(__doc__) 775 sys.exit(1) 776 777 if OPTIONS.extra_script is not None: 778 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 779 780 print "unzipping target target-files..." 781 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 782 783 OPTIONS.target_tmp = OPTIONS.input_tmp 784 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 785 if OPTIONS.verbose: 786 print "--- target info ---" 787 common.DumpInfoDict(OPTIONS.info_dict) 788 789 if OPTIONS.device_specific is None: 790 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 791 if OPTIONS.device_specific is not None: 792 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 793 print "using device-specific extensions in", OPTIONS.device_specific 794 795 temp_zip_file = tempfile.NamedTemporaryFile() 796 output_zip = zipfile.ZipFile(temp_zip_file, "w", 797 compression=zipfile.ZIP_DEFLATED) 798 799 if OPTIONS.incremental_source is None: 800 WriteFullOTAPackage(input_zip, output_zip) 801 if OPTIONS.package_key is None: 802 OPTIONS.package_key = OPTIONS.info_dict.get( 803 "default_system_dev_certificate", 804 "build/target/product/security/testkey") 805 else: 806 print "unzipping source target-files..." 807 OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) 808 OPTIONS.target_info_dict = OPTIONS.info_dict 809 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 810 if OPTIONS.package_key is None: 811 OPTIONS.package_key = OPTIONS.source_info_dict.get( 812 "default_system_dev_certificate", 813 "build/target/product/security/testkey") 814 if OPTIONS.verbose: 815 print "--- source info ---" 816 common.DumpInfoDict(OPTIONS.source_info_dict) 817 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 818 819 output_zip.close() 820 821 SignOutput(temp_zip_file.name, args[1]) 822 temp_zip_file.close() 823 824 common.Cleanup() 825 826 print "done." 827 828 829 if __name__ == '__main__': 830 try: 831 common.CloseInheritedPipes() 832 main(sys.argv[1:]) 833 except common.ExternalError, e: 834 print 835 print " ERROR: %s" % (e,) 836 print 837 sys.exit(1) 838