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