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