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