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