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