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, info_dict): 307 device = GetBuildProp("ro.product.device", info_dict) 308 script.AssertDevice(device) 309 310 311 def MakeRecoveryPatch(input_tmp, 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 diff_program = ["imgdiff"] 328 path = os.path.join(input_tmp, "SYSTEM", "etc", "recovery-resource.dat") 329 if os.path.exists(path): 330 diff_program.append("-b") 331 diff_program.append(path) 332 bonus_args = "-b /system/etc/recovery-resource.dat" 333 else: 334 bonus_args = "" 335 336 d = common.Difference(recovery_img, boot_img, diff_program=diff_program) 337 _, _, patch = d.ComputePatch() 338 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch) 339 Item.Get("system/recovery-from-boot.p", dir=False) 340 341 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 342 recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict) 343 344 sh = """#!/system/bin/sh 345 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then 346 log -t recovery "Installing new recovery image" 347 applypatch %(bonus_args)s %(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 'recovery_size': recovery_img.size, 354 'recovery_sha1': recovery_img.sha1, 355 'boot_type': boot_type, 356 'boot_device': boot_device, 357 'recovery_type': recovery_type, 358 'recovery_device': recovery_device, 359 'bonus_args': bonus_args, 360 } 361 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) 362 return Item.Get("system/etc/install-recovery.sh", dir=False) 363 364 365 def WriteFullOTAPackage(input_zip, output_zip): 366 # TODO: how to determine this? We don't know what version it will 367 # be installed on top of. For now, we expect the API just won't 368 # change very often. 369 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 370 371 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", 372 OPTIONS.info_dict), 373 "pre-device": GetBuildProp("ro.product.device", 374 OPTIONS.info_dict), 375 "post-timestamp": GetBuildProp("ro.build.date.utc", 376 OPTIONS.info_dict), 377 } 378 379 device_specific = common.DeviceSpecificParams( 380 input_zip=input_zip, 381 input_version=OPTIONS.info_dict["recovery_api_version"], 382 output_zip=output_zip, 383 script=script, 384 input_tmp=OPTIONS.input_tmp, 385 metadata=metadata, 386 info_dict=OPTIONS.info_dict) 387 388 if not OPTIONS.omit_prereq: 389 ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) 390 script.AssertOlderBuild(ts) 391 392 AppendAssertions(script, OPTIONS.info_dict) 393 device_specific.FullOTA_Assertions() 394 device_specific.FullOTA_InstallBegin() 395 396 script.ShowProgress(0.5, 0) 397 398 if OPTIONS.wipe_user_data: 399 script.FormatPartition("/data") 400 401 if "selinux_fc" in OPTIONS.info_dict: 402 WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) 403 404 script.FormatPartition("/system") 405 script.Mount("/system") 406 script.UnpackPackageDir("recovery", "/system") 407 script.UnpackPackageDir("system", "/system") 408 409 symlinks = CopySystemFiles(input_zip, output_zip) 410 script.MakeSymlinks(symlinks) 411 412 boot_img = common.GetBootableImage("boot.img", "boot.img", 413 OPTIONS.input_tmp, "BOOT") 414 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 415 OPTIONS.input_tmp, "RECOVERY") 416 MakeRecoveryPatch(OPTIONS.input_tmp, output_zip, recovery_img, boot_img) 417 418 Item.GetMetadata(input_zip) 419 Item.Get("system").SetPermissions(script) 420 421 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 422 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 423 script.ShowProgress(0.2, 0) 424 425 script.ShowProgress(0.2, 10) 426 script.WriteRawImage("/boot", "boot.img") 427 428 script.ShowProgress(0.1, 0) 429 device_specific.FullOTA_InstallEnd() 430 431 if OPTIONS.extra_script is not None: 432 script.AppendExtra(OPTIONS.extra_script) 433 434 script.UnmountAll() 435 script.AddToZip(input_zip, output_zip) 436 WriteMetadata(metadata, output_zip) 437 438 def WritePolicyConfig(file_context, output_zip): 439 f = open(file_context, 'r'); 440 basename = os.path.basename(file_context) 441 common.ZipWriteStr(output_zip, basename, f.read()) 442 443 444 def WriteMetadata(metadata, output_zip): 445 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 446 "".join(["%s=%s\n" % kv 447 for kv in sorted(metadata.iteritems())])) 448 449 def LoadSystemFiles(z): 450 """Load all the files from SYSTEM/... in a given target-files 451 ZipFile, and return a dict of {filename: File object}.""" 452 out = {} 453 for info in z.infolist(): 454 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 455 basefilename = info.filename[7:] 456 fn = "system/" + basefilename 457 data = z.read(info.filename) 458 out[fn] = common.File(fn, data) 459 return out 460 461 462 def GetBuildProp(prop, info_dict): 463 """Return the fingerprint of the build of a given target-files info_dict.""" 464 try: 465 return info_dict.get("build.prop", {})[prop] 466 except KeyError: 467 raise common.ExternalError("couldn't find %s in build.prop" % (property,)) 468 469 470 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 471 source_version = OPTIONS.source_info_dict["recovery_api_version"] 472 target_version = OPTIONS.target_info_dict["recovery_api_version"] 473 474 if source_version == 0: 475 print ("WARNING: generating edify script for a source that " 476 "can't install it.") 477 script = edify_generator.EdifyGenerator(source_version, 478 OPTIONS.target_info_dict) 479 480 metadata = {"pre-device": GetBuildProp("ro.product.device", 481 OPTIONS.source_info_dict), 482 "post-timestamp": GetBuildProp("ro.build.date.utc", 483 OPTIONS.target_info_dict), 484 } 485 486 device_specific = common.DeviceSpecificParams( 487 source_zip=source_zip, 488 source_version=source_version, 489 target_zip=target_zip, 490 target_version=target_version, 491 output_zip=output_zip, 492 script=script, 493 metadata=metadata, 494 info_dict=OPTIONS.info_dict) 495 496 print "Loading target..." 497 target_data = LoadSystemFiles(target_zip) 498 print "Loading source..." 499 source_data = LoadSystemFiles(source_zip) 500 501 verbatim_targets = [] 502 patch_list = [] 503 diffs = [] 504 largest_source_size = 0 505 for fn in sorted(target_data.keys()): 506 tf = target_data[fn] 507 assert fn == tf.name 508 sf = source_data.get(fn, None) 509 510 if sf is None or fn in OPTIONS.require_verbatim: 511 # This file should be included verbatim 512 if fn in OPTIONS.prohibit_verbatim: 513 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 514 print "send", fn, "verbatim" 515 tf.AddToZip(output_zip) 516 verbatim_targets.append((fn, tf.size)) 517 elif tf.sha1 != sf.sha1: 518 # File is different; consider sending as a patch 519 diffs.append(common.Difference(tf, sf)) 520 else: 521 # Target file identical to source. 522 pass 523 524 common.ComputeDifferences(diffs) 525 526 for diff in diffs: 527 tf, sf, d = diff.GetPatch() 528 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 529 # patch is almost as big as the file; don't bother patching 530 tf.AddToZip(output_zip) 531 verbatim_targets.append((tf.name, tf.size)) 532 else: 533 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d) 534 patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest())) 535 largest_source_size = max(largest_source_size, sf.size) 536 537 source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) 538 target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) 539 metadata["pre-build"] = source_fp 540 metadata["post-build"] = target_fp 541 542 script.Mount("/system") 543 script.AssertSomeFingerprint(source_fp, target_fp) 544 545 source_boot = common.GetBootableImage( 546 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 547 OPTIONS.source_info_dict) 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 OPTIONS.source_info_dict) 555 target_recovery = common.GetBootableImage( 556 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 557 updating_recovery = (source_recovery.data != target_recovery.data) 558 559 # Here's how we divide up the progress bar: 560 # 0.1 for verifying the start state (PatchCheck calls) 561 # 0.8 for applying patches (ApplyPatch calls) 562 # 0.1 for unpacking verbatim files, symlinking, and doing the 563 # device-specific commands. 564 565 AppendAssertions(script, OPTIONS.target_info_dict) 566 device_specific.IncrementalOTA_Assertions() 567 568 script.Print("Verifying current system...") 569 570 device_specific.IncrementalOTA_VerifyBegin() 571 572 script.ShowProgress(0.1, 0) 573 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1) 574 if updating_boot: 575 total_verify_size += source_boot.size 576 so_far = 0 577 578 for fn, tf, sf, size, patch_sha in patch_list: 579 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 580 so_far += sf.size 581 script.SetProgress(so_far / total_verify_size) 582 583 if updating_boot: 584 d = common.Difference(target_boot, source_boot) 585 _, _, d = d.ComputePatch() 586 print "boot target: %d source: %d diff: %d" % ( 587 target_boot.size, source_boot.size, len(d)) 588 589 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 590 591 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 592 593 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 594 (boot_type, boot_device, 595 source_boot.size, source_boot.sha1, 596 target_boot.size, target_boot.sha1)) 597 so_far += source_boot.size 598 script.SetProgress(so_far / total_verify_size) 599 600 if patch_list or updating_recovery or updating_boot: 601 script.CacheFreeSpaceCheck(largest_source_size) 602 603 device_specific.IncrementalOTA_VerifyEnd() 604 605 script.Comment("---- start making changes here ----") 606 607 device_specific.IncrementalOTA_InstallBegin() 608 609 if OPTIONS.wipe_user_data: 610 script.Print("Erasing user data...") 611 script.FormatPartition("/data") 612 613 script.Print("Removing unneeded files...") 614 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + 615 ["/"+i for i in sorted(source_data) 616 if i not in target_data] + 617 ["/system/recovery.img"]) 618 619 script.ShowProgress(0.8, 0) 620 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) 621 if updating_boot: 622 total_patch_size += target_boot.size 623 so_far = 0 624 625 script.Print("Patching system files...") 626 deferred_patch_list = [] 627 for item in patch_list: 628 fn, tf, sf, size, _ = item 629 if tf.name == "system/build.prop": 630 deferred_patch_list.append(item) 631 continue 632 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 633 so_far += tf.size 634 script.SetProgress(so_far / total_patch_size) 635 636 if updating_boot: 637 # Produce the boot image by applying a patch to the current 638 # contents of the boot partition, and write it back to the 639 # partition. 640 script.Print("Patching boot image...") 641 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 642 % (boot_type, boot_device, 643 source_boot.size, source_boot.sha1, 644 target_boot.size, target_boot.sha1), 645 "-", 646 target_boot.size, target_boot.sha1, 647 source_boot.sha1, "patch/boot.img.p") 648 so_far += target_boot.size 649 script.SetProgress(so_far / total_patch_size) 650 print "boot image changed; including." 651 else: 652 print "boot image unchanged; skipping." 653 654 if updating_recovery: 655 # Recovery is generated as a patch using both the boot image 656 # (which contains the same linux kernel as recovery) and the file 657 # /system/etc/recovery-resource.dat (which contains all the images 658 # used in the recovery UI) as sources. This lets us minimize the 659 # size of the patch, which must be included in every OTA package. 660 # 661 # For older builds where recovery-resource.dat is not present, we 662 # use only the boot image as the source. 663 664 MakeRecoveryPatch(OPTIONS.target_tmp, output_zip, 665 target_recovery, target_boot) 666 script.DeleteFiles(["/system/recovery-from-boot.p", 667 "/system/etc/install-recovery.sh"]) 668 print "recovery image changed; including as patch from boot." 669 else: 670 print "recovery image unchanged; skipping." 671 672 script.ShowProgress(0.1, 10) 673 674 target_symlinks = CopySystemFiles(target_zip, None) 675 676 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 677 temp_script = script.MakeTemporary() 678 Item.GetMetadata(target_zip) 679 Item.Get("system").SetPermissions(temp_script) 680 681 # Note that this call will mess up the tree of Items, so make sure 682 # we're done with it. 683 source_symlinks = CopySystemFiles(source_zip, None) 684 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 685 686 # Delete all the symlinks in source that aren't in target. This 687 # needs to happen before verbatim files are unpacked, in case a 688 # symlink in the source is replaced by a real file in the target. 689 to_delete = [] 690 for dest, link in source_symlinks: 691 if link not in target_symlinks_d: 692 to_delete.append(link) 693 script.DeleteFiles(to_delete) 694 695 if verbatim_targets: 696 script.Print("Unpacking new files...") 697 script.UnpackPackageDir("system", "/system") 698 699 if updating_recovery: 700 script.Print("Unpacking new recovery...") 701 script.UnpackPackageDir("recovery", "/system") 702 703 script.Print("Symlinks and permissions...") 704 705 # Create all the symlinks that don't already exist, or point to 706 # somewhere different than what we want. Delete each symlink before 707 # creating it, since the 'symlink' command won't overwrite. 708 to_create = [] 709 for dest, link in target_symlinks: 710 if link in source_symlinks_d: 711 if dest != source_symlinks_d[link]: 712 to_create.append((dest, link)) 713 else: 714 to_create.append((dest, link)) 715 script.DeleteFiles([i[1] for i in to_create]) 716 script.MakeSymlinks(to_create) 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 793 # If this image was originally labelled with SELinux contexts, make sure we 794 # also apply the labels in our new image. During building, the "file_contexts" 795 # is in the out/ directory tree, but for repacking from target-files.zip it's 796 # in the root directory of the ramdisk. 797 if "selinux_fc" in OPTIONS.info_dict: 798 OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK", 799 "file_contexts") 800 801 if OPTIONS.verbose: 802 print "--- target info ---" 803 common.DumpInfoDict(OPTIONS.info_dict) 804 805 if OPTIONS.device_specific is None: 806 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 807 if OPTIONS.device_specific is not None: 808 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 809 print "using device-specific extensions in", OPTIONS.device_specific 810 811 temp_zip_file = tempfile.NamedTemporaryFile() 812 output_zip = zipfile.ZipFile(temp_zip_file, "w", 813 compression=zipfile.ZIP_DEFLATED) 814 815 if OPTIONS.incremental_source is None: 816 WriteFullOTAPackage(input_zip, output_zip) 817 if OPTIONS.package_key is None: 818 OPTIONS.package_key = OPTIONS.info_dict.get( 819 "default_system_dev_certificate", 820 "build/target/product/security/testkey") 821 else: 822 print "unzipping source target-files..." 823 OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) 824 OPTIONS.target_info_dict = OPTIONS.info_dict 825 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 826 if OPTIONS.package_key is None: 827 OPTIONS.package_key = OPTIONS.source_info_dict.get( 828 "default_system_dev_certificate", 829 "build/target/product/security/testkey") 830 if OPTIONS.verbose: 831 print "--- source info ---" 832 common.DumpInfoDict(OPTIONS.source_info_dict) 833 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 834 835 output_zip.close() 836 837 SignOutput(temp_zip_file.name, args[1]) 838 temp_zip_file.close() 839 840 common.Cleanup() 841 842 print "done." 843 844 845 if __name__ == '__main__': 846 try: 847 common.CloseInheritedPipes() 848 main(sys.argv[1:]) 849 except common.ExternalError, e: 850 print 851 print " ERROR: %s" % (e,) 852 print 853 sys.exit(1) 854