ota_from_target_files.py revision d513160b76a189899ba01f87a3987b4c6f428cae
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, input_zip): 307 device = GetBuildProp("ro.product.device", input_zip) 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", 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 425def 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 431def 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 436def 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 449def 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 461def 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 OPTIONS.source_info_dict) 536 target_boot = common.GetBootableImage( 537 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 538 updating_boot = (source_boot.data != target_boot.data) 539 540 source_recovery = common.GetBootableImage( 541 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", 542 OPTIONS.source_info_dict) 543 target_recovery = common.GetBootableImage( 544 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 545 updating_recovery = (source_recovery.data != target_recovery.data) 546 547 # Here's how we divide up the progress bar: 548 # 0.1 for verifying the start state (PatchCheck calls) 549 # 0.8 for applying patches (ApplyPatch calls) 550 # 0.1 for unpacking verbatim files, symlinking, and doing the 551 # device-specific commands. 552 553 AppendAssertions(script, target_zip) 554 device_specific.IncrementalOTA_Assertions() 555 556 script.Print("Verifying current system...") 557 558 device_specific.IncrementalOTA_VerifyBegin() 559 560 script.ShowProgress(0.1, 0) 561 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1) 562 if updating_boot: 563 total_verify_size += source_boot.size 564 so_far = 0 565 566 for fn, tf, sf, size, patch_sha in patch_list: 567 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 568 so_far += sf.size 569 script.SetProgress(so_far / total_verify_size) 570 571 if updating_boot: 572 d = common.Difference(target_boot, source_boot) 573 _, _, d = d.ComputePatch() 574 print "boot target: %d source: %d diff: %d" % ( 575 target_boot.size, source_boot.size, len(d)) 576 577 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 578 579 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 580 581 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 582 (boot_type, boot_device, 583 source_boot.size, source_boot.sha1, 584 target_boot.size, target_boot.sha1)) 585 so_far += source_boot.size 586 script.SetProgress(so_far / total_verify_size) 587 588 if patch_list or updating_recovery or updating_boot: 589 script.CacheFreeSpaceCheck(largest_source_size) 590 591 device_specific.IncrementalOTA_VerifyEnd() 592 593 script.Comment("---- start making changes here ----") 594 595 device_specific.IncrementalOTA_InstallBegin() 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 = 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 = 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 707 # Now that the symlinks are created, we can set all the 708 # permissions. 709 script.AppendScript(temp_script) 710 711 # Do device-specific installation (eg, write radio image). 712 device_specific.IncrementalOTA_InstallEnd() 713 714 if OPTIONS.extra_script is not None: 715 script.AppendExtra(OPTIONS.extra_script) 716 717 # Patch the build.prop file last, so if something fails but the 718 # device can still come up, it appears to be the old build and will 719 # get set the OTA package again to retry. 720 script.Print("Patching remaining system files...") 721 for item in deferred_patch_list: 722 fn, tf, sf, size, _ = item 723 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 724 script.SetPermissions("/system/build.prop", 0, 0, 0644) 725 726 script.AddToZip(target_zip, output_zip) 727 WriteMetadata(metadata, output_zip) 728 729 730def main(argv): 731 732 def option_handler(o, a): 733 if o in ("-b", "--board_config"): 734 pass # deprecated 735 elif o in ("-k", "--package_key"): 736 OPTIONS.package_key = a 737 elif o in ("-i", "--incremental_from"): 738 OPTIONS.incremental_source = a 739 elif o in ("-w", "--wipe_user_data"): 740 OPTIONS.wipe_user_data = True 741 elif o in ("-n", "--no_prereq"): 742 OPTIONS.omit_prereq = True 743 elif o in ("-e", "--extra_script"): 744 OPTIONS.extra_script = a 745 elif o in ("-a", "--aslr_mode"): 746 if a in ("on", "On", "true", "True", "yes", "Yes"): 747 OPTIONS.aslr_mode = True 748 else: 749 OPTIONS.aslr_mode = False 750 elif o in ("--worker_threads"): 751 OPTIONS.worker_threads = int(a) 752 else: 753 return False 754 return True 755 756 args = common.ParseOptions(argv, __doc__, 757 extra_opts="b:k:i:d:wne:a:", 758 extra_long_opts=["board_config=", 759 "package_key=", 760 "incremental_from=", 761 "wipe_user_data", 762 "no_prereq", 763 "extra_script=", 764 "worker_threads=", 765 "aslr_mode=", 766 ], 767 extra_option_handler=option_handler) 768 769 if len(args) != 2: 770 common.Usage(__doc__) 771 sys.exit(1) 772 773 if OPTIONS.extra_script is not None: 774 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 775 776 print "unzipping target target-files..." 777 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 778 779 OPTIONS.target_tmp = OPTIONS.input_tmp 780 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 781 if OPTIONS.verbose: 782 print "--- target info ---" 783 common.DumpInfoDict(OPTIONS.info_dict) 784 785 if OPTIONS.device_specific is None: 786 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 787 if OPTIONS.device_specific is not None: 788 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 789 print "using device-specific extensions in", OPTIONS.device_specific 790 791 temp_zip_file = tempfile.NamedTemporaryFile() 792 output_zip = zipfile.ZipFile(temp_zip_file, "w", 793 compression=zipfile.ZIP_DEFLATED) 794 795 if OPTIONS.incremental_source is None: 796 WriteFullOTAPackage(input_zip, output_zip) 797 if OPTIONS.package_key is None: 798 OPTIONS.package_key = OPTIONS.info_dict.get( 799 "default_system_dev_certificate", 800 "build/target/product/security/testkey") 801 else: 802 print "unzipping source target-files..." 803 OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) 804 OPTIONS.target_info_dict = OPTIONS.info_dict 805 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 806 if OPTIONS.package_key is None: 807 OPTIONS.package_key = OPTIONS.source_info_dict.get( 808 "default_system_dev_certificate", 809 "build/target/product/security/testkey") 810 if OPTIONS.verbose: 811 print "--- source info ---" 812 common.DumpInfoDict(OPTIONS.source_info_dict) 813 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 814 815 output_zip.close() 816 817 SignOutput(temp_zip_file.name, args[1]) 818 temp_zip_file.close() 819 820 common.Cleanup() 821 822 print "done." 823 824 825if __name__ == '__main__': 826 try: 827 common.CloseInheritedPipes() 828 main(sys.argv[1:]) 829 except common.ExternalError, e: 830 print 831 print " ERROR: %s" % (e,) 832 print 833 sys.exit(1) 834