1 #!/usr/bin/env python 2 # 3 # Copyright (C) 2008 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """ 18 Given a target-files zipfile, produces an OTA package that installs 19 that build. An incremental OTA is produced if -i is given, otherwise 20 a full OTA is produced. 21 22 Usage: ota_from_target_files [flags] input_target_files output_ota_package 23 24 -b (--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 -w (--wipe_user_data) 41 Generate an OTA package that will wipe the user data partition 42 when installed. 43 44 -n (--no_prereq) 45 Omit the timestamp prereq check normally included at the top of 46 the build scripts (used for developer OTA packages which 47 legitimately need to go back and forth). 48 49 -e (--extra_script) <file> 50 Insert the contents of file at the end of the update script. 51 52 -a (--aslr_mode) <on|off> 53 Specify whether to turn on ASLR for the package (on by default). 54 55 -2 (--two_step) 56 Generate a 'two-step' OTA package, where recovery is updated 57 first, so that any changes made to the system partition are done 58 using the new recovery (new kernel, etc.). 59 60 """ 61 62 import sys 63 64 if sys.hexversion < 0x02040000: 65 print >> sys.stderr, "Python 2.4 or newer is required." 66 sys.exit(1) 67 68 import copy 69 import errno 70 import os 71 import re 72 import subprocess 73 import tempfile 74 import time 75 import zipfile 76 77 try: 78 from hashlib import sha1 as sha1 79 except ImportError: 80 from sha import sha as sha1 81 82 import common 83 import edify_generator 84 85 OPTIONS = common.OPTIONS 86 OPTIONS.package_key = None 87 OPTIONS.incremental_source = None 88 OPTIONS.require_verbatim = set() 89 OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 90 OPTIONS.patch_threshold = 0.95 91 OPTIONS.wipe_user_data = False 92 OPTIONS.omit_prereq = False 93 OPTIONS.extra_script = None 94 OPTIONS.aslr_mode = True 95 OPTIONS.worker_threads = 3 96 OPTIONS.two_step = False 97 98 def MostPopularKey(d, default): 99 """Given a dict, return the key corresponding to the largest 100 value. Returns 'default' if the dict is empty.""" 101 x = [(v, k) for (k, v) in d.iteritems()] 102 if not x: return default 103 x.sort() 104 return x[-1][1] 105 106 107 def IsSymlink(info): 108 """Return true if the zipfile.ZipInfo object passed in represents a 109 symlink.""" 110 return (info.external_attr >> 16) == 0120777 111 112 def IsRegular(info): 113 """Return true if the zipfile.ZipInfo object passed in represents a 114 symlink.""" 115 return (info.external_attr >> 28) == 010 116 117 def ClosestFileMatch(src, tgtfiles, existing): 118 """Returns the closest file match between a source file and list 119 of potential matches. The exact filename match is preferred, 120 then the sha1 is searched for, and finally a file with the same 121 basename is evaluated. Rename support in the updater-binary is 122 required for the latter checks to be used.""" 123 124 result = tgtfiles.get("path:" + src.name) 125 if result is not None: 126 return result 127 128 if not OPTIONS.target_info_dict.get("update_rename_support", False): 129 return None 130 131 if src.size < 1000: 132 return None 133 134 result = tgtfiles.get("sha1:" + src.sha1) 135 if result is not None and existing.get(result.name) is None: 136 return result 137 result = tgtfiles.get("file:" + src.name.split("/")[-1]) 138 if result is not None and existing.get(result.name) is None: 139 return result 140 return None 141 142 class Item: 143 """Items represent the metadata (user, group, mode) of files and 144 directories in the system image.""" 145 ITEMS = {} 146 def __init__(self, name, dir=False): 147 self.name = name 148 self.uid = None 149 self.gid = None 150 self.mode = None 151 self.selabel = None 152 self.capabilities = None 153 self.dir = dir 154 155 if name: 156 self.parent = Item.Get(os.path.dirname(name), dir=True) 157 self.parent.children.append(self) 158 else: 159 self.parent = None 160 if dir: 161 self.children = [] 162 163 def Dump(self, indent=0): 164 if self.uid is not None: 165 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) 166 else: 167 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) 168 if self.dir: 169 print "%s%s" % (" "*indent, self.descendants) 170 print "%s%s" % (" "*indent, self.best_subtree) 171 for i in self.children: 172 i.Dump(indent=indent+1) 173 174 @classmethod 175 def Get(cls, name, dir=False): 176 if name not in cls.ITEMS: 177 cls.ITEMS[name] = Item(name, dir=dir) 178 return cls.ITEMS[name] 179 180 @classmethod 181 def GetMetadata(cls, input_zip): 182 183 # The target_files contains a record of what the uid, 184 # gid, and mode are supposed to be. 185 output = input_zip.read("META/filesystem_config.txt") 186 187 for line in output.split("\n"): 188 if not line: continue 189 columns = line.split() 190 name, uid, gid, mode = columns[:4] 191 selabel = None 192 capabilities = None 193 194 # After the first 4 columns, there are a series of key=value 195 # pairs. Extract out the fields we care about. 196 for element in columns[4:]: 197 key, value = element.split("=") 198 if key == "selabel": 199 selabel = value 200 if key == "capabilities": 201 capabilities = value 202 203 i = cls.ITEMS.get(name, None) 204 if i is not None: 205 i.uid = int(uid) 206 i.gid = int(gid) 207 i.mode = int(mode, 8) 208 i.selabel = selabel 209 i.capabilities = capabilities 210 if i.dir: 211 i.children.sort(key=lambda i: i.name) 212 213 # set metadata for the files generated by this script. 214 i = cls.ITEMS.get("system/recovery-from-boot.p", None) 215 if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0644, None, None 216 i = cls.ITEMS.get("system/etc/install-recovery.sh", None) 217 if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0544, None, None 218 219 def CountChildMetadata(self): 220 """Count up the (uid, gid, mode, selabel, capabilities) tuples for 221 all children and determine the best strategy for using set_perm_recursive and 222 set_perm to correctly chown/chmod all the files to their desired 223 values. Recursively calls itself for all descendants. 224 225 Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count} counting up 226 all descendants of this node. (dmode or fmode may be None.) Also 227 sets the best_subtree of each directory Item to the (uid, gid, 228 dmode, fmode, selabel, capabilities) tuple that will match the most 229 descendants of that Item. 230 """ 231 232 assert self.dir 233 d = self.descendants = {(self.uid, self.gid, self.mode, None, self.selabel, self.capabilities): 1} 234 for i in self.children: 235 if i.dir: 236 for k, v in i.CountChildMetadata().iteritems(): 237 d[k] = d.get(k, 0) + v 238 else: 239 k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities) 240 d[k] = d.get(k, 0) + 1 241 242 # Find the (uid, gid, dmode, fmode, selabel, capabilities) 243 # tuple that matches the most descendants. 244 245 # First, find the (uid, gid) pair that matches the most 246 # descendants. 247 ug = {} 248 for (uid, gid, _, _, _, _), count in d.iteritems(): 249 ug[(uid, gid)] = ug.get((uid, gid), 0) + count 250 ug = MostPopularKey(ug, (0, 0)) 251 252 # Now find the dmode, fmode, selabel, and capabilities that match 253 # the most descendants with that (uid, gid), and choose those. 254 best_dmode = (0, 0755) 255 best_fmode = (0, 0644) 256 best_selabel = (0, None) 257 best_capabilities = (0, None) 258 for k, count in d.iteritems(): 259 if k[:2] != ug: continue 260 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) 261 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) 262 if k[4] is not None and count >= best_selabel[0]: best_selabel = (count, k[4]) 263 if k[5] is not None and count >= best_capabilities[0]: best_capabilities = (count, k[5]) 264 self.best_subtree = ug + (best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1]) 265 266 return d 267 268 def SetPermissions(self, script): 269 """Append set_perm/set_perm_recursive commands to 'script' to 270 set all permissions, users, and groups for the tree of files 271 rooted at 'self'.""" 272 273 self.CountChildMetadata() 274 275 def recurse(item, current): 276 # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple that the current 277 # item (and all its children) have already been set to. We only 278 # need to issue set_perm/set_perm_recursive commands if we're 279 # supposed to be something different. 280 if item.dir: 281 if current != item.best_subtree: 282 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) 283 current = item.best_subtree 284 285 if item.uid != current[0] or item.gid != current[1] or \ 286 item.mode != current[2] or item.selabel != current[4] or \ 287 item.capabilities != current[5]: 288 script.SetPermissions("/"+item.name, item.uid, item.gid, 289 item.mode, item.selabel, item.capabilities) 290 291 for i in item.children: 292 recurse(i, current) 293 else: 294 if item.uid != current[0] or item.gid != current[1] or \ 295 item.mode != current[3] or item.selabel != current[4] or \ 296 item.capabilities != current[5]: 297 script.SetPermissions("/"+item.name, item.uid, item.gid, 298 item.mode, item.selabel, item.capabilities) 299 300 recurse(self, (-1, -1, -1, -1, None, None)) 301 302 303 def CopySystemFiles(input_zip, output_zip=None, 304 substitute=None): 305 """Copies files underneath system/ in the input zip to the output 306 zip. Populates the Item class with their metadata, and returns a 307 list of symlinks. output_zip may be None, in which case the copy is 308 skipped (but the other side effects still happen). substitute is an 309 optional dict of {output filename: contents} to be output instead of 310 certain input files. 311 """ 312 313 symlinks = [] 314 315 for info in input_zip.infolist(): 316 if info.filename.startswith("SYSTEM/"): 317 basefilename = info.filename[7:] 318 if IsSymlink(info): 319 symlinks.append((input_zip.read(info.filename), 320 "/system/" + basefilename)) 321 else: 322 info2 = copy.copy(info) 323 fn = info2.filename = "system/" + basefilename 324 if substitute and fn in substitute and substitute[fn] is None: 325 continue 326 if output_zip is not None: 327 if substitute and fn in substitute: 328 data = substitute[fn] 329 else: 330 data = input_zip.read(info.filename) 331 output_zip.writestr(info2, data) 332 if fn.endswith("/"): 333 Item.Get(fn[:-1], dir=True) 334 else: 335 Item.Get(fn, dir=False) 336 337 symlinks.sort() 338 return symlinks 339 340 341 def SignOutput(temp_zip_name, output_zip_name): 342 key_passwords = common.GetKeyPasswords([OPTIONS.package_key]) 343 pw = key_passwords[OPTIONS.package_key] 344 345 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw, 346 whole_file=True) 347 348 349 def AppendAssertions(script, info_dict): 350 device = GetBuildProp("ro.product.device", info_dict) 351 script.AssertDevice(device) 352 353 354 def MakeRecoveryPatch(input_tmp, output_zip, recovery_img, boot_img): 355 """Generate a binary patch that creates the recovery image starting 356 with the boot image. (Most of the space in these images is just the 357 kernel, which is identical for the two, so the resulting patch 358 should be efficient.) Add it to the output zip, along with a shell 359 script that is run from init.rc on first boot to actually do the 360 patching and install the new recovery image. 361 362 recovery_img and boot_img should be File objects for the 363 corresponding images. info should be the dictionary returned by 364 common.LoadInfoDict() on the input target_files. 365 366 Returns an Item for the shell script, which must be made 367 executable. 368 """ 369 370 diff_program = ["imgdiff"] 371 path = os.path.join(input_tmp, "SYSTEM", "etc", "recovery-resource.dat") 372 if os.path.exists(path): 373 diff_program.append("-b") 374 diff_program.append(path) 375 bonus_args = "-b /system/etc/recovery-resource.dat" 376 else: 377 bonus_args = "" 378 379 d = common.Difference(recovery_img, boot_img, diff_program=diff_program) 380 _, _, patch = d.ComputePatch() 381 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch) 382 Item.Get("system/recovery-from-boot.p", dir=False) 383 384 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 385 recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict) 386 387 sh = """#!/system/bin/sh 388 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then 389 log -t recovery "Installing new recovery image" 390 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p 391 else 392 log -t recovery "Recovery image already installed" 393 fi 394 """ % { 'boot_size': boot_img.size, 395 'boot_sha1': boot_img.sha1, 396 'recovery_size': recovery_img.size, 397 'recovery_sha1': recovery_img.sha1, 398 'boot_type': boot_type, 399 'boot_device': boot_device, 400 'recovery_type': recovery_type, 401 'recovery_device': recovery_device, 402 'bonus_args': bonus_args, 403 } 404 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) 405 return Item.Get("system/etc/install-recovery.sh", dir=False) 406 407 408 def WriteFullOTAPackage(input_zip, output_zip): 409 # TODO: how to determine this? We don't know what version it will 410 # be installed on top of. For now, we expect the API just won't 411 # change very often. 412 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 413 414 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", 415 OPTIONS.info_dict), 416 "pre-device": GetBuildProp("ro.product.device", 417 OPTIONS.info_dict), 418 "post-timestamp": GetBuildProp("ro.build.date.utc", 419 OPTIONS.info_dict), 420 } 421 422 device_specific = common.DeviceSpecificParams( 423 input_zip=input_zip, 424 input_version=OPTIONS.info_dict["recovery_api_version"], 425 output_zip=output_zip, 426 script=script, 427 input_tmp=OPTIONS.input_tmp, 428 metadata=metadata, 429 info_dict=OPTIONS.info_dict) 430 431 if not OPTIONS.omit_prereq: 432 ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) 433 ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) 434 script.AssertOlderBuild(ts, ts_text) 435 436 AppendAssertions(script, OPTIONS.info_dict) 437 device_specific.FullOTA_Assertions() 438 439 # Two-step package strategy (in chronological order, which is *not* 440 # the order in which the generated script has things): 441 # 442 # if stage is not "2/3" or "3/3": 443 # write recovery image to boot partition 444 # set stage to "2/3" 445 # reboot to boot partition and restart recovery 446 # else if stage is "2/3": 447 # write recovery image to recovery partition 448 # set stage to "3/3" 449 # reboot to recovery partition and restart recovery 450 # else: 451 # (stage must be "3/3") 452 # set stage to "" 453 # do normal full package installation: 454 # wipe and install system, boot image, etc. 455 # set up system to update recovery partition on first boot 456 # complete script normally (allow recovery to mark itself finished and reboot) 457 458 recovery_img = common.GetBootableImage("recovery.img", "recovery.img", 459 OPTIONS.input_tmp, "RECOVERY") 460 if OPTIONS.two_step: 461 if not OPTIONS.info_dict.get("multistage_support", None): 462 assert False, "two-step packages not supported by this build" 463 fs = OPTIONS.info_dict["fstab"]["/misc"] 464 assert fs.fs_type.upper() == "EMMC", \ 465 "two-step packages only supported on devices with EMMC /misc partitions" 466 bcb_dev = {"bcb_dev": fs.device} 467 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data) 468 script.AppendExtra(""" 469 if get_stage("%(bcb_dev)s", "stage") == "2/3" then 470 """ % bcb_dev) 471 script.WriteRawImage("/recovery", "recovery.img") 472 script.AppendExtra(""" 473 set_stage("%(bcb_dev)s", "3/3"); 474 reboot_now("%(bcb_dev)s", "recovery"); 475 else if get_stage("%(bcb_dev)s", "stage") == "3/3" then 476 """ % bcb_dev) 477 478 device_specific.FullOTA_InstallBegin() 479 480 script.ShowProgress(0.5, 0) 481 482 if OPTIONS.wipe_user_data: 483 script.FormatPartition("/data") 484 485 if "selinux_fc" in OPTIONS.info_dict: 486 WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) 487 488 script.FormatPartition("/system") 489 script.Mount("/system") 490 script.UnpackPackageDir("recovery", "/system") 491 script.UnpackPackageDir("system", "/system") 492 493 symlinks = CopySystemFiles(input_zip, output_zip) 494 script.MakeSymlinks(symlinks) 495 496 boot_img = common.GetBootableImage("boot.img", "boot.img", 497 OPTIONS.input_tmp, "BOOT") 498 MakeRecoveryPatch(OPTIONS.input_tmp, output_zip, recovery_img, boot_img) 499 500 Item.GetMetadata(input_zip) 501 Item.Get("system").SetPermissions(script) 502 503 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 504 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 505 script.ShowProgress(0.2, 0) 506 507 script.ShowProgress(0.2, 10) 508 script.WriteRawImage("/boot", "boot.img") 509 510 script.ShowProgress(0.1, 0) 511 device_specific.FullOTA_InstallEnd() 512 513 if OPTIONS.extra_script is not None: 514 script.AppendExtra(OPTIONS.extra_script) 515 516 script.UnmountAll() 517 518 if OPTIONS.two_step: 519 script.AppendExtra(""" 520 set_stage("%(bcb_dev)s", ""); 521 """ % bcb_dev) 522 script.AppendExtra("else\n") 523 script.WriteRawImage("/boot", "recovery.img") 524 script.AppendExtra(""" 525 set_stage("%(bcb_dev)s", "2/3"); 526 reboot_now("%(bcb_dev)s", ""); 527 endif; 528 endif; 529 """ % bcb_dev) 530 script.AddToZip(input_zip, output_zip) 531 WriteMetadata(metadata, output_zip) 532 533 def WritePolicyConfig(file_context, output_zip): 534 f = open(file_context, 'r'); 535 basename = os.path.basename(file_context) 536 common.ZipWriteStr(output_zip, basename, f.read()) 537 538 539 def WriteMetadata(metadata, output_zip): 540 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 541 "".join(["%s=%s\n" % kv 542 for kv in sorted(metadata.iteritems())])) 543 544 def LoadSystemFiles(z): 545 """Load all the files from SYSTEM/... in a given target-files 546 ZipFile, and return a dict of {filename: File object}.""" 547 out = {} 548 for info in z.infolist(): 549 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 550 basefilename = info.filename[7:] 551 fn = "system/" + basefilename 552 data = z.read(info.filename) 553 out[fn] = common.File(fn, data) 554 return out 555 556 557 def GetBuildProp(prop, info_dict): 558 """Return the fingerprint of the build of a given target-files info_dict.""" 559 try: 560 return info_dict.get("build.prop", {})[prop] 561 except KeyError: 562 raise common.ExternalError("couldn't find %s in build.prop" % (property,)) 563 564 def AddToKnownPaths(filename, known_paths): 565 if filename[-1] == "/": 566 return 567 dirs = filename.split("/")[:-1] 568 while len(dirs) > 0: 569 path = "/".join(dirs) 570 if path in known_paths: 571 break; 572 known_paths.add(path) 573 dirs.pop() 574 575 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 576 source_version = OPTIONS.source_info_dict["recovery_api_version"] 577 target_version = OPTIONS.target_info_dict["recovery_api_version"] 578 579 if source_version == 0: 580 print ("WARNING: generating edify script for a source that " 581 "can't install it.") 582 script = edify_generator.EdifyGenerator(source_version, 583 OPTIONS.target_info_dict) 584 585 metadata = {"pre-device": GetBuildProp("ro.product.device", 586 OPTIONS.source_info_dict), 587 "post-timestamp": GetBuildProp("ro.build.date.utc", 588 OPTIONS.target_info_dict), 589 } 590 591 device_specific = common.DeviceSpecificParams( 592 source_zip=source_zip, 593 source_version=source_version, 594 target_zip=target_zip, 595 target_version=target_version, 596 output_zip=output_zip, 597 script=script, 598 metadata=metadata, 599 info_dict=OPTIONS.info_dict) 600 601 print "Loading target..." 602 target_data = LoadSystemFiles(target_zip) 603 print "Loading source..." 604 source_data = LoadSystemFiles(source_zip) 605 606 verbatim_targets = [] 607 patch_list = [] 608 diffs = [] 609 renames = {} 610 known_paths = set() 611 largest_source_size = 0 612 613 matching_file_cache = {} 614 for fn, sf in source_data.items(): 615 assert fn == sf.name 616 matching_file_cache["path:" + fn] = sf 617 if fn in target_data.keys(): 618 AddToKnownPaths(fn, known_paths) 619 # Only allow eligibility for filename/sha matching 620 # if there isn't a perfect path match. 621 if target_data.get(sf.name) is None: 622 matching_file_cache["file:" + fn.split("/")[-1]] = sf 623 matching_file_cache["sha:" + sf.sha1] = sf 624 625 for fn in sorted(target_data.keys()): 626 tf = target_data[fn] 627 assert fn == tf.name 628 sf = ClosestFileMatch(tf, matching_file_cache, renames) 629 if sf is not None and sf.name != tf.name: 630 print "File has moved from " + sf.name + " to " + tf.name 631 renames[sf.name] = tf 632 633 if sf is None or fn in OPTIONS.require_verbatim: 634 # This file should be included verbatim 635 if fn in OPTIONS.prohibit_verbatim: 636 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 637 print "send", fn, "verbatim" 638 tf.AddToZip(output_zip) 639 verbatim_targets.append((fn, tf.size)) 640 if fn in target_data.keys(): 641 AddToKnownPaths(fn, known_paths) 642 elif tf.sha1 != sf.sha1: 643 # File is different; consider sending as a patch 644 diffs.append(common.Difference(tf, sf)) 645 else: 646 # Target file data identical to source (may still be renamed) 647 pass 648 649 common.ComputeDifferences(diffs) 650 651 for diff in diffs: 652 tf, sf, d = diff.GetPatch() 653 path = "/".join(tf.name.split("/")[:-1]) 654 if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \ 655 path not in known_paths: 656 # patch is almost as big as the file; don't bother patching 657 # or a patch + rename cannot take place due to the target 658 # directory not existing 659 tf.AddToZip(output_zip) 660 verbatim_targets.append((tf.name, tf.size)) 661 if sf.name in renames: 662 del renames[sf.name] 663 AddToKnownPaths(tf.name, known_paths) 664 else: 665 common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d) 666 patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest())) 667 largest_source_size = max(largest_source_size, sf.size) 668 669 source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) 670 target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) 671 metadata["pre-build"] = source_fp 672 metadata["post-build"] = target_fp 673 674 script.Mount("/system") 675 script.AssertSomeFingerprint(source_fp, target_fp) 676 677 source_boot = common.GetBootableImage( 678 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", 679 OPTIONS.source_info_dict) 680 target_boot = common.GetBootableImage( 681 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT") 682 updating_boot = (not OPTIONS.two_step and 683 (source_boot.data != target_boot.data)) 684 685 source_recovery = common.GetBootableImage( 686 "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY", 687 OPTIONS.source_info_dict) 688 target_recovery = common.GetBootableImage( 689 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY") 690 updating_recovery = (source_recovery.data != target_recovery.data) 691 692 # Here's how we divide up the progress bar: 693 # 0.1 for verifying the start state (PatchCheck calls) 694 # 0.8 for applying patches (ApplyPatch calls) 695 # 0.1 for unpacking verbatim files, symlinking, and doing the 696 # device-specific commands. 697 698 AppendAssertions(script, OPTIONS.target_info_dict) 699 device_specific.IncrementalOTA_Assertions() 700 701 # Two-step incremental package strategy (in chronological order, 702 # which is *not* the order in which the generated script has 703 # things): 704 # 705 # if stage is not "2/3" or "3/3": 706 # do verification on current system 707 # write recovery image to boot partition 708 # set stage to "2/3" 709 # reboot to boot partition and restart recovery 710 # else if stage is "2/3": 711 # write recovery image to recovery partition 712 # set stage to "3/3" 713 # reboot to recovery partition and restart recovery 714 # else: 715 # (stage must be "3/3") 716 # perform update: 717 # patch system files, etc. 718 # force full install of new boot image 719 # set up system to update recovery partition on first boot 720 # complete script normally (allow recovery to mark itself finished and reboot) 721 722 if OPTIONS.two_step: 723 if not OPTIONS.info_dict.get("multistage_support", None): 724 assert False, "two-step packages not supported by this build" 725 fs = OPTIONS.info_dict["fstab"]["/misc"] 726 assert fs.fs_type.upper() == "EMMC", \ 727 "two-step packages only supported on devices with EMMC /misc partitions" 728 bcb_dev = {"bcb_dev": fs.device} 729 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data) 730 script.AppendExtra(""" 731 if get_stage("%(bcb_dev)s", "stage") == "2/3" then 732 """ % bcb_dev) 733 script.AppendExtra("sleep(20);\n"); 734 script.WriteRawImage("/recovery", "recovery.img") 735 script.AppendExtra(""" 736 set_stage("%(bcb_dev)s", "3/3"); 737 reboot_now("%(bcb_dev)s", "recovery"); 738 else if get_stage("%(bcb_dev)s", "stage") != "3/3" then 739 """ % bcb_dev) 740 741 script.Print("Verifying current system...") 742 743 device_specific.IncrementalOTA_VerifyBegin() 744 745 script.ShowProgress(0.1, 0) 746 total_verify_size = float(sum([i[1].size for i in patch_list]) + 1) 747 if updating_boot: 748 total_verify_size += source_boot.size 749 so_far = 0 750 751 for tf, sf, size, patch_sha in patch_list: 752 if tf.name != sf.name: 753 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 754 script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1) 755 so_far += sf.size 756 script.SetProgress(so_far / total_verify_size) 757 758 if updating_boot: 759 d = common.Difference(target_boot, source_boot) 760 _, _, d = d.ComputePatch() 761 print "boot target: %d source: %d diff: %d" % ( 762 target_boot.size, source_boot.size, len(d)) 763 764 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 765 766 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict) 767 768 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 769 (boot_type, boot_device, 770 source_boot.size, source_boot.sha1, 771 target_boot.size, target_boot.sha1)) 772 so_far += source_boot.size 773 script.SetProgress(so_far / total_verify_size) 774 775 if patch_list or updating_recovery or updating_boot: 776 script.CacheFreeSpaceCheck(largest_source_size) 777 778 device_specific.IncrementalOTA_VerifyEnd() 779 780 if OPTIONS.two_step: 781 script.WriteRawImage("/boot", "recovery.img") 782 script.AppendExtra(""" 783 set_stage("%(bcb_dev)s", "2/3"); 784 reboot_now("%(bcb_dev)s", ""); 785 else 786 """ % bcb_dev) 787 788 script.Comment("---- start making changes here ----") 789 790 device_specific.IncrementalOTA_InstallBegin() 791 792 if OPTIONS.two_step: 793 common.ZipWriteStr(output_zip, "boot.img", target_boot.data) 794 script.WriteRawImage("/boot", "boot.img") 795 print "writing full boot image (forced by two-step mode)" 796 797 if OPTIONS.wipe_user_data: 798 script.Print("Erasing user data...") 799 script.FormatPartition("/data") 800 801 script.Print("Removing unneeded files...") 802 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + 803 ["/"+i for i in sorted(source_data) 804 if i not in target_data and 805 i not in renames] + 806 ["/system/recovery.img"]) 807 808 script.ShowProgress(0.8, 0) 809 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) 810 if updating_boot: 811 total_patch_size += target_boot.size 812 so_far = 0 813 814 script.Print("Patching system files...") 815 deferred_patch_list = [] 816 for item in patch_list: 817 tf, sf, size, _ = item 818 if tf.name == "system/build.prop": 819 deferred_patch_list.append(item) 820 continue 821 if (sf.name != tf.name): 822 script.SkipNextActionIfTargetExists(tf.name, tf.sha1) 823 script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") 824 so_far += tf.size 825 script.SetProgress(so_far / total_patch_size) 826 827 if not OPTIONS.two_step: 828 if updating_boot: 829 # Produce the boot image by applying a patch to the current 830 # contents of the boot partition, and write it back to the 831 # partition. 832 script.Print("Patching boot image...") 833 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 834 % (boot_type, boot_device, 835 source_boot.size, source_boot.sha1, 836 target_boot.size, target_boot.sha1), 837 "-", 838 target_boot.size, target_boot.sha1, 839 source_boot.sha1, "patch/boot.img.p") 840 so_far += target_boot.size 841 script.SetProgress(so_far / total_patch_size) 842 print "boot image changed; including." 843 else: 844 print "boot image unchanged; skipping." 845 846 if updating_recovery: 847 # Recovery is generated as a patch using both the boot image 848 # (which contains the same linux kernel as recovery) and the file 849 # /system/etc/recovery-resource.dat (which contains all the images 850 # used in the recovery UI) as sources. This lets us minimize the 851 # size of the patch, which must be included in every OTA package. 852 # 853 # For older builds where recovery-resource.dat is not present, we 854 # use only the boot image as the source. 855 856 MakeRecoveryPatch(OPTIONS.target_tmp, output_zip, 857 target_recovery, target_boot) 858 script.DeleteFiles(["/system/recovery-from-boot.p", 859 "/system/etc/install-recovery.sh"]) 860 print "recovery image changed; including as patch from boot." 861 else: 862 print "recovery image unchanged; skipping." 863 864 script.ShowProgress(0.1, 10) 865 866 target_symlinks = CopySystemFiles(target_zip, None) 867 868 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks]) 869 temp_script = script.MakeTemporary() 870 Item.GetMetadata(target_zip) 871 Item.Get("system").SetPermissions(temp_script) 872 873 # Note that this call will mess up the tree of Items, so make sure 874 # we're done with it. 875 source_symlinks = CopySystemFiles(source_zip, None) 876 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks]) 877 878 # Delete all the symlinks in source that aren't in target. This 879 # needs to happen before verbatim files are unpacked, in case a 880 # symlink in the source is replaced by a real file in the target. 881 to_delete = [] 882 for dest, link in source_symlinks: 883 if link not in target_symlinks_d: 884 to_delete.append(link) 885 script.DeleteFiles(to_delete) 886 887 if verbatim_targets: 888 script.Print("Unpacking new files...") 889 script.UnpackPackageDir("system", "/system") 890 891 if updating_recovery: 892 script.Print("Unpacking new recovery...") 893 script.UnpackPackageDir("recovery", "/system") 894 895 if len(renames) > 0: 896 script.Print("Renaming files...") 897 898 for src in renames: 899 print "Renaming " + src + " to " + renames[src].name 900 script.RenameFile(src, renames[src].name) 901 902 script.Print("Symlinks and permissions...") 903 904 # Create all the symlinks that don't already exist, or point to 905 # somewhere different than what we want. Delete each symlink before 906 # creating it, since the 'symlink' command won't overwrite. 907 to_create = [] 908 for dest, link in target_symlinks: 909 if link in source_symlinks_d: 910 if dest != source_symlinks_d[link]: 911 to_create.append((dest, link)) 912 else: 913 to_create.append((dest, link)) 914 script.DeleteFiles([i[1] for i in to_create]) 915 script.MakeSymlinks(to_create) 916 917 # Now that the symlinks are created, we can set all the 918 # permissions. 919 script.AppendScript(temp_script) 920 921 # Do device-specific installation (eg, write radio image). 922 device_specific.IncrementalOTA_InstallEnd() 923 924 if OPTIONS.extra_script is not None: 925 script.AppendExtra(OPTIONS.extra_script) 926 927 # Patch the build.prop file last, so if something fails but the 928 # device can still come up, it appears to be the old build and will 929 # get set the OTA package again to retry. 930 script.Print("Patching remaining system files...") 931 for item in deferred_patch_list: 932 tf, sf, size, _ = item 933 script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p") 934 script.SetPermissions("/system/build.prop", 0, 0, 0644, None, None) 935 936 if OPTIONS.two_step: 937 script.AppendExtra(""" 938 set_stage("%(bcb_dev)s", ""); 939 endif; 940 endif; 941 """ % bcb_dev) 942 943 script.AddToZip(target_zip, output_zip) 944 WriteMetadata(metadata, output_zip) 945 946 947 def main(argv): 948 949 def option_handler(o, a): 950 if o in ("-b", "--board_config"): 951 pass # deprecated 952 elif o in ("-k", "--package_key"): 953 OPTIONS.package_key = a 954 elif o in ("-i", "--incremental_from"): 955 OPTIONS.incremental_source = a 956 elif o in ("-w", "--wipe_user_data"): 957 OPTIONS.wipe_user_data = True 958 elif o in ("-n", "--no_prereq"): 959 OPTIONS.omit_prereq = True 960 elif o in ("-e", "--extra_script"): 961 OPTIONS.extra_script = a 962 elif o in ("-a", "--aslr_mode"): 963 if a in ("on", "On", "true", "True", "yes", "Yes"): 964 OPTIONS.aslr_mode = True 965 else: 966 OPTIONS.aslr_mode = False 967 elif o in ("--worker_threads"): 968 OPTIONS.worker_threads = int(a) 969 elif o in ("-2", "--two_step"): 970 OPTIONS.two_step = True 971 else: 972 return False 973 return True 974 975 args = common.ParseOptions(argv, __doc__, 976 extra_opts="b:k:i:d:wne:a:2", 977 extra_long_opts=["board_config=", 978 "package_key=", 979 "incremental_from=", 980 "wipe_user_data", 981 "no_prereq", 982 "extra_script=", 983 "worker_threads=", 984 "aslr_mode=", 985 "two_step", 986 ], 987 extra_option_handler=option_handler) 988 989 if len(args) != 2: 990 common.Usage(__doc__) 991 sys.exit(1) 992 993 if OPTIONS.extra_script is not None: 994 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 995 996 print "unzipping target target-files..." 997 OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) 998 999 OPTIONS.target_tmp = OPTIONS.input_tmp 1000 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 1001 1002 # If this image was originally labelled with SELinux contexts, make sure we 1003 # also apply the labels in our new image. During building, the "file_contexts" 1004 # is in the out/ directory tree, but for repacking from target-files.zip it's 1005 # in the root directory of the ramdisk. 1006 if "selinux_fc" in OPTIONS.info_dict: 1007 OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK", 1008 "file_contexts") 1009 1010 if OPTIONS.verbose: 1011 print "--- target info ---" 1012 common.DumpInfoDict(OPTIONS.info_dict) 1013 1014 if OPTIONS.device_specific is None: 1015 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 1016 if OPTIONS.device_specific is not None: 1017 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 1018 print "using device-specific extensions in", OPTIONS.device_specific 1019 1020 temp_zip_file = tempfile.NamedTemporaryFile() 1021 output_zip = zipfile.ZipFile(temp_zip_file, "w", 1022 compression=zipfile.ZIP_DEFLATED) 1023 1024 if OPTIONS.incremental_source is None: 1025 WriteFullOTAPackage(input_zip, output_zip) 1026 if OPTIONS.package_key is None: 1027 OPTIONS.package_key = OPTIONS.info_dict.get( 1028 "default_system_dev_certificate", 1029 "build/target/product/security/testkey") 1030 else: 1031 print "unzipping source target-files..." 1032 OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source) 1033 OPTIONS.target_info_dict = OPTIONS.info_dict 1034 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 1035 if OPTIONS.package_key is None: 1036 OPTIONS.package_key = OPTIONS.source_info_dict.get( 1037 "default_system_dev_certificate", 1038 "build/target/product/security/testkey") 1039 if OPTIONS.verbose: 1040 print "--- source info ---" 1041 common.DumpInfoDict(OPTIONS.source_info_dict) 1042 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 1043 1044 output_zip.close() 1045 1046 SignOutput(temp_zip_file.name, args[1]) 1047 temp_zip_file.close() 1048 1049 common.Cleanup() 1050 1051 print "done." 1052 1053 1054 if __name__ == '__main__': 1055 try: 1056 common.CloseInheritedPipes() 1057 main(sys.argv[1:]) 1058 except common.ExternalError, e: 1059 print 1060 print " ERROR: %s" % (e,) 1061 print 1062 sys.exit(1) 1063