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