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