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