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