ota_from_target_files.py revision 1eb74dd9a01ec14a2e41309986ef7efba790be8f
1#!/usr/bin/env python 2# 3# Copyright (C) 2008 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Given a target-files zipfile, produces an OTA package that installs 19that build. An incremental OTA is produced if -i is given, otherwise 20a full OTA is produced. 21 22Usage: ota_from_target_files [flags] input_target_files output_ota_package 23 24 -b (--board_config) <file> 25 Deprecated. 26 27 -k (--package_key) <key> 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 57import sys 58 59if sys.hexversion < 0x02040000: 60 print >> sys.stderr, "Python 2.4 or newer is required." 61 sys.exit(1) 62 63import copy 64import errno 65import os 66import re 67import subprocess 68import tempfile 69import time 70import zipfile 71 72try: 73 from hashlib import sha1 as sha1 74except ImportError: 75 from sha import sha as sha1 76 77import common 78import edify_generator 79 80OPTIONS = common.OPTIONS 81OPTIONS.package_key = None 82OPTIONS.incremental_source = None 83OPTIONS.require_verbatim = set() 84OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 85OPTIONS.patch_threshold = 0.95 86OPTIONS.wipe_user_data = False 87OPTIONS.omit_prereq = False 88OPTIONS.extra_script = None 89OPTIONS.aslr_mode = True 90OPTIONS.worker_threads = 3 91 92def 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 101def IsSymlink(info): 102 """Return true if the zipfile.ZipInfo object passed in represents a 103 symlink.""" 104 return (info.external_attr >> 16) == 0120777 105 106def IsRegular(info): 107 """Return true if the zipfile.ZipInfo object passed in represents a 108 symlink.""" 109 return (info.external_attr >> 28) == 010 110 111class 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 260def 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 298def 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 306def AppendAssertions(script, info_dict): 307 device = GetBuildProp("ro.product.device", info_dict) 308 script.AssertDevice(device) 309 310 311def 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 336if ! 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 339else 340 log -t recovery "Recovery image already installed" 341fi 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 355def 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", 362 OPTIONS.info_dict), 363 "pre-device": GetBuildProp("ro.product.device", 364 OPTIONS.info_dict), 365 "post-timestamp": GetBuildProp("ro.build.date.utc", 366 OPTIONS.info_dict), 367 } 368 369 device_specific = common.DeviceSpecificParams( 370 input_zip=input_zip, 371 input_version=OPTIONS.info_dict["recovery_api_version"], 372 output_zip=output_zip, 373 script=script, 374 input_tmp=OPTIONS.input_tmp, 375 metadata=metadata, 376 info_dict=OPTIONS.info_dict) 377 378 if not OPTIONS.omit_prereq: 379 ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) 380 script.AssertOlderBuild(ts) 381 382 AppendAssertions(script, OPTIONS.info_dict) 383 device_specific.FullOTA_Assertions() 384 device_specific.FullOTA_InstallBegin() 385 386 script.ShowProgress(0.5, 0) 387 388 if OPTIONS.wipe_user_data: 389 script.FormatPartition("/data") 390 391 if "selinux_fc" in OPTIONS.info_dict: 392 WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) 393 394 script.FormatPartition("/system") 395 script.Mount("/system") 396 script.UnpackPackageDir("recovery", "/system") 397 script.UnpackPackageDir("system", "/system") 398 399 symlinks = CopySystemFiles(input_zip, output_zip) 400 script.MakeSymlinks(symlinks) 401 402 boot_img = common.GetBootableImage("boot.img", "boot.img", 403 OPTIONS.input_tmp, "BOOT") 404 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 405 OPTIONS.input_tmp, "RECOVERY") 406 MakeRecoveryPatch(output_zip, recovery_img, boot_img) 407 408 Item.GetMetadata(input_zip) 409 Item.Get("system").SetPermissions(script) 410 411 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 412 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 413 script.ShowProgress(0.2, 0) 414 415 script.ShowProgress(0.2, 10) 416 script.WriteRawImage("/boot", "boot.img") 417 418 script.ShowProgress(0.1, 0) 419 device_specific.FullOTA_InstallEnd() 420 421 if OPTIONS.extra_script is not None: 422 script.AppendExtra(OPTIONS.extra_script) 423 424 script.UnmountAll() 425 script.AddToZip(input_zip, output_zip) 426 WriteMetadata(metadata, output_zip) 427 428def WritePolicyConfig(file_context, output_zip): 429 f = open(file_context, 'r'); 430 basename = os.path.basename(file_context) 431 common.ZipWriteStr(output_zip, basename, f.read()) 432 433 434def WriteMetadata(metadata, output_zip): 435 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 436 "".join(["%s=%s\n" % kv 437 for kv in sorted(metadata.iteritems())])) 438 439def LoadSystemFiles(z): 440 """Load all the files from SYSTEM/... in a given target-files 441 ZipFile, and return a dict of {filename: File object}.""" 442 out = {} 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 return out 450 451 452def GetBuildProp(prop, info_dict): 453 """Return the fingerprint of the build of a given target-files info_dict.""" 454 try: 455 return info_dict.get("build.prop", {})[prop] 456 except KeyError: 457 raise common.ExternalError("couldn't find %s in build.prop" % (property,)) 458 459 460def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 461 source_version = OPTIONS.source_info_dict["recovery_api_version"] 462 target_version = OPTIONS.target_info_dict["recovery_api_version"] 463 464 if source_version == 0: 465 print ("WARNING: generating edify script for a source that " 466 "can't install it.") 467 script = edify_generator.EdifyGenerator(source_version, 468 OPTIONS.target_info_dict) 469 470 metadata = {"pre-device": GetBuildProp("ro.product.device", 471 OPTIONS.source_info_dict), 472 "post-timestamp": GetBuildProp("ro.build.date.utc", 473 OPTIONS.target_info_dict), 474 } 475 476 device_specific = common.DeviceSpecificParams( 477 source_zip=source_zip, 478 source_version=source_version, 479 target_zip=target_zip, 480 target_version=target_version, 481 output_zip=output_zip, 482 script=script, 483 metadata=metadata, 484 info_dict=OPTIONS.info_dict) 485 486 print "Loading target..." 487 target_data = LoadSystemFiles(target_zip) 488 print "Loading source..." 489 source_data = LoadSystemFiles(source_zip) 490 491 verbatim_targets = [] 492 patch_list = [] 493 diffs = [] 494 largest_source_size = 0 495 for fn in sorted(target_data.keys()): 496 tf = target_data[fn] 497 assert fn == tf.name 498 sf = source_data.get(fn, None) 499 500 if sf is None or fn in OPTIONS.require_verbatim: 501 # This file should be included verbatim 502 if fn in OPTIONS.prohibit_verbatim: 503 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 504 print "send", fn, "verbatim" 505 tf.AddToZip(output_zip) 506 verbatim_targets.append((fn, tf.size)) 507 elif tf.sha1 != sf.sha1: 508 # File is different; consider sending as a patch 509 diffs.append(common.Difference(tf, sf)) 510 else: 511 # Target file identical to source. 512 pass 513 514 common.ComputeDifferences(diffs) 515 516 for diff in diffs: 517 tf, sf, d = diff.GetPatch() 518 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 519 # patch is almost as big as the file; don't bother patching 520 tf.AddToZip(output_zip) 521 verbatim_targets.append((tf.name, tf.size)) 522 else: 523 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d) 524 patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest())) 525 largest_source_size = max(largest_source_size, sf.size) 526 527 source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) 528 target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) 529 metadata["pre-build"] = source_fp 530 metadata["post-build"] = target_fp 531 532 script.Mount("/system") 533 script.AssertSomeFingerprint(source_fp, target_fp) 534 535 source_boot = common.GetBootableImage( 536 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 537 OPTIONS.source_info_dict) 538 target_boot = common.GetBootableImage( 539 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 540 updating_boot = (source_boot.data != target_boot.data) 541 542 source_recovery = common.GetBootableImage( 543 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", 544 OPTIONS.source_info_dict) 545 target_recovery = common.GetBootableImage( 546 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 547 updating_recovery = (source_recovery.data != target_recovery.data) 548 549 # Here's how we divide up the progress bar: 550 # 0.1 for verifying the start state (PatchCheck calls) 551 # 0.8 for applying patches (ApplyPatch calls) 552 # 0.1 for unpacking verbatim files, symlinking, and doing the 553 # device-specific commands. 554 555 AppendAssertions(script, OPTIONS.target_info_dict) 556 device_specific.IncrementalOTA_Assertions() 557 558 script.Print("Verifying current system...") 559 560 device_specific.IncrementalOTA_VerifyBegin() 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 device_specific.IncrementalOTA_InstallBegin() 598 599 if OPTIONS.wipe_user_data: 600 script.Print("Erasing user data...") 601 script.FormatPartition("/data") 602 603 script.Print("Removing unneeded files...") 604 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + 605 ["/"+i for i in sorted(source_data) 606 if i not in target_data] + 607 ["/system/recovery.img"]) 608 609 script.ShowProgress(0.8, 0) 610 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) 611 if updating_boot: 612 total_patch_size += target_boot.size 613 so_far = 0 614 615 script.Print("Patching system files...") 616 deferred_patch_list = [] 617 for item in patch_list: 618 fn, tf, sf, size, _ = item 619 if tf.name == "system/build.prop": 620 deferred_patch_list.append(item) 621 continue 622 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 623 so_far += tf.size 624 script.SetProgress(so_far / total_patch_size) 625 626 if updating_boot: 627 # Produce the boot image by applying a patch to the current 628 # contents of the boot partition, and write it back to the 629 # partition. 630 script.Print("Patching boot image...") 631 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 632 % (boot_type, boot_device, 633 source_boot.size, source_boot.sha1, 634 target_boot.size, target_boot.sha1), 635 "-", 636 target_boot.size, target_boot.sha1, 637 source_boot.sha1, "patch/boot.img.p") 638 so_far += target_boot.size 639 script.SetProgress(so_far / total_patch_size) 640 print "boot image changed; including." 641 else: 642 print "boot image unchanged; skipping." 643 644 if updating_recovery: 645 # Is it better to generate recovery as a patch from the current 646 # boot image, or from the previous recovery image? For large 647 # updates with significant kernel changes, probably the former. 648 # For small updates where the kernel hasn't changed, almost 649 # certainly the latter. We pick the first option. Future 650 # complicated schemes may let us effectively use both. 651 # 652 # A wacky possibility: as long as there is room in the boot 653 # partition, include the binaries and image files from recovery in 654 # the boot image (though not in the ramdisk) so they can be used 655 # as fodder for constructing the recovery image. 656 MakeRecoveryPatch(output_zip, target_recovery, target_boot) 657 script.DeleteFiles(["/system/recovery-from-boot.p", 658 "/system/etc/install-recovery.sh"]) 659 print "recovery image changed; including as patch from boot." 660 else: 661 print "recovery image unchanged; skipping." 662 663 script.ShowProgress(0.1, 10) 664 665 target_symlinks = CopySystemFiles(target_zip, None) 666 667 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 668 temp_script = script.MakeTemporary() 669 Item.GetMetadata(target_zip) 670 Item.Get("system").SetPermissions(temp_script) 671 672 # Note that this call will mess up the tree of Items, so make sure 673 # we're done with it. 674 source_symlinks = CopySystemFiles(source_zip, None) 675 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 676 677 # Delete all the symlinks in source that aren't in target. This 678 # needs to happen before verbatim files are unpacked, in case a 679 # symlink in the source is replaced by a real file in the target. 680 to_delete = [] 681 for dest, link in source_symlinks: 682 if link not in target_symlinks_d: 683 to_delete.append(link) 684 script.DeleteFiles(to_delete) 685 686 if verbatim_targets: 687 script.Print("Unpacking new files...") 688 script.UnpackPackageDir("system", "/system") 689 690 if updating_recovery: 691 script.Print("Unpacking new recovery...") 692 script.UnpackPackageDir("recovery", "/system") 693 694 script.Print("Symlinks and permissions...") 695 696 # Create all the symlinks that don't already exist, or point to 697 # somewhere different than what we want. Delete each symlink before 698 # creating it, since the 'symlink' command won't overwrite. 699 to_create = [] 700 for dest, link in target_symlinks: 701 if link in source_symlinks_d: 702 if dest != source_symlinks_d[link]: 703 to_create.append((dest, link)) 704 else: 705 to_create.append((dest, link)) 706 script.DeleteFiles([i[1] for i in to_create]) 707 script.MakeSymlinks(to_create) 708 709 # Now that the symlinks are created, we can set all the 710 # permissions. 711 script.AppendScript(temp_script) 712 713 # Do device-specific installation (eg, write radio image). 714 device_specific.IncrementalOTA_InstallEnd() 715 716 if OPTIONS.extra_script is not None: 717 script.AppendExtra(OPTIONS.extra_script) 718 719 # Patch the build.prop file last, so if something fails but the 720 # device can still come up, it appears to be the old build and will 721 # get set the OTA package again to retry. 722 script.Print("Patching remaining system files...") 723 for item in deferred_patch_list: 724 fn, tf, sf, size, _ = item 725 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 726 script.SetPermissions("/system/build.prop", 0, 0, 0644) 727 728 script.AddToZip(target_zip, output_zip) 729 WriteMetadata(metadata, output_zip) 730 731 732def main(argv): 733 734 def option_handler(o, a): 735 if o in ("-b", "--board_config"): 736 pass # deprecated 737 elif o in ("-k", "--package_key"): 738 OPTIONS.package_key = a 739 elif o in ("-i", "--incremental_from"): 740 OPTIONS.incremental_source = a 741 elif o in ("-w", "--wipe_user_data"): 742 OPTIONS.wipe_user_data = True 743 elif o in ("-n", "--no_prereq"): 744 OPTIONS.omit_prereq = True 745 elif o in ("-e", "--extra_script"): 746 OPTIONS.extra_script = a 747 elif o in ("-a", "--aslr_mode"): 748 if a in ("on", "On", "true", "True", "yes", "Yes"): 749 OPTIONS.aslr_mode = True 750 else: 751 OPTIONS.aslr_mode = False 752 elif o in ("--worker_threads"): 753 OPTIONS.worker_threads = int(a) 754 else: 755 return False 756 return True 757 758 args = common.ParseOptions(argv, __doc__, 759 extra_opts="b:k:i:d:wne:a:", 760 extra_long_opts=["board_config=", 761 "package_key=", 762 "incremental_from=", 763 "wipe_user_data", 764 "no_prereq", 765 "extra_script=", 766 "worker_threads=", 767 "aslr_mode=", 768 ], 769 extra_option_handler=option_handler) 770 771 if len(args) != 2: 772 common.Usage(__doc__) 773 sys.exit(1) 774 775 if OPTIONS.extra_script is not None: 776 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 777 778 print "unzipping target target-files..." 779 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 780 781 OPTIONS.target_tmp = OPTIONS.input_tmp 782 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 783 if OPTIONS.verbose: 784 print "--- target info ---" 785 common.DumpInfoDict(OPTIONS.info_dict) 786 787 if OPTIONS.device_specific is None: 788 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 789 if OPTIONS.device_specific is not None: 790 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 791 print "using device-specific extensions in", OPTIONS.device_specific 792 793 temp_zip_file = tempfile.NamedTemporaryFile() 794 output_zip = zipfile.ZipFile(temp_zip_file, "w", 795 compression=zipfile.ZIP_DEFLATED) 796 797 if OPTIONS.incremental_source is None: 798 WriteFullOTAPackage(input_zip, output_zip) 799 if OPTIONS.package_key is None: 800 OPTIONS.package_key = OPTIONS.info_dict.get( 801 "default_system_dev_certificate", 802 "build/target/product/security/testkey") 803 else: 804 print "unzipping source target-files..." 805 OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) 806 OPTIONS.target_info_dict = OPTIONS.info_dict 807 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 808 if OPTIONS.package_key is None: 809 OPTIONS.package_key = OPTIONS.source_info_dict.get( 810 "default_system_dev_certificate", 811 "build/target/product/security/testkey") 812 if OPTIONS.verbose: 813 print "--- source info ---" 814 common.DumpInfoDict(OPTIONS.source_info_dict) 815 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 816 817 output_zip.close() 818 819 SignOutput(temp_zip_file.name, args[1]) 820 temp_zip_file.close() 821 822 common.Cleanup() 823 824 print "done." 825 826 827if __name__ == '__main__': 828 try: 829 common.CloseInheritedPipes() 830 main(sys.argv[1:]) 831 except common.ExternalError, e: 832 print 833 print " ERROR: %s" % (e,) 834 print 835 sys.exit(1) 836