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