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