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