ota_from_target_files.py revision bebd3cfbf934beb18b73a4d4e98b98c2c0a1d6fe
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. Similarly for fstab, it might have changed 491 # in the target build. 492 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 493 494 oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") 495 recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options") 496 oem_dict = None 497 if oem_props is not None and len(oem_props) > 0: 498 if OPTIONS.oem_source is None: 499 raise common.ExternalError("OEM source required for this build") 500 script.Mount("/oem", recovery_mount_options) 501 oem_dict = common.LoadDictionaryFromLines( 502 open(OPTIONS.oem_source).readlines()) 503 504 metadata = { 505 "post-build": CalculateFingerprint(oem_props, oem_dict, 506 OPTIONS.info_dict), 507 "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, 508 OPTIONS.info_dict), 509 "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), 510 } 511 512 device_specific = common.DeviceSpecificParams( 513 input_zip=input_zip, 514 input_version=OPTIONS.info_dict["recovery_api_version"], 515 output_zip=output_zip, 516 script=script, 517 input_tmp=OPTIONS.input_tmp, 518 metadata=metadata, 519 info_dict=OPTIONS.info_dict) 520 521 has_recovery_patch = HasRecoveryPatch(input_zip) 522 block_based = OPTIONS.block_based and has_recovery_patch 523 524 if not OPTIONS.omit_prereq: 525 ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) 526 ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) 527 script.AssertOlderBuild(ts, ts_text) 528 529 AppendAssertions(script, OPTIONS.info_dict, oem_dict) 530 device_specific.FullOTA_Assertions() 531 532 # Two-step package strategy (in chronological order, which is *not* 533 # the order in which the generated script has things): 534 # 535 # if stage is not "2/3" or "3/3": 536 # write recovery image to boot partition 537 # set stage to "2/3" 538 # reboot to boot partition and restart recovery 539 # else if stage is "2/3": 540 # write recovery image to recovery partition 541 # set stage to "3/3" 542 # reboot to recovery partition and restart recovery 543 # else: 544 # (stage must be "3/3") 545 # set stage to "" 546 # do normal full package installation: 547 # wipe and install system, boot image, etc. 548 # set up system to update recovery partition on first boot 549 # complete script normally 550 # (allow recovery to mark itself finished and reboot) 551 552 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 553 OPTIONS.input_tmp, "RECOVERY") 554 if OPTIONS.two_step: 555 if not OPTIONS.info_dict.get("multistage_support", None): 556 assert False, "two-step packages not supported by this build" 557 fs = OPTIONS.info_dict["fstab"]["/misc"] 558 assert fs.fs_type.upper() == "EMMC", \ 559 "two-step packages only supported on devices with EMMC /misc partitions" 560 bcb_dev = {"bcb_dev": fs.device} 561 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data) 562 script.AppendExtra(""" 563if get_stage("%(bcb_dev)s") == "2/3" then 564""" % bcb_dev) 565 script.WriteRawImage("/recovery", "recovery.img") 566 script.AppendExtra(""" 567set_stage("%(bcb_dev)s", "3/3"); 568reboot_now("%(bcb_dev)s", "recovery"); 569else if get_stage("%(bcb_dev)s") == "3/3" then 570""" % bcb_dev) 571 572 # Dump fingerprints 573 script.Print("Target: %s" % CalculateFingerprint( 574 oem_props, oem_dict, OPTIONS.info_dict)) 575 576 device_specific.FullOTA_InstallBegin() 577 578 system_progress = 0.75 579 580 if OPTIONS.wipe_user_data: 581 system_progress -= 0.1 582 if HasVendorPartition(input_zip): 583 system_progress -= 0.1 584 585 if "selinux_fc" in OPTIONS.info_dict: 586 WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) 587 588 recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options") 589 590 system_items = ItemSet("system", "META/filesystem_config.txt") 591 script.ShowProgress(system_progress, 0) 592 593 if block_based: 594 # Full OTA is done as an "incremental" against an empty source 595 # image. This has the effect of writing new data from the package 596 # to the entire partition, but lets us reuse the updater code that 597 # writes incrementals to do it. 598 system_tgt = GetImage("system", OPTIONS.input_tmp, OPTIONS.info_dict) 599 system_tgt.ResetFileMap() 600 system_diff = common.BlockDifference("system", system_tgt, src=None) 601 system_diff.WriteScript(script, output_zip) 602 else: 603 script.FormatPartition("/system") 604 script.Mount("/system", recovery_mount_options) 605 if not has_recovery_patch: 606 script.UnpackPackageDir("recovery", "/system") 607 script.UnpackPackageDir("system", "/system") 608 609 symlinks = CopyPartitionFiles(system_items, input_zip, output_zip) 610 script.MakeSymlinks(symlinks) 611 612 boot_img = common.GetBootableImage("boot.img", "boot.img", 613 OPTIONS.input_tmp, "BOOT") 614 615 if not block_based: 616 def output_sink(fn, data): 617 common.ZipWriteStr(output_zip, "recovery/" + fn, data) 618 system_items.Get("system/" + fn) 619 620 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, 621 recovery_img, boot_img) 622 623 system_items.GetMetadata(input_zip) 624 system_items.Get("system").SetPermissions(script) 625 626 if HasVendorPartition(input_zip): 627 vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") 628 script.ShowProgress(0.1, 0) 629 630 if block_based: 631 vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict) 632 vendor_tgt.ResetFileMap() 633 vendor_diff = common.BlockDifference("vendor", vendor_tgt) 634 vendor_diff.WriteScript(script, output_zip) 635 else: 636 script.FormatPartition("/vendor") 637 script.Mount("/vendor", recovery_mount_options) 638 script.UnpackPackageDir("vendor", "/vendor") 639 640 symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip) 641 script.MakeSymlinks(symlinks) 642 643 vendor_items.GetMetadata(input_zip) 644 vendor_items.Get("vendor").SetPermissions(script) 645 646 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 647 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 648 649 script.ShowProgress(0.05, 5) 650 script.WriteRawImage("/boot", "boot.img") 651 652 script.ShowProgress(0.2, 10) 653 device_specific.FullOTA_InstallEnd() 654 655 if OPTIONS.extra_script is not None: 656 script.AppendExtra(OPTIONS.extra_script) 657 658 script.UnmountAll() 659 660 if OPTIONS.wipe_user_data: 661 script.ShowProgress(0.1, 10) 662 script.FormatPartition("/data") 663 664 if OPTIONS.two_step: 665 script.AppendExtra(""" 666set_stage("%(bcb_dev)s", ""); 667""" % bcb_dev) 668 script.AppendExtra("else\n") 669 script.WriteRawImage("/boot", "recovery.img") 670 script.AppendExtra(""" 671set_stage("%(bcb_dev)s", "2/3"); 672reboot_now("%(bcb_dev)s", ""); 673endif; 674endif; 675""" % bcb_dev) 676 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary) 677 WriteMetadata(metadata, output_zip) 678 679 680def WritePolicyConfig(file_name, output_zip): 681 common.ZipWrite(output_zip, file_name, os.path.basename(file_name)) 682 683 684def WriteMetadata(metadata, output_zip): 685 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 686 "".join(["%s=%s\n" % kv 687 for kv in sorted(metadata.iteritems())])) 688 689 690def LoadPartitionFiles(z, partition): 691 """Load all the files from the given partition in a given target-files 692 ZipFile, and return a dict of {filename: File object}.""" 693 out = {} 694 prefix = partition.upper() + "/" 695 for info in z.infolist(): 696 if info.filename.startswith(prefix) and not IsSymlink(info): 697 basefilename = info.filename[len(prefix):] 698 fn = partition + "/" + basefilename 699 data = z.read(info.filename) 700 out[fn] = common.File(fn, data) 701 return out 702 703 704def GetBuildProp(prop, info_dict): 705 """Return the fingerprint of the build of a given target-files info_dict.""" 706 try: 707 return info_dict.get("build.prop", {})[prop] 708 except KeyError: 709 raise common.ExternalError("couldn't find %s in build.prop" % (prop,)) 710 711 712def AddToKnownPaths(filename, known_paths): 713 if filename[-1] == "/": 714 return 715 dirs = filename.split("/")[:-1] 716 while len(dirs) > 0: 717 path = "/".join(dirs) 718 if path in known_paths: 719 break 720 known_paths.add(path) 721 dirs.pop() 722 723 724def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): 725 source_version = OPTIONS.source_info_dict["recovery_api_version"] 726 target_version = OPTIONS.target_info_dict["recovery_api_version"] 727 728 if source_version == 0: 729 print ("WARNING: generating edify script for a source that " 730 "can't install it.") 731 script = edify_generator.EdifyGenerator( 732 source_version, OPTIONS.target_info_dict, 733 fstab=OPTIONS.source_info_dict["fstab"]) 734 735 metadata = { 736 "pre-device": GetBuildProp("ro.product.device", 737 OPTIONS.source_info_dict), 738 "post-timestamp": GetBuildProp("ro.build.date.utc", 739 OPTIONS.target_info_dict), 740 } 741 742 device_specific = common.DeviceSpecificParams( 743 source_zip=source_zip, 744 source_version=source_version, 745 target_zip=target_zip, 746 target_version=target_version, 747 output_zip=output_zip, 748 script=script, 749 metadata=metadata, 750 info_dict=OPTIONS.info_dict) 751 752 # TODO: Currently this works differently from WriteIncrementalOTAPackage(). 753 # This function doesn't consider thumbprints when writing 754 # metadata["pre/post-build"]. One possible reason is that the current 755 # devices with thumbprints are all using file-based OTAs. Long term we 756 # should factor out the common parts into a shared one to avoid further 757 # divergence. 758 source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) 759 target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) 760 metadata["pre-build"] = source_fp 761 metadata["post-build"] = target_fp 762 763 source_boot = common.GetBootableImage( 764 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 765 OPTIONS.source_info_dict) 766 target_boot = common.GetBootableImage( 767 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 768 updating_boot = (not OPTIONS.two_step and 769 (source_boot.data != target_boot.data)) 770 771 target_recovery = common.GetBootableImage( 772 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 773 774 system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict) 775 system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict) 776 777 blockimgdiff_version = 1 778 if OPTIONS.info_dict: 779 blockimgdiff_version = max( 780 int(i) for i in 781 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(",")) 782 783 system_diff = common.BlockDifference("system", system_tgt, system_src, 784 version=blockimgdiff_version) 785 786 if HasVendorPartition(target_zip): 787 if not HasVendorPartition(source_zip): 788 raise RuntimeError("can't generate incremental that adds /vendor") 789 vendor_src = GetImage("vendor", OPTIONS.source_tmp, 790 OPTIONS.source_info_dict) 791 vendor_tgt = GetImage("vendor", OPTIONS.target_tmp, 792 OPTIONS.target_info_dict) 793 vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src, 794 version=blockimgdiff_version) 795 else: 796 vendor_diff = None 797 798 oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties") 799 recovery_mount_options = OPTIONS.source_info_dict.get( 800 "recovery_mount_options") 801 oem_dict = None 802 if oem_props is not None and len(oem_props) > 0: 803 if OPTIONS.oem_source is None: 804 raise common.ExternalError("OEM source required for this build") 805 script.Mount("/oem", recovery_mount_options) 806 oem_dict = common.LoadDictionaryFromLines( 807 open(OPTIONS.oem_source).readlines()) 808 809 AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) 810 device_specific.IncrementalOTA_Assertions() 811 812 # Two-step incremental package strategy (in chronological order, 813 # which is *not* the order in which the generated script has 814 # things): 815 # 816 # if stage is not "2/3" or "3/3": 817 # do verification on current system 818 # write recovery image to boot partition 819 # set stage to "2/3" 820 # reboot to boot partition and restart recovery 821 # else if stage is "2/3": 822 # write recovery image to recovery partition 823 # set stage to "3/3" 824 # reboot to recovery partition and restart recovery 825 # else: 826 # (stage must be "3/3") 827 # perform update: 828 # patch system files, etc. 829 # force full install of new boot image 830 # set up system to update recovery partition on first boot 831 # complete script normally 832 # (allow recovery to mark itself finished and reboot) 833 834 if OPTIONS.two_step: 835 if not OPTIONS.info_dict.get("multistage_support", None): 836 assert False, "two-step packages not supported by this build" 837 fs = OPTIONS.info_dict["fstab"]["/misc"] 838 assert fs.fs_type.upper() == "EMMC", \ 839 "two-step packages only supported on devices with EMMC /misc partitions" 840 bcb_dev = {"bcb_dev": fs.device} 841 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 842 script.AppendExtra(""" 843if get_stage("%(bcb_dev)s") == "2/3" then 844""" % bcb_dev) 845 script.AppendExtra("sleep(20);\n") 846 script.WriteRawImage("/recovery", "recovery.img") 847 script.AppendExtra(""" 848set_stage("%(bcb_dev)s", "3/3"); 849reboot_now("%(bcb_dev)s", "recovery"); 850else if get_stage("%(bcb_dev)s") != "3/3" then 851""" % bcb_dev) 852 853 # Dump fingerprints 854 script.Print("Source: %s" % CalculateFingerprint( 855 oem_props, oem_dict, OPTIONS.source_info_dict)) 856 script.Print("Target: %s" % CalculateFingerprint( 857 oem_props, oem_dict, OPTIONS.target_info_dict)) 858 859 script.Print("Verifying current system...") 860 861 device_specific.IncrementalOTA_VerifyBegin() 862 863 if oem_props is None: 864 # When blockimgdiff version is less than 3 (non-resumable block-based OTA), 865 # patching on a device that's already on the target build will damage the 866 # system. Because operations like move don't check the block state, they 867 # always apply the changes unconditionally. 868 if blockimgdiff_version <= 2: 869 script.AssertSomeFingerprint(source_fp) 870 else: 871 script.AssertSomeFingerprint(source_fp, target_fp) 872 else: 873 if blockimgdiff_version <= 2: 874 script.AssertSomeThumbprint( 875 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 876 else: 877 script.AssertSomeThumbprint( 878 GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), 879 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 880 881 if updating_boot: 882 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 883 d = common.Difference(target_boot, source_boot) 884 _, _, d = d.ComputePatch() 885 if d is None: 886 include_full_boot = True 887 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 888 else: 889 include_full_boot = False 890 891 print "boot target: %d source: %d diff: %d" % ( 892 target_boot.size, source_boot.size, len(d)) 893 894 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 895 896 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 897 (boot_type, boot_device, 898 source_boot.size, source_boot.sha1, 899 target_boot.size, target_boot.sha1)) 900 901 device_specific.IncrementalOTA_VerifyEnd() 902 903 if OPTIONS.two_step: 904 script.WriteRawImage("/boot", "recovery.img") 905 script.AppendExtra(""" 906set_stage("%(bcb_dev)s", "2/3"); 907reboot_now("%(bcb_dev)s", ""); 908else 909""" % bcb_dev) 910 911 # Verify the existing partitions. 912 system_diff.WriteVerifyScript(script) 913 if vendor_diff: 914 vendor_diff.WriteVerifyScript(script) 915 916 script.Comment("---- start making changes here ----") 917 918 device_specific.IncrementalOTA_InstallBegin() 919 920 system_diff.WriteScript(script, output_zip, 921 progress=0.8 if vendor_diff else 0.9) 922 if vendor_diff: 923 vendor_diff.WriteScript(script, output_zip, progress=0.1) 924 925 if OPTIONS.two_step: 926 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 927 script.WriteRawImage("/boot", "boot.img") 928 print "writing full boot image (forced by two-step mode)" 929 930 if not OPTIONS.two_step: 931 if updating_boot: 932 if include_full_boot: 933 print "boot image changed; including full." 934 script.Print("Installing boot image...") 935 script.WriteRawImage("/boot", "boot.img") 936 else: 937 # Produce the boot image by applying a patch to the current 938 # contents of the boot partition, and write it back to the 939 # partition. 940 print "boot image changed; including patch." 941 script.Print("Patching boot image...") 942 script.ShowProgress(0.1, 10) 943 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 944 % (boot_type, boot_device, 945 source_boot.size, source_boot.sha1, 946 target_boot.size, target_boot.sha1), 947 "-", 948 target_boot.size, target_boot.sha1, 949 source_boot.sha1, "patch/boot.img.p") 950 else: 951 print "boot image unchanged; skipping." 952 953 # Do device-specific installation (eg, write radio image). 954 device_specific.IncrementalOTA_InstallEnd() 955 956 if OPTIONS.extra_script is not None: 957 script.AppendExtra(OPTIONS.extra_script) 958 959 if OPTIONS.wipe_user_data: 960 script.Print("Erasing user data...") 961 script.FormatPartition("/data") 962 963 if OPTIONS.two_step: 964 script.AppendExtra(""" 965set_stage("%(bcb_dev)s", ""); 966endif; 967endif; 968""" % bcb_dev) 969 970 script.SetProgress(1) 971 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) 972 WriteMetadata(metadata, output_zip) 973 974 975class FileDifference(object): 976 def __init__(self, partition, source_zip, target_zip, output_zip): 977 self.deferred_patch_list = None 978 print "Loading target..." 979 self.target_data = target_data = LoadPartitionFiles(target_zip, partition) 980 print "Loading source..." 981 self.source_data = source_data = LoadPartitionFiles(source_zip, partition) 982 983 self.verbatim_targets = verbatim_targets = [] 984 self.patch_list = patch_list = [] 985 diffs = [] 986 self.renames = renames = {} 987 known_paths = set() 988 largest_source_size = 0 989 990 matching_file_cache = {} 991 for fn, sf in source_data.items(): 992 assert fn == sf.name 993 matching_file_cache["path:" + fn] = sf 994 if fn in target_data.keys(): 995 AddToKnownPaths(fn, known_paths) 996 # Only allow eligibility for filename/sha matching 997 # if there isn't a perfect path match. 998 if target_data.get(sf.name) is None: 999 matching_file_cache["file:" + fn.split("/")[-1]] = sf 1000 matching_file_cache["sha:" + sf.sha1] = sf 1001 1002 for fn in sorted(target_data.keys()): 1003 tf = target_data[fn] 1004 assert fn == tf.name 1005 sf = ClosestFileMatch(tf, matching_file_cache, renames) 1006 if sf is not None and sf.name != tf.name: 1007 print "File has moved from " + sf.name + " to " + tf.name 1008 renames[sf.name] = tf 1009 1010 if sf is None or fn in OPTIONS.require_verbatim: 1011 # This file should be included verbatim 1012 if fn in OPTIONS.prohibit_verbatim: 1013 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 1014 print "send", fn, "verbatim" 1015 tf.AddToZip(output_zip) 1016 verbatim_targets.append((fn, tf.size, tf.sha1)) 1017 if fn in target_data.keys(): 1018 AddToKnownPaths(fn, known_paths) 1019 elif tf.sha1 != sf.sha1: 1020 # File is different; consider sending as a patch 1021 diffs.append(common.Difference(tf, sf)) 1022 else: 1023 # Target file data identical to source (may still be renamed) 1024 pass 1025 1026 common.ComputeDifferences(diffs) 1027 1028 for diff in diffs: 1029 tf, sf, d = diff.GetPatch() 1030 path = "/".join(tf.name.split("/")[:-1]) 1031 if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \ 1032 path not in known_paths: 1033 # patch is almost as big as the file; don't bother patching 1034 # or a patch + rename cannot take place due to the target 1035 # directory not existing 1036 tf.AddToZip(output_zip) 1037 verbatim_targets.append((tf.name, tf.size, tf.sha1)) 1038 if sf.name in renames: 1039 del renames[sf.name] 1040 AddToKnownPaths(tf.name, known_paths) 1041 else: 1042 common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d) 1043 patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest())) 1044 largest_source_size = max(largest_source_size, sf.size) 1045 1046 self.largest_source_size = largest_source_size 1047 1048 def EmitVerification(self, script): 1049 so_far = 0 1050 for tf, sf, _, _ in self.patch_list: 1051 if tf.name != sf.name: 1052 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 1053 script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1) 1054 so_far += sf.size 1055 return so_far 1056 1057 def EmitExplicitTargetVerification(self, script): 1058 for fn, _, sha1 in self.verbatim_targets: 1059 if fn[-1] != "/": 1060 script.FileCheck("/"+fn, sha1) 1061 for tf, _, _, _ in self.patch_list: 1062 script.FileCheck(tf.name, tf.sha1) 1063 1064 def RemoveUnneededFiles(self, script, extras=()): 1065 script.DeleteFiles( 1066 ["/" + i[0] for i in self.verbatim_targets] + 1067 ["/" + i for i in sorted(self.source_data) 1068 if i not in self.target_data and i not in self.renames] + 1069 list(extras)) 1070 1071 def TotalPatchSize(self): 1072 return sum(i[1].size for i in self.patch_list) 1073 1074 def EmitPatches(self, script, total_patch_size, so_far): 1075 self.deferred_patch_list = deferred_patch_list = [] 1076 for item in self.patch_list: 1077 tf, sf, _, _ = item 1078 if tf.name == "system/build.prop": 1079 deferred_patch_list.append(item) 1080 continue 1081 if sf.name != tf.name: 1082 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 1083 script.ApplyPatch("/" + sf.name, "-", tf.size, tf.sha1, sf.sha1, 1084 "patch/" + sf.name + ".p") 1085 so_far += tf.size 1086 script.SetProgress(so_far / total_patch_size) 1087 return so_far 1088 1089 def EmitDeferredPatches(self, script): 1090 for item in self.deferred_patch_list: 1091 tf, sf, _, _ = item 1092 script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, 1093 "patch/" + sf.name + ".p") 1094 script.SetPermissions("/system/build.prop", 0, 0, 0o644, None, None) 1095 1096 def EmitRenames(self, script): 1097 if len(self.renames) > 0: 1098 script.Print("Renaming files...") 1099 for src, tgt in self.renames.iteritems(): 1100 print "Renaming " + src + " to " + tgt.name 1101 script.RenameFile(src, tgt.name) 1102 1103 1104def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 1105 target_has_recovery_patch = HasRecoveryPatch(target_zip) 1106 source_has_recovery_patch = HasRecoveryPatch(source_zip) 1107 1108 if (OPTIONS.block_based and 1109 target_has_recovery_patch and 1110 source_has_recovery_patch): 1111 return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip) 1112 1113 source_version = OPTIONS.source_info_dict["recovery_api_version"] 1114 target_version = OPTIONS.target_info_dict["recovery_api_version"] 1115 1116 if source_version == 0: 1117 print ("WARNING: generating edify script for a source that " 1118 "can't install it.") 1119 script = edify_generator.EdifyGenerator( 1120 source_version, OPTIONS.target_info_dict, 1121 fstab=OPTIONS.source_info_dict["fstab"]) 1122 1123 oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties") 1124 recovery_mount_options = OPTIONS.source_info_dict.get( 1125 "recovery_mount_options") 1126 oem_dict = None 1127 if oem_props is not None and len(oem_props) > 0: 1128 if OPTIONS.oem_source is None: 1129 raise common.ExternalError("OEM source required for this build") 1130 script.Mount("/oem", recovery_mount_options) 1131 oem_dict = common.LoadDictionaryFromLines( 1132 open(OPTIONS.oem_source).readlines()) 1133 1134 metadata = { 1135 "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, 1136 OPTIONS.source_info_dict), 1137 "post-timestamp": GetBuildProp("ro.build.date.utc", 1138 OPTIONS.target_info_dict), 1139 } 1140 1141 device_specific = common.DeviceSpecificParams( 1142 source_zip=source_zip, 1143 source_version=source_version, 1144 target_zip=target_zip, 1145 target_version=target_version, 1146 output_zip=output_zip, 1147 script=script, 1148 metadata=metadata, 1149 info_dict=OPTIONS.info_dict) 1150 1151 system_diff = FileDifference("system", source_zip, target_zip, output_zip) 1152 script.Mount("/system", recovery_mount_options) 1153 if HasVendorPartition(target_zip): 1154 vendor_diff = FileDifference("vendor", source_zip, target_zip, output_zip) 1155 script.Mount("/vendor", recovery_mount_options) 1156 else: 1157 vendor_diff = None 1158 1159 target_fp = CalculateFingerprint(oem_props, oem_dict, 1160 OPTIONS.target_info_dict) 1161 source_fp = CalculateFingerprint(oem_props, oem_dict, 1162 OPTIONS.source_info_dict) 1163 1164 if oem_props is None: 1165 script.AssertSomeFingerprint(source_fp, target_fp) 1166 else: 1167 script.AssertSomeThumbprint( 1168 GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), 1169 GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) 1170 1171 metadata["pre-build"] = source_fp 1172 metadata["post-build"] = target_fp 1173 1174 source_boot = common.GetBootableImage( 1175 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 1176 OPTIONS.source_info_dict) 1177 target_boot = common.GetBootableImage( 1178 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 1179 updating_boot = (not OPTIONS.two_step and 1180 (source_boot.data != target_boot.data)) 1181 1182 source_recovery = common.GetBootableImage( 1183 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", 1184 OPTIONS.source_info_dict) 1185 target_recovery = common.GetBootableImage( 1186 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 1187 updating_recovery = (source_recovery.data != target_recovery.data) 1188 1189 # Here's how we divide up the progress bar: 1190 # 0.1 for verifying the start state (PatchCheck calls) 1191 # 0.8 for applying patches (ApplyPatch calls) 1192 # 0.1 for unpacking verbatim files, symlinking, and doing the 1193 # device-specific commands. 1194 1195 AppendAssertions(script, OPTIONS.target_info_dict, oem_dict) 1196 device_specific.IncrementalOTA_Assertions() 1197 1198 # Two-step incremental package strategy (in chronological order, 1199 # which is *not* the order in which the generated script has 1200 # things): 1201 # 1202 # if stage is not "2/3" or "3/3": 1203 # do verification on current system 1204 # write recovery image to boot partition 1205 # set stage to "2/3" 1206 # reboot to boot partition and restart recovery 1207 # else if stage is "2/3": 1208 # write recovery image to recovery partition 1209 # set stage to "3/3" 1210 # reboot to recovery partition and restart recovery 1211 # else: 1212 # (stage must be "3/3") 1213 # perform update: 1214 # patch system files, etc. 1215 # force full install of new boot image 1216 # set up system to update recovery partition on first boot 1217 # complete script normally 1218 # (allow recovery to mark itself finished and reboot) 1219 1220 if OPTIONS.two_step: 1221 if not OPTIONS.info_dict.get("multistage_support", None): 1222 assert False, "two-step packages not supported by this build" 1223 fs = OPTIONS.info_dict["fstab"]["/misc"] 1224 assert fs.fs_type.upper() == "EMMC", \ 1225 "two-step packages only supported on devices with EMMC /misc partitions" 1226 bcb_dev = {"bcb_dev": fs.device} 1227 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 1228 script.AppendExtra(""" 1229if get_stage("%(bcb_dev)s") == "2/3" then 1230""" % bcb_dev) 1231 script.AppendExtra("sleep(20);\n") 1232 script.WriteRawImage("/recovery", "recovery.img") 1233 script.AppendExtra(""" 1234set_stage("%(bcb_dev)s", "3/3"); 1235reboot_now("%(bcb_dev)s", "recovery"); 1236else if get_stage("%(bcb_dev)s") != "3/3" then 1237""" % bcb_dev) 1238 1239 # Dump fingerprints 1240 script.Print("Source: %s" % (source_fp,)) 1241 script.Print("Target: %s" % (target_fp,)) 1242 1243 script.Print("Verifying current system...") 1244 1245 device_specific.IncrementalOTA_VerifyBegin() 1246 1247 script.ShowProgress(0.1, 0) 1248 so_far = system_diff.EmitVerification(script) 1249 if vendor_diff: 1250 so_far += vendor_diff.EmitVerification(script) 1251 1252 if updating_boot: 1253 d = common.Difference(target_boot, source_boot) 1254 _, _, d = d.ComputePatch() 1255 print "boot target: %d source: %d diff: %d" % ( 1256 target_boot.size, source_boot.size, len(d)) 1257 1258 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 1259 1260 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 1261 1262 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 1263 (boot_type, boot_device, 1264 source_boot.size, source_boot.sha1, 1265 target_boot.size, target_boot.sha1)) 1266 so_far += source_boot.size 1267 1268 size = [] 1269 if system_diff.patch_list: 1270 size.append(system_diff.largest_source_size) 1271 if vendor_diff: 1272 if vendor_diff.patch_list: 1273 size.append(vendor_diff.largest_source_size) 1274 if size or updating_recovery or updating_boot: 1275 script.CacheFreeSpaceCheck(max(size)) 1276 1277 device_specific.IncrementalOTA_VerifyEnd() 1278 1279 if OPTIONS.two_step: 1280 script.WriteRawImage("/boot", "recovery.img") 1281 script.AppendExtra(""" 1282set_stage("%(bcb_dev)s", "2/3"); 1283reboot_now("%(bcb_dev)s", ""); 1284else 1285""" % bcb_dev) 1286 1287 script.Comment("---- start making changes here ----") 1288 1289 device_specific.IncrementalOTA_InstallBegin() 1290 1291 if OPTIONS.two_step: 1292 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 1293 script.WriteRawImage("/boot", "boot.img") 1294 print "writing full boot image (forced by two-step mode)" 1295 1296 script.Print("Removing unneeded files...") 1297 system_diff.RemoveUnneededFiles(script, ("/system/recovery.img",)) 1298 if vendor_diff: 1299 vendor_diff.RemoveUnneededFiles(script) 1300 1301 script.ShowProgress(0.8, 0) 1302 total_patch_size = 1.0 + system_diff.TotalPatchSize() 1303 if vendor_diff: 1304 total_patch_size += vendor_diff.TotalPatchSize() 1305 if updating_boot: 1306 total_patch_size += target_boot.size 1307 1308 script.Print("Patching system files...") 1309 so_far = system_diff.EmitPatches(script, total_patch_size, 0) 1310 if vendor_diff: 1311 script.Print("Patching vendor files...") 1312 so_far = vendor_diff.EmitPatches(script, total_patch_size, so_far) 1313 1314 if not OPTIONS.two_step: 1315 if updating_boot: 1316 # Produce the boot image by applying a patch to the current 1317 # contents of the boot partition, and write it back to the 1318 # partition. 1319 script.Print("Patching boot image...") 1320 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 1321 % (boot_type, boot_device, 1322 source_boot.size, source_boot.sha1, 1323 target_boot.size, target_boot.sha1), 1324 "-", 1325 target_boot.size, target_boot.sha1, 1326 source_boot.sha1, "patch/boot.img.p") 1327 so_far += target_boot.size 1328 script.SetProgress(so_far / total_patch_size) 1329 print "boot image changed; including." 1330 else: 1331 print "boot image unchanged; skipping." 1332 1333 system_items = ItemSet("system", "META/filesystem_config.txt") 1334 if vendor_diff: 1335 vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt") 1336 1337 if updating_recovery: 1338 # Recovery is generated as a patch using both the boot image 1339 # (which contains the same linux kernel as recovery) and the file 1340 # /system/etc/recovery-resource.dat (which contains all the images 1341 # used in the recovery UI) as sources. This lets us minimize the 1342 # size of the patch, which must be included in every OTA package. 1343 # 1344 # For older builds where recovery-resource.dat is not present, we 1345 # use only the boot image as the source. 1346 1347 if not target_has_recovery_patch: 1348 def output_sink(fn, data): 1349 common.ZipWriteStr(output_zip, "recovery/" + fn, data) 1350 system_items.Get("system/" + fn) 1351 1352 common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink, 1353 target_recovery, target_boot) 1354 script.DeleteFiles(["/system/recovery-from-boot.p", 1355 "/system/etc/install-recovery.sh"]) 1356 print "recovery image changed; including as patch from boot." 1357 else: 1358 print "recovery image unchanged; skipping." 1359 1360 script.ShowProgress(0.1, 10) 1361 1362 target_symlinks = CopyPartitionFiles(system_items, target_zip, None) 1363 if vendor_diff: 1364 target_symlinks.extend(CopyPartitionFiles(vendor_items, target_zip, None)) 1365 1366 temp_script = script.MakeTemporary() 1367 system_items.GetMetadata(target_zip) 1368 system_items.Get("system").SetPermissions(temp_script) 1369 if vendor_diff: 1370 vendor_items.GetMetadata(target_zip) 1371 vendor_items.Get("vendor").SetPermissions(temp_script) 1372 1373 # Note that this call will mess up the trees of Items, so make sure 1374 # we're done with them. 1375 source_symlinks = CopyPartitionFiles(system_items, source_zip, None) 1376 if vendor_diff: 1377 source_symlinks.extend(CopyPartitionFiles(vendor_items, source_zip, None)) 1378 1379 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 1380 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 1381 1382 # Delete all the symlinks in source that aren't in target. This 1383 # needs to happen before verbatim files are unpacked, in case a 1384 # symlink in the source is replaced by a real file in the target. 1385 to_delete = [] 1386 for dest, link in source_symlinks: 1387 if link not in target_symlinks_d: 1388 to_delete.append(link) 1389 script.DeleteFiles(to_delete) 1390 1391 if system_diff.verbatim_targets: 1392 script.Print("Unpacking new system files...") 1393 script.UnpackPackageDir("system", "/system") 1394 if vendor_diff and vendor_diff.verbatim_targets: 1395 script.Print("Unpacking new vendor files...") 1396 script.UnpackPackageDir("vendor", "/vendor") 1397 1398 if updating_recovery and not target_has_recovery_patch: 1399 script.Print("Unpacking new recovery...") 1400 script.UnpackPackageDir("recovery", "/system") 1401 1402 system_diff.EmitRenames(script) 1403 if vendor_diff: 1404 vendor_diff.EmitRenames(script) 1405 1406 script.Print("Symlinks and permissions...") 1407 1408 # Create all the symlinks that don't already exist, or point to 1409 # somewhere different than what we want. Delete each symlink before 1410 # creating it, since the 'symlink' command won't overwrite. 1411 to_create = [] 1412 for dest, link in target_symlinks: 1413 if link in source_symlinks_d: 1414 if dest != source_symlinks_d[link]: 1415 to_create.append((dest, link)) 1416 else: 1417 to_create.append((dest, link)) 1418 script.DeleteFiles([i[1] for i in to_create]) 1419 script.MakeSymlinks(to_create) 1420 1421 # Now that the symlinks are created, we can set all the 1422 # permissions. 1423 script.AppendScript(temp_script) 1424 1425 # Do device-specific installation (eg, write radio image). 1426 device_specific.IncrementalOTA_InstallEnd() 1427 1428 if OPTIONS.extra_script is not None: 1429 script.AppendExtra(OPTIONS.extra_script) 1430 1431 # Patch the build.prop file last, so if something fails but the 1432 # device can still come up, it appears to be the old build and will 1433 # get set the OTA package again to retry. 1434 script.Print("Patching remaining system files...") 1435 system_diff.EmitDeferredPatches(script) 1436 1437 if OPTIONS.wipe_user_data: 1438 script.Print("Erasing user data...") 1439 script.FormatPartition("/data") 1440 1441 if OPTIONS.two_step: 1442 script.AppendExtra(""" 1443set_stage("%(bcb_dev)s", ""); 1444endif; 1445endif; 1446""" % bcb_dev) 1447 1448 if OPTIONS.verify and system_diff: 1449 script.Print("Remounting and verifying system partition files...") 1450 script.Unmount("/system") 1451 script.Mount("/system") 1452 system_diff.EmitExplicitTargetVerification(script) 1453 1454 if OPTIONS.verify and vendor_diff: 1455 script.Print("Remounting and verifying vendor partition files...") 1456 script.Unmount("/vendor") 1457 script.Mount("/vendor") 1458 vendor_diff.EmitExplicitTargetVerification(script) 1459 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary) 1460 1461 WriteMetadata(metadata, output_zip) 1462 1463 1464def main(argv): 1465 1466 def option_handler(o, a): 1467 if o == "--board_config": 1468 pass # deprecated 1469 elif o in ("-k", "--package_key"): 1470 OPTIONS.package_key = a 1471 elif o in ("-i", "--incremental_from"): 1472 OPTIONS.incremental_source = a 1473 elif o == "--full_radio": 1474 OPTIONS.full_radio = True 1475 elif o in ("-w", "--wipe_user_data"): 1476 OPTIONS.wipe_user_data = True 1477 elif o in ("-n", "--no_prereq"): 1478 OPTIONS.omit_prereq = True 1479 elif o in ("-o", "--oem_settings"): 1480 OPTIONS.oem_source = a 1481 elif o in ("-e", "--extra_script"): 1482 OPTIONS.extra_script = a 1483 elif o in ("-a", "--aslr_mode"): 1484 if a in ("on", "On", "true", "True", "yes", "Yes"): 1485 OPTIONS.aslr_mode = True 1486 else: 1487 OPTIONS.aslr_mode = False 1488 elif o in ("-t", "--worker_threads"): 1489 if a.isdigit(): 1490 OPTIONS.worker_threads = int(a) 1491 else: 1492 raise ValueError("Cannot parse value %r for option %r - only " 1493 "integers are allowed." % (a, o)) 1494 elif o in ("-2", "--two_step"): 1495 OPTIONS.two_step = True 1496 elif o == "--no_signing": 1497 OPTIONS.no_signing = True 1498 elif o == "--verify": 1499 OPTIONS.verify = True 1500 elif o == "--block": 1501 OPTIONS.block_based = True 1502 elif o in ("-b", "--binary"): 1503 OPTIONS.updater_binary = a 1504 elif o in ("--no_fallback_to_full",): 1505 OPTIONS.fallback_to_full = False 1506 else: 1507 return False 1508 return True 1509 1510 args = common.ParseOptions(argv, __doc__, 1511 extra_opts="b:k:i:d:wne:t:a:2o:", 1512 extra_long_opts=[ 1513 "board_config=", 1514 "package_key=", 1515 "incremental_from=", 1516 "full_radio", 1517 "wipe_user_data", 1518 "no_prereq", 1519 "extra_script=", 1520 "worker_threads=", 1521 "aslr_mode=", 1522 "two_step", 1523 "no_signing", 1524 "block", 1525 "binary=", 1526 "oem_settings=", 1527 "verify", 1528 "no_fallback_to_full", 1529 ], extra_option_handler=option_handler) 1530 1531 if len(args) != 2: 1532 common.Usage(__doc__) 1533 sys.exit(1) 1534 1535 if OPTIONS.extra_script is not None: 1536 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 1537 1538 print "unzipping target target-files..." 1539 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 1540 1541 OPTIONS.target_tmp = OPTIONS.input_tmp 1542 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 1543 1544 # If this image was originally labelled with SELinux contexts, make sure we 1545 # also apply the labels in our new image. During building, the "file_contexts" 1546 # is in the out/ directory tree, but for repacking from target-files.zip it's 1547 # in the root directory of the ramdisk. 1548 if "selinux_fc" in OPTIONS.info_dict: 1549 OPTIONS.info_dict["selinux_fc"] = os.path.join( 1550 OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts") 1551 1552 if OPTIONS.verbose: 1553 print "--- target info ---" 1554 common.DumpInfoDict(OPTIONS.info_dict) 1555 1556 # If the caller explicitly specified the device-specific extensions 1557 # path via -s/--device_specific, use that. Otherwise, use 1558 # META/releasetools.py if it is present in the target target_files. 1559 # Otherwise, take the path of the file from 'tool_extensions' in the 1560 # info dict and look for that in the local filesystem, relative to 1561 # the current directory. 1562 1563 if OPTIONS.device_specific is None: 1564 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py") 1565 if os.path.exists(from_input): 1566 print "(using device-specific extensions from target_files)" 1567 OPTIONS.device_specific = from_input 1568 else: 1569 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 1570 1571 if OPTIONS.device_specific is not None: 1572 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific) 1573 1574 while True: 1575 1576 if OPTIONS.no_signing: 1577 if os.path.exists(args[1]): 1578 os.unlink(args[1]) 1579 output_zip = zipfile.ZipFile(args[1], "w", 1580 compression=zipfile.ZIP_DEFLATED) 1581 else: 1582 temp_zip_file = tempfile.NamedTemporaryFile() 1583 output_zip = zipfile.ZipFile(temp_zip_file, "w", 1584 compression=zipfile.ZIP_DEFLATED) 1585 1586 if OPTIONS.incremental_source is None: 1587 WriteFullOTAPackage(input_zip, output_zip) 1588 if OPTIONS.package_key is None: 1589 OPTIONS.package_key = OPTIONS.info_dict.get( 1590 "default_system_dev_certificate", 1591 "build/target/product/security/testkey") 1592 common.ZipClose(output_zip) 1593 break 1594 1595 else: 1596 print "unzipping source target-files..." 1597 OPTIONS.source_tmp, source_zip = common.UnzipTemp( 1598 OPTIONS.incremental_source) 1599 OPTIONS.target_info_dict = OPTIONS.info_dict 1600 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 1601 if "selinux_fc" in OPTIONS.source_info_dict: 1602 OPTIONS.source_info_dict["selinux_fc"] = os.path.join( 1603 OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts") 1604 if OPTIONS.package_key is None: 1605 OPTIONS.package_key = OPTIONS.source_info_dict.get( 1606 "default_system_dev_certificate", 1607 "build/target/product/security/testkey") 1608 if OPTIONS.verbose: 1609 print "--- source info ---" 1610 common.DumpInfoDict(OPTIONS.source_info_dict) 1611 try: 1612 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 1613 common.ZipClose(output_zip) 1614 break 1615 except ValueError: 1616 if not OPTIONS.fallback_to_full: 1617 raise 1618 print "--- failed to build incremental; falling back to full ---" 1619 OPTIONS.incremental_source = None 1620 common.ZipClose(output_zip) 1621 1622 if not OPTIONS.no_signing: 1623 SignOutput(temp_zip_file.name, args[1]) 1624 temp_zip_file.close() 1625 1626 print "done." 1627 1628 1629if __name__ == '__main__': 1630 try: 1631 common.CloseInheritedPipes() 1632 main(sys.argv[1:]) 1633 except common.ExternalError as e: 1634 print 1635 print " ERROR: %s" % (e,) 1636 print 1637 sys.exit(1) 1638 finally: 1639 common.Cleanup() 1640