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