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