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