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