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