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