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