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