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