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