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