ota_from_target_files.py revision 1e7f6f7442ca2addc8e4447d3070c625dbba8d3f
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 --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 --full_radio 41 When generating an incremental OTA, always include a full copy of 42 radio image. This option is only meaningful when -i is specified, 43 because a full radio is always included in a full OTA if applicable. 44 45 -v (--verify) 46 Remount and verify the checksums of the files written to the 47 system and vendor (if used) partitions. Incremental builds only. 48 49 -o (--oem_settings) <file> 50 Use the file to specify the expected OEM-specific properties 51 on the OEM partition of the intended device. 52 53 -w (--wipe_user_data) 54 Generate an OTA package that will wipe the user data partition 55 when installed. 56 57 -n (--no_prereq) 58 Omit the timestamp prereq check normally included at the top of 59 the build scripts (used for developer OTA packages which 60 legitimately need to go back and forth). 61 62 -e (--extra_script) <file> 63 Insert the contents of file at the end of the update script. 64 65 -a (--aslr_mode) <on|off> 66 Specify whether to turn on ASLR for the package (on by default). 67 68 -2 (--two_step) 69 Generate a 'two-step' OTA package, where recovery is updated 70 first, so that any changes made to the system partition are done 71 using the new recovery (new kernel, etc.). 72 73 --block 74 Generate a block-based OTA if possible. Will fall back to a 75 file-based OTA if the target_files is older and doesn't support 76 block-based OTAs. 77 78 -b (--binary) <file> 79 Use the given binary as the update-binary in the output package, 80 instead of the binary in the build's target_files. Use for 81 development only. 82 83 -t (--worker_threads) <int> 84 Specifies the number of worker-threads that will be used when 85 generating patches for incremental updates (defaults to 3). 86 87 --stash_threshold <float> 88 Specifies the threshold that will be used to compute the maximum 89 allowed stash size (defaults to 0.8). 90""" 91 92import sys 93 94if sys.hexversion < 0x02070000: 95 print >> sys.stderr, "Python 2.7 or newer is required." 96 sys.exit(1) 97 98import multiprocessing 99import os 100import tempfile 101import zipfile 102 103import common 104import edify_generator 105import sparse_img 106 107OPTIONS = common.OPTIONS 108OPTIONS.package_key = None 109OPTIONS.incremental_source = None 110OPTIONS.verify = False 111OPTIONS.require_verbatim = set() 112OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 113OPTIONS.patch_threshold = 0.95 114OPTIONS.wipe_user_data = False 115OPTIONS.omit_prereq = False 116OPTIONS.extra_script = None 117OPTIONS.aslr_mode = True 118OPTIONS.worker_threads = multiprocessing.cpu_count() // 2 119if OPTIONS.worker_threads == 0: 120 OPTIONS.worker_threads = 1 121OPTIONS.two_step = False 122OPTIONS.no_signing = False 123OPTIONS.block_based = False 124OPTIONS.updater_binary = None 125OPTIONS.oem_source = None 126OPTIONS.fallback_to_full = True 127OPTIONS.full_radio = False 128# Stash size cannot exceed cache_size * threshold. 129OPTIONS.cache_size = None 130OPTIONS.stash_threshold = 0.8 131 132 133def MostPopularKey(d, default): 134 """Given a dict, return the key corresponding to the largest 135 value. Returns 'default' if the dict is empty.""" 136 x = [(v, k) for (k, v) in d.iteritems()] 137 if not x: 138 return default 139 x.sort() 140 return x[-1][1] 141 142 143def IsSymlink(info): 144 """Return true if the zipfile.ZipInfo object passed in represents a 145 symlink.""" 146 return (info.external_attr >> 16) == 0o120777 147 148def IsRegular(info): 149 """Return true if the zipfile.ZipInfo object passed in represents a 150 symlink.""" 151 return (info.external_attr >> 28) == 0o10 152 153def ClosestFileMatch(src, tgtfiles, existing): 154 """Returns the closest file match between a source file and list 155 of potential matches. The exact filename match is preferred, 156 then the sha1 is searched for, and finally a file with the same 157 basename is evaluated. Rename support in the updater-binary is 158 required for the latter checks to be used.""" 159 160 result = tgtfiles.get("path:" + src.name) 161 if result is not None: 162 return result 163 164 if not OPTIONS.target_info_dict.get("update_rename_support", False): 165 return None 166 167 if src.size < 1000: 168 return None 169 170 result = tgtfiles.get("sha1:" + src.sha1) 171 if result is not None and existing.get(result.name) is None: 172 return result 173 result = tgtfiles.get("file:" + src.name.split("/")[-1]) 174 if result is not None and existing.get(result.name) is None: 175 return result 176 return None 177 178class ItemSet(object): 179 def __init__(self, partition, fs_config): 180 self.partition = partition 181 self.fs_config = fs_config 182 self.ITEMS = {} 183 184 def Get(self, name, is_dir=False): 185 if name not in self.ITEMS: 186 self.ITEMS[name] = Item(self, name, is_dir=is_dir) 187 return self.ITEMS[name] 188 189 def GetMetadata(self, input_zip): 190 # The target_files contains a record of what the uid, 191 # gid, and mode are supposed to be. 192 output = input_zip.read(self.fs_config) 193 194 for line in output.split("\n"): 195 if not line: 196 continue 197 columns = line.split() 198 name, uid, gid, mode = columns[:4] 199 selabel = None 200 capabilities = None 201 202 # After the first 4 columns, there are a series of key=value 203 # pairs. Extract out the fields we care about. 204 for element in columns[4:]: 205 key, value = element.split("=") 206 if key == "selabel": 207 selabel = value 208 if key == "capabilities": 209 capabilities = value 210 211 i = self.ITEMS.get(name, None) 212 if i is not None: 213 i.uid = int(uid) 214 i.gid = int(gid) 215 i.mode = int(mode, 8) 216 i.selabel = selabel 217 i.capabilities = capabilities 218 if i.is_dir: 219 i.children.sort(key=lambda i: i.name) 220 221 # set metadata for the files generated by this script. 222 i = self.ITEMS.get("system/recovery-from-boot.p", None) 223 if i: 224 i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o644, None, None 225 i = self.ITEMS.get("system/etc/install-recovery.sh", None) 226 if i: 227 i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0o544, None, None 228 229 230class Item(object): 231 """Items represent the metadata (user, group, mode) of files and 232 directories in the system image.""" 233 def __init__(self, itemset, name, is_dir=False): 234 self.itemset = itemset 235 self.name = name 236 self.uid = None 237 self.gid = None 238 self.mode = None 239 self.selabel = None 240 self.capabilities = None 241 self.is_dir = is_dir 242 self.descendants = None 243 self.best_subtree = None 244 245 if name: 246 self.parent = itemset.Get(os.path.dirname(name), is_dir=True) 247 self.parent.children.append(self) 248 else: 249 self.parent = None 250 if self.is_dir: 251 self.children = [] 252 253 def Dump(self, indent=0): 254 if self.uid is not None: 255 print "%s%s %d %d %o" % ( 256 " " * indent, self.name, self.uid, self.gid, self.mode) 257 else: 258 print "%s%s %s %s %s" % ( 259 " " * indent, self.name, self.uid, self.gid, self.mode) 260 if self.is_dir: 261 print "%s%s" % (" "*indent, self.descendants) 262 print "%s%s" % (" "*indent, self.best_subtree) 263 for i in self.children: 264 i.Dump(indent=indent+1) 265 266 def CountChildMetadata(self): 267 """Count up the (uid, gid, mode, selabel, capabilities) tuples for 268 all children and determine the best strategy for using set_perm_recursive 269 and set_perm to correctly chown/chmod all the files to their desired 270 values. Recursively calls itself for all descendants. 271 272 Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count} 273 counting up all descendants of this node. (dmode or fmode may be None.) 274 Also sets the best_subtree of each directory Item to the (uid, gid, dmode, 275 fmode, selabel, capabilities) tuple that will match the most descendants of 276 that Item. 277 """ 278 279 assert self.is_dir 280 key = (self.uid, self.gid, self.mode, None, self.selabel, 281 self.capabilities) 282 self.descendants = {key: 1} 283 d = self.descendants 284 for i in self.children: 285 if i.is_dir: 286 for k, v in i.CountChildMetadata().iteritems(): 287 d[k] = d.get(k, 0) + v 288 else: 289 k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities) 290 d[k] = d.get(k, 0) + 1 291 292 # Find the (uid, gid, dmode, fmode, selabel, capabilities) 293 # tuple that matches the most descendants. 294 295 # First, find the (uid, gid) pair that matches the most 296 # descendants. 297 ug = {} 298 for (uid, gid, _, _, _, _), count in d.iteritems(): 299 ug[(uid, gid)] = ug.get((uid, gid), 0) + count 300 ug = MostPopularKey(ug, (0, 0)) 301 302 # Now find the dmode, fmode, selabel, and capabilities that match 303 # the most descendants with that (uid, gid), and choose those. 304 best_dmode = (0, 0o755) 305 best_fmode = (0, 0o644) 306 best_selabel = (0, None) 307 best_capabilities = (0, None) 308 for k, count in d.iteritems(): 309 if k[:2] != ug: 310 continue 311 if k[2] is not None and count >= best_dmode[0]: 312 best_dmode = (count, k[2]) 313 if k[3] is not None and count >= best_fmode[0]: 314 best_fmode = (count, k[3]) 315 if k[4] is not None and count >= best_selabel[0]: 316 best_selabel = (count, k[4]) 317 if k[5] is not None and count >= best_capabilities[0]: 318 best_capabilities = (count, k[5]) 319 self.best_subtree = ug + ( 320 best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1]) 321 322 return d 323 324 def SetPermissions(self, script): 325 """Append set_perm/set_perm_recursive commands to 'script' to 326 set all permissions, users, and groups for the tree of files 327 rooted at 'self'.""" 328 329 self.CountChildMetadata() 330 331 def recurse(item, current): 332 # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple 333 # that the current item (and all its children) have already been set to. 334 # We only need to issue set_perm/set_perm_recursive commands if we're 335 # supposed to be something different. 336 if item.is_dir: 337 if current != item.best_subtree: 338 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) 339 current = item.best_subtree 340 341 if item.uid != current[0] or item.gid != current[1] or \ 342 item.mode != current[2] or item.selabel != current[4] or \ 343 item.capabilities != current[5]: 344 script.SetPermissions("/"+item.name, item.uid, item.gid, 345 item.mode, item.selabel, item.capabilities) 346 347 for i in item.children: 348 recurse(i, current) 349 else: 350 if item.uid != current[0] or item.gid != current[1] or \ 351 item.mode != current[3] or item.selabel != current[4] or \ 352 item.capabilities != current[5]: 353 script.SetPermissions("/"+item.name, item.uid, item.gid, 354 item.mode, item.selabel, item.capabilities) 355 356 recurse(self, (-1, -1, -1, -1, None, None)) 357 358 359def CopyPartitionFiles(itemset, input_zip, output_zip=None, substitute=None): 360 """Copies files for the partition in the input zip to the output 361 zip. Populates the Item class with their metadata, and returns a 362 list of symlinks. output_zip may be None, in which case the copy is 363 skipped (but the other side effects still happen). substitute is an 364 optional dict of {output filename: contents} to be output instead of 365 certain input files. 366 """ 367 368 symlinks = [] 369 370 partition = itemset.partition 371 372 for info in input_zip.infolist(): 373 prefix = partition.upper() + "/" 374 if info.filename.startswith(prefix): 375 basefilename = info.filename[len(prefix):] 376 if IsSymlink(info): 377 symlinks.append((input_zip.read(info.filename), 378 "/" + partition + "/" + basefilename)) 379 else: 380 import copy 381 info2 = copy.copy(info) 382 fn = info2.filename = partition + "/" + basefilename 383 if substitute and fn in substitute and substitute[fn] is None: 384 continue 385 if output_zip is not None: 386 if substitute and fn in substitute: 387 data = substitute[fn] 388 else: 389 data = input_zip.read(info.filename) 390 common.ZipWriteStr(output_zip, info2, data) 391 if fn.endswith("/"): 392 itemset.Get(fn[:-1], is_dir=True) 393 else: 394 itemset.Get(fn) 395 396 symlinks.sort() 397 return symlinks 398 399 400def SignOutput(temp_zip_name, output_zip_name): 401 key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 402 pw = key_passwords[OPTIONS.package_key] 403 404 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw, 405 whole_file=True) 406 407 408def AppendAssertions(script, info_dict, oem_dict=None): 409 oem_props = info_dict.get("oem_fingerprint_properties") 410 if oem_props is None or len(oem_props) == 0: 411 device = GetBuildProp("ro.product.device", info_dict) 412 script.AssertDevice(device) 413 else: 414 if oem_dict is None: 415 raise common.ExternalError( 416 "No OEM file provided to answer expected assertions") 417 for prop in oem_props.split(): 418 if oem_dict.get(prop) is None: 419 raise common.ExternalError( 420 "The OEM file is missing the property %s" % prop) 421 script.AssertOemProperty(prop, oem_dict.get(prop)) 422 423 424def HasRecoveryPatch(target_files_zip): 425 try: 426 target_files_zip.getinfo("SYSTEM/recovery-from-boot.p") 427 return True 428 except KeyError: 429 return False 430 431def HasVendorPartition(target_files_zip): 432 try: 433 target_files_zip.getinfo("VENDOR/") 434 return True 435 except KeyError: 436 return False 437 438def GetOemProperty(name, oem_props, oem_dict, info_dict): 439 if oem_props is not None and name in oem_props: 440 return oem_dict[name] 441 return GetBuildProp(name, info_dict) 442 443 444def CalculateFingerprint(oem_props, oem_dict, info_dict): 445 if oem_props is None: 446 return GetBuildProp("ro.build.fingerprint", info_dict) 447 return "%s/%s/%s:%s" % ( 448 GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict), 449 GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict), 450 GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict), 451 GetBuildProp("ro.build.thumbprint", info_dict)) 452 453 454def GetImage(which, tmpdir, info_dict): 455 # Return an image object (suitable for passing to BlockImageDiff) 456 # for the 'which' partition (most be "system" or "vendor"). If a 457 # prebuilt image and file map are found in tmpdir they are used, 458 # otherwise they are reconstructed from the individual files. 459 460 assert which in ("system", "vendor") 461 462 path = os.path.join(tmpdir, "IMAGES", which + ".img") 463 mappath = os.path.join(tmpdir, "IMAGES", which + ".map") 464 if os.path.exists(path) and os.path.exists(mappath): 465 print "using %s.img from target-files" % (which,) 466 # This is a 'new' target-files, which already has the image in it. 467 468 else: 469 print "building %s.img from target-files" % (which,) 470 471 # This is an 'old' target-files, which does not contain images 472 # already built. Build them. 473 474 mappath = tempfile.mkstemp()[1] 475 OPTIONS.tempfiles.append(mappath) 476 477 import add_img_to_target_files 478 if which == "system": 479 path = add_img_to_target_files.BuildSystem( 480 tmpdir, info_dict, block_list=mappath) 481 elif which == "vendor": 482 path = add_img_to_target_files.BuildVendor( 483 tmpdir, info_dict, block_list=mappath) 484 485 # Bug: http://b/20939131 486 # In ext4 filesystems, block 0 might be changed even being mounted 487 # R/O. We add it to clobbered_blocks so that it will be written to the 488 # target unconditionally. Note that they are still part of care_map. 489 clobbered_blocks = "0" 490 491 return sparse_img.SparseImage(path, mappath, clobbered_blocks) 492 493 494def WriteFullOTAPackage(input_zip, output_zip): 495 # TODO: how to determine this? We don't know what version it will 496 # be installed on top of. For now, we expect the API just won't 497 # change very often. Similarly for fstab, it might have changed 498 # in the target build. 499 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 500 501 oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") 502 recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options") 503 oem_dict = None 504 if oem_props is not None and len(oem_props) > 0: 505 if OPTIONS.oem_source is None: 506 raise common.ExternalError("OEM source required for this build") 507 script.Mount("/oem", recovery_mount_options) 508 oem_dict = common.LoadDictionaryFromLines( 509 open(OPTIONS.oem_source).readlines()) 510 511 metadata = { 512 "post-build": CalculateFingerprint(oem_props, oem_dict, 513 OPTIONS.info_dict), 514 "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, 515 OPTIONS.info_dict), 516 "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), 517 } 518 519 device_specific = common.DeviceSpecificParams( 520 input_zip=input_zip, 521 input_version=OPTIONS.info_dict["recovery_api_version"], 522 output_zip=output_zip, 523 script=script, 524 input_tmp=OPTIONS.input_tmp, 525 metadata=metadata, 526 info_dict=OPTIONS.info_dict) 527 528 has_recovery_patch = HasRecoveryPatch(input_zip) 529 block_based = OPTIONS.block_based and has_recovery_patch 530 531 if not OPTIONS.omit_prereq: 532 ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) 533 ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) 534 script.AssertOlderBuild(ts, ts_text) 535 536 AppendAssertions(script, OPTIONS.info_dict, oem_dict) 537 device_specific.FullOTA_Assertions() 538 539 # Two-step package strategy (in chronological order, which is *not* 540 # the order in which the generated script has things): 541 # 542 # if stage is not "2/3" or "3/3": 543 # write recovery image to boot partition 544 # set stage to "2/3" 545 # reboot to boot partition and restart recovery 546 # else if stage is "2/3": 547 # write recovery image to recovery partition 548 # set stage to "3/3" 549 # reboot to recovery partition and restart recovery 550 # else: 551 # (stage must be "3/3") 552 # set stage to "" 553 # do normal full package installation: 554 # wipe and install system, boot image, etc. 555 # set up system to update recovery partition on first boot 556 # complete script normally 557 # (allow recovery to mark itself finished and reboot) 558 559 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 560 OPTIONS.input_tmp, "RECOVERY") 561 if OPTIONS.two_step: 562 if not OPTIONS.info_dict.get("multistage_support", None): 563 assert False, "two-step packages not supported by this build" 564 fs = OPTIONS.info_dict["fstab"]["/misc"] 565 assert fs.fs_type.upper() == "EMMC", \ 566 "two-step packages only supported on devices with EMMC /misc partitions" 567 bcb_dev = {"bcb_dev": fs.device} 568 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data) 569 script.AppendExtra(""" 570if get_stage("%(bcb_dev)s") == "2/3" then 571""" % bcb_dev) 572 script.WriteRawImage("/recovery", "recovery.img") 573 script.AppendExtra(""" 574set_stage("%(bcb_dev)s", "3/3"); 575reboot_now("%(bcb_dev)s", "recovery"); 576else if get_stage("%(bcb_dev)s") == "3/3" then 577""" % bcb_dev) 578 579 # Dump fingerprints 580 script.Print("Target: %s" % CalculateFingerprint( 581 oem_props, oem_dict, OPTIONS.info_dict)) 582 583 device_specific.FullOTA_InstallBegin() 584 585 system_progress = 0.75 586 587 if OPTIONS.wipe_user_data: 588 system_progress -= 0.1 589 if HasVendorPartition(input_zip): 590 system_progress -= 0.1 591 592 if "selinux_fc" in OPTIONS.info_dict: 593 WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) 594 595 recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options") 596 597 system_items = ItemSet("system", "META/filesystem_config.txt") 598 script.ShowProgress(system_progress, 0) 599 600 if block_based: 601 # Full OTA is done as an "incremental" against an empty source 602 # image. This has the effect of writing new data from the package 603 # to the entire partition, but lets us reuse the updater code that 604 # writes incrementals to do it. 605 system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict) 606 system_tgt.ResetFileMap() 607 system_diff = common.BlockDifference("system", system_tgt, src=None) 608 system_diff.WriteScript(script, output_zip) 609 else: 610 script.FormatPartition("/system") 611 script.Mount("/system", recovery_mount_options) 612 if not has_recovery_patch: 613 script.UnpackPackageDir("recovery", "/system") 614 script.UnpackPackageDir("system", "/system") 615 616 symlinks = CopyPartitionFiles(system_items, input_zip, output_zip) 617 script.MakeSymlinks(symlinks) 618 619 boot_img = common.GetBootableImage("boot.img", "boot.img", 620 OPTIONS.input_tmp, "BOOT") 621 622 if not block_based: 623 def output_sink(fn, data): 624 common.ZipWriteStr(output_zip, "recovery/" + fn, data) 625 system_items.Get("system/" + fn) 626 627 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, 628 recovery_img, boot_img) 629 630 system_items.GetMetadata(input_zip) 631 system_items.Get("system").SetPermissions(script) 632 633 if HasVendorPartition(input_zip): 634 vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") 635 script.ShowProgress(0.1, 0) 636 637 if block_based: 638 vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict) 639 vendor_tgt.ResetFileMap() 640 vendor_diff = common.BlockDifference("vendor", vendor_tgt) 641 vendor_diff.WriteScript(script, output_zip) 642 else: 643 script.FormatPartition("/vendor") 644 script.Mount("/vendor", recovery_mount_options) 645 script.UnpackPackageDir("vendor", "/vendor") 646 647 symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip) 648 script.MakeSymlinks(symlinks) 649 650 vendor_items.GetMetadata(input_zip) 651 vendor_items.Get("vendor").SetPermissions(script) 652 653 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 654 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 655 656 script.ShowProgress(0.05, 5) 657 script.WriteRawImage("/boot", "boot.img") 658 659 script.ShowProgress(0.2, 10) 660 device_specific.FullOTA_InstallEnd() 661 662 if OPTIONS.extra_script is not None: 663 script.AppendExtra(OPTIONS.extra_script) 664 665 script.UnmountAll() 666 667 if OPTIONS.wipe_user_data: 668 script.ShowProgress(0.1, 10) 669 script.FormatPartition("/data") 670 671 if OPTIONS.two_step: 672 script.AppendExtra(""" 673set_stage("%(bcb_dev)s", ""); 674""" % bcb_dev) 675 script.AppendExtra("else\n") 676 script.WriteRawImage("/boot", "recovery.img") 677 script.AppendExtra(""" 678set_stage("%(bcb_dev)s", "2/3"); 679reboot_now("%(bcb_dev)s", ""); 680endif; 681endif; 682""" % bcb_dev) 683 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary) 684 WriteMetadata(metadata, output_zip) 685 686 687def WritePolicyConfig(file_name, output_zip): 688 common.ZipWrite(output_zip, file_name, os.path.basename(file_name)) 689 690 691def WriteMetadata(metadata, output_zip): 692 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 693 "".join(["%s=%s\n" % kv 694 for kv in sorted(metadata.iteritems())])) 695 696 697def LoadPartitionFiles(z, partition): 698 """Load all the files from the given partition in a given target-files 699 ZipFile, and return a dict of {filename: File object}.""" 700 out = {} 701 prefix = partition.upper() + "/" 702 for info in z.infolist(): 703 if info.filename.startswith(prefix) and not IsSymlink(info): 704 basefilename = info.filename[len(prefix):] 705 fn = partition + "/" + basefilename 706 data = z.read(info.filename) 707 out[fn] = common.File(fn, data) 708 return out 709 710 711def GetBuildProp(prop, info_dict): 712 """Return the fingerprint of the build of a given target-files info_dict.""" 713 try: 714 return info_dict.get("build.prop", {})[prop] 715 except KeyError: 716 raise common.ExternalError("couldn't find %s in build.prop" % (prop,)) 717 718 719def AddToKnownPaths(filename, known_paths): 720 if filename[-1] == "/": 721 return 722 dirs = filename.split("/")[:-1] 723 while len(dirs) > 0: 724 path = "/".join(dirs) 725 if path in known_paths: 726 break 727 known_paths.add(path) 728 dirs.pop() 729 730 731def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): 732 source_version = OPTIONS.source_info_dict["recovery_api_version"] 733 target_version = OPTIONS.target_info_dict["recovery_api_version"] 734 735 if source_version == 0: 736 print ("WARNING: generating edify script for a source that " 737 "can't install it.") 738 script = edify_generator.EdifyGenerator( 739 source_version, OPTIONS.target_info_dict, 740 fstab=OPTIONS.source_info_dict["fstab"]) 741 742 metadata = { 743 "pre-device": GetBuildProp("ro.product.device", 744 OPTIONS.source_info_dict), 745 "post-timestamp": GetBuildProp("ro.build.date.utc", 746 OPTIONS.target_info_dict), 747 } 748 749 device_specific = common.DeviceSpecificParams( 750 source_zip=source_zip, 751 source_version=source_version, 752 target_zip=target_zip, 753 target_version=target_version, 754 output_zip=output_zip, 755 script=script, 756 metadata=metadata, 757 info_dict=OPTIONS.info_dict) 758 759 # TODO: Currently this works differently from WriteIncrementalOTAPackage(). 760 # This function doesn't consider thumbprints when writing 761 # metadata["pre/post-build"]. One possible reason is that the current 762 # devices with thumbprints are all using file-based OTAs. Long term we 763 # should factor out the common parts into a shared one to avoid further 764 # divergence. 765 source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) 766 target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) 767 metadata["pre-build"] = source_fp 768 metadata["post-build"] = target_fp 769 770 source_boot = common.GetBootableImage( 771 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 772 OPTIONS.source_info_dict) 773 target_boot = common.GetBootableImage( 774 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 775 updating_boot = (not OPTIONS.two_step and 776 (source_boot.data != target_boot.data)) 777 778 target_recovery = common.GetBootableImage( 779 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 780 781 system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict) 782 system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict) 783 784 blockimgdiff_version = 1 785 if OPTIONS.info_dict: 786 blockimgdiff_version = max( 787 int(i) for i in 788 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(",")) 789 790 system_diff = common.BlockDifference("system", system_tgt, system_src, 791 version=blockimgdiff_version) 792 793 if HasVendorPartition(target_zip): 794 if not HasVendorPartition(source_zip): 795 raise RuntimeError("can't generate incremental that adds /vendor") 796 vendor_src = GetImage("vendor", OPTIONS.source_tmp, 797 OPTIONS.source_info_dict) 798 vendor_tgt = GetImage("vendor", OPTIONS.target_tmp, 799 OPTIONS.target_info_dict) 800 vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src, 801 version=blockimgdiff_version) 802 else: 803 vendor_diff = None 804 805 oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties") 806 recovery_mount_options = OPTIONS.source_info_dict.get( 807 "recovery_mount_options") 808 oem_dict = None 809 if oem_props is not None and len(oem_props) > 0: 810 if OPTIONS.oem_source is None: 811 raise common.ExternalError("OEM source required for this build") 812 script.Mount("/oem", recovery_mount_options) 813 oem_dict = common.LoadDictionaryFromLines( 814 open(OPTIONS.oem_source).readlines()) 815 816 AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) 817 device_specific.IncrementalOTA_Assertions() 818 819 # Two-step incremental package strategy (in chronological order, 820 # which is *not* the order in which the generated script has 821 # things): 822 # 823 # if stage is not "2/3" or "3/3": 824 # do verification on current system 825 # write recovery image to boot partition 826 # set stage to "2/3" 827 # reboot to boot partition and restart recovery 828 # else if stage is "2/3": 829 # write recovery image to recovery partition 830 # set stage to "3/3" 831 # reboot to recovery partition and restart recovery 832 # else: 833 # (stage must be "3/3") 834 # perform update: 835 # patch system files, etc. 836 # force full install of new boot image 837 # set up system to update recovery partition on first boot 838 # complete script normally 839 # (allow recovery to mark itself finished and reboot) 840 841 if OPTIONS.two_step: 842 if not OPTIONS.source_info_dict.get("multistage_support", None): 843 assert False, "two-step packages not supported by this build" 844 fs = OPTIONS.source_info_dict["fstab"]["/misc"] 845 assert fs.fs_type.upper() == "EMMC", \ 846 "two-step packages only supported on devices with EMMC /misc partitions" 847 bcb_dev = {"bcb_dev": fs.device} 848 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 849 script.AppendExtra(""" 850if get_stage("%(bcb_dev)s") == "2/3" then 851""" % bcb_dev) 852 script.AppendExtra("sleep(20);\n") 853 script.WriteRawImage("/recovery", "recovery.img") 854 script.AppendExtra(""" 855set_stage("%(bcb_dev)s", "3/3"); 856reboot_now("%(bcb_dev)s", "recovery"); 857else if get_stage("%(bcb_dev)s") != "3/3" then 858""" % bcb_dev) 859 860 # Dump fingerprints 861 script.Print("Source: %s" % CalculateFingerprint( 862 oem_props, oem_dict, OPTIONS.source_info_dict)) 863 script.Print("Target: %s" % CalculateFingerprint( 864 oem_props, oem_dict, OPTIONS.target_info_dict)) 865 866 script.Print("Verifying current system...") 867 868 device_specific.IncrementalOTA_VerifyBegin() 869 870 if oem_props is None: 871 # When blockimgdiff version is less than 3 (non-resumable block-based OTA), 872 # patching on a device that's already on the target build will damage the 873 # system. Because operations like move don't check the block state, they 874 # always apply the changes unconditionally. 875 if blockimgdiff_version <= 2: 876 script.AssertSomeFingerprint(source_fp) 877 else: 878 script.AssertSomeFingerprint(source_fp, target_fp) 879 else: 880 if blockimgdiff_version <= 2: 881 script.AssertSomeThumbprint( 882 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 883 else: 884 script.AssertSomeThumbprint( 885 GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), 886 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 887 888 if updating_boot: 889 boot_type, boot_device = common.GetTypeAndDevice( 890 "/boot", OPTIONS.source_info_dict) 891 d = common.Difference(target_boot, source_boot) 892 _, _, d = d.ComputePatch() 893 if d is None: 894 include_full_boot = True 895 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 896 else: 897 include_full_boot = False 898 899 print "boot target: %d source: %d diff: %d" % ( 900 target_boot.size, source_boot.size, len(d)) 901 902 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 903 904 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 905 (boot_type, boot_device, 906 source_boot.size, source_boot.sha1, 907 target_boot.size, target_boot.sha1)) 908 909 device_specific.IncrementalOTA_VerifyEnd() 910 911 if OPTIONS.two_step: 912 script.WriteRawImage("/boot", "recovery.img") 913 script.AppendExtra(""" 914set_stage("%(bcb_dev)s", "2/3"); 915reboot_now("%(bcb_dev)s", ""); 916else 917""" % bcb_dev) 918 919 # Verify the existing partitions. 920 system_diff.WriteVerifyScript(script) 921 if vendor_diff: 922 vendor_diff.WriteVerifyScript(script) 923 924 script.Comment("---- start making changes here ----") 925 926 device_specific.IncrementalOTA_InstallBegin() 927 928 system_diff.WriteScript(script, output_zip, 929 progress=0.8 if vendor_diff else 0.9) 930 if vendor_diff: 931 vendor_diff.WriteScript(script, output_zip, progress=0.1) 932 933 if OPTIONS.two_step: 934 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 935 script.WriteRawImage("/boot", "boot.img") 936 print "writing full boot image (forced by two-step mode)" 937 938 if not OPTIONS.two_step: 939 if updating_boot: 940 if include_full_boot: 941 print "boot image changed; including full." 942 script.Print("Installing boot image...") 943 script.WriteRawImage("/boot", "boot.img") 944 else: 945 # Produce the boot image by applying a patch to the current 946 # contents of the boot partition, and write it back to the 947 # partition. 948 print "boot image changed; including patch." 949 script.Print("Patching boot image...") 950 script.ShowProgress(0.1, 10) 951 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 952 % (boot_type, boot_device, 953 source_boot.size, source_boot.sha1, 954 target_boot.size, target_boot.sha1), 955 "-", 956 target_boot.size, target_boot.sha1, 957 source_boot.sha1, "patch/boot.img.p") 958 else: 959 print "boot image unchanged; skipping." 960 961 # Do device-specific installation (eg, write radio image). 962 device_specific.IncrementalOTA_InstallEnd() 963 964 if OPTIONS.extra_script is not None: 965 script.AppendExtra(OPTIONS.extra_script) 966 967 if OPTIONS.wipe_user_data: 968 script.Print("Erasing user data...") 969 script.FormatPartition("/data") 970 971 if OPTIONS.two_step: 972 script.AppendExtra(""" 973set_stage("%(bcb_dev)s", ""); 974endif; 975endif; 976""" % bcb_dev) 977 978 script.SetProgress(1) 979 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) 980 WriteMetadata(metadata, output_zip) 981 982 983class FileDifference(object): 984 def __init__(self, partition, source_zip, target_zip, output_zip): 985 self.deferred_patch_list = None 986 print "Loading target..." 987 self.target_data = target_data = LoadPartitionFiles(target_zip, partition) 988 print "Loading source..." 989 self.source_data = source_data = LoadPartitionFiles(source_zip, partition) 990 991 self.verbatim_targets = verbatim_targets = [] 992 self.patch_list = patch_list = [] 993 diffs = [] 994 self.renames = renames = {} 995 known_paths = set() 996 largest_source_size = 0 997 998 matching_file_cache = {} 999 for fn, sf in source_data.items(): 1000 assert fn == sf.name 1001 matching_file_cache["path:" + fn] = sf 1002 if fn in target_data.keys(): 1003 AddToKnownPaths(fn, known_paths) 1004 # Only allow eligibility for filename/sha matching 1005 # if there isn't a perfect path match. 1006 if target_data.get(sf.name) is None: 1007 matching_file_cache["file:" + fn.split("/")[-1]] = sf 1008 matching_file_cache["sha:" + sf.sha1] = sf 1009 1010 for fn in sorted(target_data.keys()): 1011 tf = target_data[fn] 1012 assert fn == tf.name 1013 sf = ClosestFileMatch(tf, matching_file_cache, renames) 1014 if sf is not None and sf.name != tf.name: 1015 print "File has moved from " + sf.name + " to " + tf.name 1016 renames[sf.name] = tf 1017 1018 if sf is None or fn in OPTIONS.require_verbatim: 1019 # This file should be included verbatim 1020 if fn in OPTIONS.prohibit_verbatim: 1021 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 1022 print "send", fn, "verbatim" 1023 tf.AddToZip(output_zip) 1024 verbatim_targets.append((fn, tf.size, tf.sha1)) 1025 if fn in target_data.keys(): 1026 AddToKnownPaths(fn, known_paths) 1027 elif tf.sha1 != sf.sha1: 1028 # File is different; consider sending as a patch 1029 diffs.append(common.Difference(tf, sf)) 1030 else: 1031 # Target file data identical to source (may still be renamed) 1032 pass 1033 1034 common.ComputeDifferences(diffs) 1035 1036 for diff in diffs: 1037 tf, sf, d = diff.GetPatch() 1038 path = "/".join(tf.name.split("/")[:-1]) 1039 if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \ 1040 path not in known_paths: 1041 # patch is almost as big as the file; don't bother patching 1042 # or a patch + rename cannot take place due to the target 1043 # directory not existing 1044 tf.AddToZip(output_zip) 1045 verbatim_targets.append((tf.name, tf.size, tf.sha1)) 1046 if sf.name in renames: 1047 del renames[sf.name] 1048 AddToKnownPaths(tf.name, known_paths) 1049 else: 1050 common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d) 1051 patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest())) 1052 largest_source_size = max(largest_source_size, sf.size) 1053 1054 self.largest_source_size = largest_source_size 1055 1056 def EmitVerification(self, script): 1057 so_far = 0 1058 for tf, sf, _, _ in self.patch_list: 1059 if tf.name != sf.name: 1060 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 1061 script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1) 1062 so_far += sf.size 1063 return so_far 1064 1065 def EmitExplicitTargetVerification(self, script): 1066 for fn, _, sha1 in self.verbatim_targets: 1067 if fn[-1] != "/": 1068 script.FileCheck("/"+fn, sha1) 1069 for tf, _, _, _ in self.patch_list: 1070 script.FileCheck(tf.name, tf.sha1) 1071 1072 def RemoveUnneededFiles(self, script, extras=()): 1073 script.DeleteFiles( 1074 ["/" + i[0] for i in self.verbatim_targets] + 1075 ["/" + i for i in sorted(self.source_data) 1076 if i not in self.target_data and i not in self.renames] + 1077 list(extras)) 1078 1079 def TotalPatchSize(self): 1080 return sum(i[1].size for i in self.patch_list) 1081 1082 def EmitPatches(self, script, total_patch_size, so_far): 1083 self.deferred_patch_list = deferred_patch_list = [] 1084 for item in self.patch_list: 1085 tf, sf, _, _ = item 1086 if tf.name == "system/build.prop": 1087 deferred_patch_list.append(item) 1088 continue 1089 if sf.name != tf.name: 1090 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 1091 script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1, 1092 "patch/" + sf.name + ".p") 1093 so_far += tf.size 1094 script.SetProgress(so_far / total_patch_size) 1095 return so_far 1096 1097 def EmitDeferredPatches(self, script): 1098 for item in self.deferred_patch_list: 1099 tf, sf, _, _ = item 1100 script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, 1101 "patch/" + sf.name + ".p") 1102 script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None) 1103 1104 def EmitRenames(self, script): 1105 if len(self.renames) > 0: 1106 script.Print("Renaming files...") 1107 for src, tgt in self.renames.iteritems(): 1108 print "Renaming " + src + " to " + tgt.name 1109 script.RenameFile(src, tgt.name) 1110 1111 1112def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 1113 target_has_recovery_patch = HasRecoveryPatch(target_zip) 1114 source_has_recovery_patch = HasRecoveryPatch(source_zip) 1115 1116 if (OPTIONS.block_based and 1117 target_has_recovery_patch and 1118 source_has_recovery_patch): 1119 return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip) 1120 1121 source_version = OPTIONS.source_info_dict["recovery_api_version"] 1122 target_version = OPTIONS.target_info_dict["recovery_api_version"] 1123 1124 if source_version == 0: 1125 print ("WARNING: generating edify script for a source that " 1126 "can't install it.") 1127 script = edify_generator.EdifyGenerator( 1128 source_version, OPTIONS.target_info_dict, 1129 fstab=OPTIONS.source_info_dict["fstab"]) 1130 1131 oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") 1132 recovery_mount_options = OPTIONS.source_info_dict.get( 1133 "recovery_mount_options") 1134 oem_dict = None 1135 if oem_props is not None and len(oem_props) > 0: 1136 if OPTIONS.oem_source is None: 1137 raise common.ExternalError("OEM source required for this build") 1138 script.Mount("/oem", recovery_mount_options) 1139 oem_dict = common.LoadDictionaryFromLines( 1140 open(OPTIONS.oem_source).readlines()) 1141 1142 metadata = { 1143 "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, 1144 OPTIONS.source_info_dict), 1145 "post-timestamp": GetBuildProp("ro.build.date.utc", 1146 OPTIONS.target_info_dict), 1147 } 1148 1149 device_specific = common.DeviceSpecificParams( 1150 source_zip=source_zip, 1151 source_version=source_version, 1152 target_zip=target_zip, 1153 target_version=target_version, 1154 output_zip=output_zip, 1155 script=script, 1156 metadata=metadata, 1157 info_dict=OPTIONS.info_dict) 1158 1159 system_diff = FileDifference("system", source_zip, target_zip, output_zip) 1160 script.Mount("/system", recovery_mount_options) 1161 if HasVendorPartition(target_zip): 1162 vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip) 1163 script.Mount("/vendor", recovery_mount_options) 1164 else: 1165 vendor_diff = None 1166 1167 target_fp = CalculateFingerprint(oem_props, oem_dict, 1168 OPTIONS.target_info_dict) 1169 source_fp = CalculateFingerprint(oem_props, oem_dict, 1170 OPTIONS.source_info_dict) 1171 1172 if oem_props is None: 1173 script.AssertSomeFingerprint(source_fp, target_fp) 1174 else: 1175 script.AssertSomeThumbprint( 1176 GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), 1177 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 1178 1179 metadata["pre-build"] = source_fp 1180 metadata["post-build"] = target_fp 1181 1182 source_boot = common.GetBootableImage( 1183 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 1184 OPTIONS.source_info_dict) 1185 target_boot = common.GetBootableImage( 1186 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 1187 updating_boot = (not OPTIONS.two_step and 1188 (source_boot.data != target_boot.data)) 1189 1190 source_recovery = common.GetBootableImage( 1191 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", 1192 OPTIONS.source_info_dict) 1193 target_recovery = common.GetBootableImage( 1194 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 1195 updating_recovery = (source_recovery.data != target_recovery.data) 1196 1197 # Here's how we divide up the progress bar: 1198 # 0.1 for verifying the start state (PatchCheck calls) 1199 # 0.8 for applying patches (ApplyPatch calls) 1200 # 0.1 for unpacking verbatim files, symlinking, and doing the 1201 # device-specific commands. 1202 1203 AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) 1204 device_specific.IncrementalOTA_Assertions() 1205 1206 # Two-step incremental package strategy (in chronological order, 1207 # which is *not* the order in which the generated script has 1208 # things): 1209 # 1210 # if stage is not "2/3" or "3/3": 1211 # do verification on current system 1212 # write recovery image to boot partition 1213 # set stage to "2/3" 1214 # reboot to boot partition and restart recovery 1215 # else if stage is "2/3": 1216 # write recovery image to recovery partition 1217 # set stage to "3/3" 1218 # reboot to recovery partition and restart recovery 1219 # else: 1220 # (stage must be "3/3") 1221 # perform update: 1222 # patch system files, etc. 1223 # force full install of new boot image 1224 # set up system to update recovery partition on first boot 1225 # complete script normally 1226 # (allow recovery to mark itself finished and reboot) 1227 1228 if OPTIONS.two_step: 1229 if not OPTIONS.source_info_dict.get("multistage_support", None): 1230 assert False, "two-step packages not supported by this build" 1231 fs = OPTIONS.source_info_dict["fstab"]["/misc"] 1232 assert fs.fs_type.upper() == "EMMC", \ 1233 "two-step packages only supported on devices with EMMC /misc partitions" 1234 bcb_dev = {"bcb_dev": fs.device} 1235 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 1236 script.AppendExtra(""" 1237if get_stage("%(bcb_dev)s") == "2/3" then 1238""" % bcb_dev) 1239 script.AppendExtra("sleep(20);\n") 1240 script.WriteRawImage("/recovery", "recovery.img") 1241 script.AppendExtra(""" 1242set_stage("%(bcb_dev)s", "3/3"); 1243reboot_now("%(bcb_dev)s", "recovery"); 1244else if get_stage("%(bcb_dev)s") != "3/3" then 1245""" % bcb_dev) 1246 1247 # Dump fingerprints 1248 script.Print("Source: %s" % (source_fp,)) 1249 script.Print("Target: %s" % (target_fp,)) 1250 1251 script.Print("Verifying current system...") 1252 1253 device_specific.IncrementalOTA_VerifyBegin() 1254 1255 script.ShowProgress(0.1, 0) 1256 so_far = system_diff.EmitVerification(script) 1257 if vendor_diff: 1258 so_far += vendor_diff.EmitVerification(script) 1259 1260 if updating_boot: 1261 d = common.Difference(target_boot, source_boot) 1262 _, _, d = d.ComputePatch() 1263 print "boot target: %d source: %d diff: %d" % ( 1264 target_boot.size, source_boot.size, len(d)) 1265 1266 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 1267 1268 boot_type, boot_device = common.GetTypeAndDevice( 1269 "/boot", OPTIONS.source_info_dict) 1270 1271 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 1272 (boot_type, boot_device, 1273 source_boot.size, source_boot.sha1, 1274 target_boot.size, target_boot.sha1)) 1275 so_far += source_boot.size 1276 1277 size = [] 1278 if system_diff.patch_list: 1279 size.append(system_diff.largest_source_size) 1280 if vendor_diff: 1281 if vendor_diff.patch_list: 1282 size.append(vendor_diff.largest_source_size) 1283 if size or updating_recovery or updating_boot: 1284 script.CacheFreeSpaceCheck(max(size)) 1285 1286 device_specific.IncrementalOTA_VerifyEnd() 1287 1288 if OPTIONS.two_step: 1289 script.WriteRawImage("/boot", "recovery.img") 1290 script.AppendExtra(""" 1291set_stage("%(bcb_dev)s", "2/3"); 1292reboot_now("%(bcb_dev)s", ""); 1293else 1294""" % bcb_dev) 1295 1296 script.Comment("---- start making changes here ----") 1297 1298 device_specific.IncrementalOTA_InstallBegin() 1299 1300 if OPTIONS.two_step: 1301 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 1302 script.WriteRawImage("/boot", "boot.img") 1303 print "writing full boot image (forced by two-step mode)" 1304 1305 script.Print("Removing unneeded files...") 1306 system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",)) 1307 if vendor_diff: 1308 vendor_diff.RemoveUnneededFiles(script) 1309 1310 script.ShowProgress(0.8, 0) 1311 total_patch_size = 1.0 + system_diff.TotalPatchSize() 1312 if vendor_diff: 1313 total_patch_size += vendor_diff.TotalPatchSize() 1314 if updating_boot: 1315 total_patch_size += target_boot.size 1316 1317 script.Print("Patching system files...") 1318 so_far = system_diff.EmitPatches(script, total_patch_size, 0) 1319 if vendor_diff: 1320 script.Print("Patching vendor files...") 1321 so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far) 1322 1323 if not OPTIONS.two_step: 1324 if updating_boot: 1325 # Produce the boot image by applying a patch to the current 1326 # contents of the boot partition, and write it back to the 1327 # partition. 1328 script.Print("Patching boot image...") 1329 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 1330 % (boot_type, boot_device, 1331 source_boot.size, source_boot.sha1, 1332 target_boot.size, target_boot.sha1), 1333 "-", 1334 target_boot.size, target_boot.sha1, 1335 source_boot.sha1, "patch/boot.img.p") 1336 so_far += target_boot.size 1337 script.SetProgress(so_far / total_patch_size) 1338 print "boot image changed; including." 1339 else: 1340 print "boot image unchanged; skipping." 1341 1342 system_items = ItemSet("system", "META/filesystem_config.txt") 1343 if vendor_diff: 1344 vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") 1345 1346 if updating_recovery: 1347 # Recovery is generated as a patch using both the boot image 1348 # (which contains the same linux kernel as recovery) and the file 1349 # /system/etc/recovery-resource.dat (which contains all the images 1350 # used in the recovery UI) as sources. This lets us minimize the 1351 # size of the patch, which must be included in every OTA package. 1352 # 1353 # For older builds where recovery-resource.dat is not present, we 1354 # use only the boot image as the source. 1355 1356 if not target_has_recovery_patch: 1357 def output_sink(fn, data): 1358 common.ZipWriteStr(output_zip, "recovery/" + fn, data) 1359 system_items.Get("system/" + fn) 1360 1361 common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink, 1362 target_recovery, target_boot) 1363 script.DeleteFiles(["/system/recovery-from-boot.p", 1364 "/system/etc/install-recovery.sh"]) 1365 print "recovery image changed; including as patch from boot." 1366 else: 1367 print "recovery image unchanged; skipping." 1368 1369 script.ShowProgress(0.1, 10) 1370 1371 target_symlinks = CopyPartitionFiles(system_items, target_zip, None) 1372 if vendor_diff: 1373 target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None)) 1374 1375 temp_script = script.MakeTemporary() 1376 system_items.GetMetadata(target_zip) 1377 system_items.Get("system").SetPermissions(temp_script) 1378 if vendor_diff: 1379 vendor_items.GetMetadata(target_zip) 1380 vendor_items.Get("vendor").SetPermissions(temp_script) 1381 1382 # Note that this call will mess up the trees of Items, so make sure 1383 # we're done with them. 1384 source_symlinks = CopyPartitionFiles(system_items, source_zip, None) 1385 if vendor_diff: 1386 source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None)) 1387 1388 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 1389 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 1390 1391 # Delete all the symlinks in source that aren't in target. This 1392 # needs to happen before verbatim files are unpacked, in case a 1393 # symlink in the source is replaced by a real file in the target. 1394 1395 # If a symlink in the source will be replaced by a regular file, we cannot 1396 # delete the symlink/file in case the package gets applied again. For such 1397 # a symlink, we prepend a sha1_check() to detect if it has been updated. 1398 # (Bug: 23646151) 1399 replaced_symlinks = dict() 1400 if system_diff: 1401 for i in system_diff.verbatim_targets: 1402 replaced_symlinks["/%s" % (i[0],)] = i[2] 1403 if vendor_diff: 1404 for i in vendor_diff.verbatim_targets: 1405 replaced_symlinks["/%s" % (i[0],)] = i[2] 1406 1407 if system_diff: 1408 for tf in system_diff.renames.values(): 1409 replaced_symlinks["/%s" % (tf.name,)] = tf.sha1 1410 if vendor_diff: 1411 for tf in vendor_diff.renames.values(): 1412 replaced_symlinks["/%s" % (tf.name,)] = tf.sha1 1413 1414 always_delete = [] 1415 may_delete = [] 1416 for dest, link in source_symlinks: 1417 if link not in target_symlinks_d: 1418 if link in replaced_symlinks: 1419 may_delete.append((link, replaced_symlinks[link])) 1420 else: 1421 always_delete.append(link) 1422 script.DeleteFiles(always_delete) 1423 script.DeleteFilesIfNotMatching(may_delete) 1424 1425 if system_diff.verbatim_targets: 1426 script.Print("Unpacking new system files...") 1427 script.UnpackPackageDir("system", "/system") 1428 if vendor_diff and vendor_diff.verbatim_targets: 1429 script.Print("Unpacking new vendor files...") 1430 script.UnpackPackageDir("vendor", "/vendor") 1431 1432 if updating_recovery and not target_has_recovery_patch: 1433 script.Print("Unpacking new recovery...") 1434 script.UnpackPackageDir("recovery", "/system") 1435 1436 system_diff.EmitRenames(script) 1437 if vendor_diff: 1438 vendor_diff.EmitRenames(script) 1439 1440 script.Print("Symlinks and permissions...") 1441 1442 # Create all the symlinks that don't already exist, or point to 1443 # somewhere different than what we want. Delete each symlink before 1444 # creating it, since the 'symlink' command won't overwrite. 1445 to_create = [] 1446 for dest, link in target_symlinks: 1447 if link in source_symlinks_d: 1448 if dest != source_symlinks_d[link]: 1449 to_create.append((dest, link)) 1450 else: 1451 to_create.append((dest, link)) 1452 script.DeleteFiles([i[1] for i in to_create]) 1453 script.MakeSymlinks(to_create) 1454 1455 # Now that the symlinks are created, we can set all the 1456 # permissions. 1457 script.AppendScript(temp_script) 1458 1459 # Do device-specific installation (eg, write radio image). 1460 device_specific.IncrementalOTA_InstallEnd() 1461 1462 if OPTIONS.extra_script is not None: 1463 script.AppendExtra(OPTIONS.extra_script) 1464 1465 # Patch the build.prop file last, so if something fails but the 1466 # device can still come up, it appears to be the old build and will 1467 # get set the OTA package again to retry. 1468 script.Print("Patching remaining system files...") 1469 system_diff.EmitDeferredPatches(script) 1470 1471 if OPTIONS.wipe_user_data: 1472 script.Print("Erasing user data...") 1473 script.FormatPartition("/data") 1474 1475 if OPTIONS.two_step: 1476 script.AppendExtra(""" 1477set_stage("%(bcb_dev)s", ""); 1478endif; 1479endif; 1480""" % bcb_dev) 1481 1482 if OPTIONS.verify and system_diff: 1483 script.Print("Remounting and verifying system partition files...") 1484 script.Unmount("/system") 1485 script.Mount("/system") 1486 system_diff.EmitExplicitTargetVerification(script) 1487 1488 if OPTIONS.verify and vendor_diff: 1489 script.Print("Remounting and verifying vendor partition files...") 1490 script.Unmount("/vendor") 1491 script.Mount("/vendor") 1492 vendor_diff.EmitExplicitTargetVerification(script) 1493 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) 1494 1495 WriteMetadata(metadata, output_zip) 1496 1497 1498def main(argv): 1499 1500 def option_handler(o, a): 1501 if o == "--board_config": 1502 pass # deprecated 1503 elif o in ("-k", "--package_key"): 1504 OPTIONS.package_key = a 1505 elif o in ("-i", "--incremental_from"): 1506 OPTIONS.incremental_source = a 1507 elif o == "--full_radio": 1508 OPTIONS.full_radio = True 1509 elif o in ("-w", "--wipe_user_data"): 1510 OPTIONS.wipe_user_data = True 1511 elif o in ("-n", "--no_prereq"): 1512 OPTIONS.omit_prereq = True 1513 elif o in ("-o", "--oem_settings"): 1514 OPTIONS.oem_source = a 1515 elif o in ("-e", "--extra_script"): 1516 OPTIONS.extra_script = a 1517 elif o in ("-a", "--aslr_mode"): 1518 if a in ("on", "On", "true", "True", "yes", "Yes"): 1519 OPTIONS.aslr_mode = True 1520 else: 1521 OPTIONS.aslr_mode = False 1522 elif o in ("-t", "--worker_threads"): 1523 if a.isdigit(): 1524 OPTIONS.worker_threads = int(a) 1525 else: 1526 raise ValueError("Cannot parse value %r for option %r - only " 1527 "integers are allowed." % (a, o)) 1528 elif o in ("-2", "--two_step"): 1529 OPTIONS.two_step = True 1530 elif o == "--no_signing": 1531 OPTIONS.no_signing = True 1532 elif o == "--verify": 1533 OPTIONS.verify = True 1534 elif o == "--block": 1535 OPTIONS.block_based = True 1536 elif o in ("-b", "--binary"): 1537 OPTIONS.updater_binary = a 1538 elif o in ("--no_fallback_to_full",): 1539 OPTIONS.fallback_to_full = False 1540 elif o == "--stash_threshold": 1541 try: 1542 OPTIONS.stash_threshold = float(a) 1543 except ValueError: 1544 raise ValueError("Cannot parse value %r for option %r - expecting " 1545 "a float" % (a, o)) 1546 else: 1547 return False 1548 return True 1549 1550 args = common.ParseOptions(argv, __doc__, 1551 extra_opts="b:k:i:d:wne:t:a:2o:", 1552 extra_long_opts=[ 1553 "board_config=", 1554 "package_key=", 1555 "incremental_from=", 1556 "full_radio", 1557 "wipe_user_data", 1558 "no_prereq", 1559 "extra_script=", 1560 "worker_threads=", 1561 "aslr_mode=", 1562 "two_step", 1563 "no_signing", 1564 "block", 1565 "binary=", 1566 "oem_settings=", 1567 "verify", 1568 "no_fallback_to_full", 1569 "stash_threshold=", 1570 ], extra_option_handler=option_handler) 1571 1572 if len(args) != 2: 1573 common.Usage(__doc__) 1574 sys.exit(1) 1575 1576 if OPTIONS.extra_script is not None: 1577 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 1578 1579 print "unzipping target target-files..." 1580 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 1581 1582 OPTIONS.target_tmp = OPTIONS.input_tmp 1583 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 1584 1585 # If this image was originally labelled with SELinux contexts, make sure we 1586 # also apply the labels in our new image. During building, the "file_contexts" 1587 # is in the out/ directory tree, but for repacking from target-files.zip it's 1588 # in the root directory of the ramdisk. 1589 if "selinux_fc" in OPTIONS.info_dict: 1590 OPTIONS.info_dict["selinux_fc"] = os.path.join( 1591 OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts") 1592 1593 if OPTIONS.verbose: 1594 print "--- target info ---" 1595 common.DumpInfoDict(OPTIONS.info_dict) 1596 1597 # If the caller explicitly specified the device-specific extensions 1598 # path via -s/--device_specific, use that. Otherwise, use 1599 # META/releasetools.py if it is present in the target target_files. 1600 # Otherwise, take the path of the file from 'tool_extensions' in the 1601 # info dict and look for that in the local filesystem, relative to 1602 # the current directory. 1603 1604 if OPTIONS.device_specific is None: 1605 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") 1606 if os.path.exists(from_input): 1607 print "(using device-specific extensions from target_files)" 1608 OPTIONS.device_specific = from_input 1609 else: 1610 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 1611 1612 if OPTIONS.device_specific is not None: 1613 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) 1614 1615 while True: 1616 1617 if OPTIONS.no_signing: 1618 if os.path.exists(args[1]): 1619 os.unlink(args[1]) 1620 output_zip = zipfile.ZipFile(args[1], "w", 1621 compression=zipfile.ZIP_DEFLATED) 1622 else: 1623 temp_zip_file = tempfile.NamedTemporaryFile() 1624 output_zip = zipfile.ZipFile(temp_zip_file, "w", 1625 compression=zipfile.ZIP_DEFLATED) 1626 1627 cache_size = OPTIONS.info_dict.get("cache_size", None) 1628 if cache_size is None: 1629 raise RuntimeError("can't determine the cache partition size") 1630 OPTIONS.cache_size = cache_size 1631 1632 if OPTIONS.incremental_source is None: 1633 WriteFullOTAPackage(input_zip, output_zip) 1634 if OPTIONS.package_key is None: 1635 OPTIONS.package_key = OPTIONS.info_dict.get( 1636 "default_system_dev_certificate", 1637 "build/target/product/security/testkey") 1638 common.ZipClose(output_zip) 1639 break 1640 1641 else: 1642 print "unzipping source target-files..." 1643 OPTIONS.source_tmp, source_zip = common.UnzipTemp( 1644 OPTIONS.incremental_source) 1645 OPTIONS.target_info_dict = OPTIONS.info_dict 1646 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 1647 if "selinux_fc" in OPTIONS.source_info_dict: 1648 OPTIONS.source_info_dict["selinux_fc"] = os.path.join( 1649 OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts") 1650 if OPTIONS.package_key is None: 1651 OPTIONS.package_key = OPTIONS.source_info_dict.get( 1652 "default_system_dev_certificate", 1653 "build/target/product/security/testkey") 1654 if OPTIONS.verbose: 1655 print "--- source info ---" 1656 common.DumpInfoDict(OPTIONS.source_info_dict) 1657 try: 1658 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 1659 common.ZipClose(output_zip) 1660 break 1661 except ValueError: 1662 if not OPTIONS.fallback_to_full: 1663 raise 1664 print "--- failed to build incremental; falling back to full ---" 1665 OPTIONS.incremental_source = None 1666 common.ZipClose(output_zip) 1667 1668 if not OPTIONS.no_signing: 1669 SignOutput(temp_zip_file.name, args[1]) 1670 temp_zip_file.close() 1671 1672 print "done." 1673 1674 1675if __name__ == '__main__': 1676 try: 1677 common.CloseInheritedPipes() 1678 main(sys.argv[1:]) 1679 except common.ExternalError as e: 1680 print 1681 print " ERROR: %s" % (e,) 1682 print 1683 sys.exit(1) 1684 finally: 1685 common.Cleanup() 1686