ota_from_target_files.py revision f2ab290550d6f1b1ea9bab91eb4c3c77ceeb5df4
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> 28 Key to use to sign the package (default is 29 "build/target/product/security/testkey"). 30 31 -i (--incremental_from) <file> 32 Generate an incremental OTA using the given target-files zip as 33 the starting build. 34 35 -w (--wipe_user_data) 36 Generate an OTA package that will wipe the user data partition 37 when installed. 38 39 -n (--no_prereq) 40 Omit the timestamp prereq check normally included at the top of 41 the build scripts (used for developer OTA packages which 42 legitimately need to go back and forth). 43 44 -e (--extra_script) <file> 45 Insert the contents of file at the end of the update script. 46 47 -a (--aslr_mode) <on|off> 48 Specify whether to turn on ASLR for the package (on by default). 49""" 50 51import sys 52 53if sys.hexversion < 0x02040000: 54 print >> sys.stderr, "Python 2.4 or newer is required." 55 sys.exit(1) 56 57import copy 58import errno 59import os 60import re 61import sha 62import subprocess 63import tempfile 64import time 65import zipfile 66 67import common 68import edify_generator 69 70OPTIONS = common.OPTIONS 71OPTIONS.package_key = "build/target/product/security/testkey" 72OPTIONS.incremental_source = None 73OPTIONS.require_verbatim = set() 74OPTIONS.prohibit_verbatim = set(("system/build.prop",)) 75OPTIONS.patch_threshold = 0.95 76OPTIONS.wipe_user_data = False 77OPTIONS.omit_prereq = False 78OPTIONS.extra_script = None 79OPTIONS.aslr_mode = True 80OPTIONS.worker_threads = 3 81 82# TODO: this is duplicated from edify_generator.py; fix. 83PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD", 84 "ext4": "EMMC", "emmc": "EMMC" } 85 86def MostPopularKey(d, default): 87 """Given a dict, return the key corresponding to the largest 88 value. Returns 'default' if the dict is empty.""" 89 x = [(v, k) for (k, v) in d.iteritems()] 90 if not x: return default 91 x.sort() 92 return x[-1][1] 93 94 95def IsSymlink(info): 96 """Return true if the zipfile.ZipInfo object passed in represents a 97 symlink.""" 98 return (info.external_attr >> 16) == 0120777 99 100def IsRegular(info): 101 """Return true if the zipfile.ZipInfo object passed in represents a 102 symlink.""" 103 return (info.external_attr >> 28) == 010 104 105def GetTypeAndDevice(mount_point, info): 106 fstab = info["fstab"] 107 if fstab: 108 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device 109 else: 110 devices = {"/boot": "boot", 111 "/recovery": "recovery", 112 "/radio": "radio", 113 "/data": "userdata", 114 "/cache": "cache"} 115 return info["partition_type"], info.get("partition_path", "") + devices[mount_point] 116 117 118class Item: 119 """Items represent the metadata (user, group, mode) of files and 120 directories in the system image.""" 121 ITEMS = {} 122 def __init__(self, name, dir=False): 123 self.name = name 124 self.uid = None 125 self.gid = None 126 self.mode = None 127 self.dir = dir 128 129 if name: 130 self.parent = Item.Get(os.path.dirname(name), dir=True) 131 self.parent.children.append(self) 132 else: 133 self.parent = None 134 if dir: 135 self.children = [] 136 137 def Dump(self, indent=0): 138 if self.uid is not None: 139 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode) 140 else: 141 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode) 142 if self.dir: 143 print "%s%s" % (" "*indent, self.descendants) 144 print "%s%s" % (" "*indent, self.best_subtree) 145 for i in self.children: 146 i.Dump(indent=indent+1) 147 148 @classmethod 149 def Get(cls, name, dir=False): 150 if name not in cls.ITEMS: 151 cls.ITEMS[name] = Item(name, dir=dir) 152 return cls.ITEMS[name] 153 154 @classmethod 155 def GetMetadata(cls, input_zip): 156 157 try: 158 # See if the target_files contains a record of what the uid, 159 # gid, and mode is supposed to be. 160 output = input_zip.read("META/filesystem_config.txt") 161 except KeyError: 162 # Run the external 'fs_config' program to determine the desired 163 # uid, gid, and mode for every Item object. Note this uses the 164 # one in the client now, which might not be the same as the one 165 # used when this target_files was built. 166 p = common.Run(["fs_config"], stdin=subprocess.PIPE, 167 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 168 suffix = { False: "", True: "/" } 169 input = "".join(["%s%s\n" % (i.name, suffix[i.dir]) 170 for i in cls.ITEMS.itervalues() if i.name]) 171 output, error = p.communicate(input) 172 assert not error 173 174 for line in output.split("\n"): 175 if not line: continue 176 name, uid, gid, mode = line.split() 177 i = cls.ITEMS.get(name, None) 178 if i is not None: 179 i.uid = int(uid) 180 i.gid = int(gid) 181 i.mode = int(mode, 8) 182 if i.dir: 183 i.children.sort(key=lambda i: i.name) 184 185 # set metadata for the files generated by this script. 186 i = cls.ITEMS.get("system/recovery-from-boot.p", None) 187 if i: i.uid, i.gid, i.mode = 0, 0, 0644 188 i = cls.ITEMS.get("system/etc/install-recovery.sh", None) 189 if i: i.uid, i.gid, i.mode = 0, 0, 0544 190 191 def CountChildMetadata(self): 192 """Count up the (uid, gid, mode) tuples for all children and 193 determine the best strategy for using set_perm_recursive and 194 set_perm to correctly chown/chmod all the files to their desired 195 values. Recursively calls itself for all descendants. 196 197 Returns a dict of {(uid, gid, dmode, fmode): count} counting up 198 all descendants of this node. (dmode or fmode may be None.) Also 199 sets the best_subtree of each directory Item to the (uid, gid, 200 dmode, fmode) tuple that will match the most descendants of that 201 Item. 202 """ 203 204 assert self.dir 205 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1} 206 for i in self.children: 207 if i.dir: 208 for k, v in i.CountChildMetadata().iteritems(): 209 d[k] = d.get(k, 0) + v 210 else: 211 k = (i.uid, i.gid, None, i.mode) 212 d[k] = d.get(k, 0) + 1 213 214 # Find the (uid, gid, dmode, fmode) tuple that matches the most 215 # descendants. 216 217 # First, find the (uid, gid) pair that matches the most 218 # descendants. 219 ug = {} 220 for (uid, gid, _, _), count in d.iteritems(): 221 ug[(uid, gid)] = ug.get((uid, gid), 0) + count 222 ug = MostPopularKey(ug, (0, 0)) 223 224 # Now find the dmode and fmode that match the most descendants 225 # with that (uid, gid), and choose those. 226 best_dmode = (0, 0755) 227 best_fmode = (0, 0644) 228 for k, count in d.iteritems(): 229 if k[:2] != ug: continue 230 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2]) 231 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3]) 232 self.best_subtree = ug + (best_dmode[1], best_fmode[1]) 233 234 return d 235 236 def SetPermissions(self, script): 237 """Append set_perm/set_perm_recursive commands to 'script' to 238 set all permissions, users, and groups for the tree of files 239 rooted at 'self'.""" 240 241 self.CountChildMetadata() 242 243 def recurse(item, current): 244 # current is the (uid, gid, dmode, fmode) tuple that the current 245 # item (and all its children) have already been set to. We only 246 # need to issue set_perm/set_perm_recursive commands if we're 247 # supposed to be something different. 248 if item.dir: 249 if current != item.best_subtree: 250 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree) 251 current = item.best_subtree 252 253 if item.uid != current[0] or item.gid != current[1] or \ 254 item.mode != current[2]: 255 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) 256 257 for i in item.children: 258 recurse(i, current) 259 else: 260 if item.uid != current[0] or item.gid != current[1] or \ 261 item.mode != current[3]: 262 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode) 263 264 recurse(self, (-1, -1, -1, -1)) 265 266 267def CopySystemFiles(input_zip, output_zip=None, 268 substitute=None): 269 """Copies files underneath system/ in the input zip to the output 270 zip. Populates the Item class with their metadata, and returns a 271 list of symlinks as well as a list of files that will be retouched. 272 output_zip may be None, in which case the copy is skipped (but the 273 other side effects still happen). substitute is an optional dict 274 of {output filename: contents} to be output instead of certain input 275 files. 276 """ 277 278 symlinks = [] 279 retouch_files = [] 280 281 for info in input_zip.infolist(): 282 if info.filename.startswith("SYSTEM/"): 283 basefilename = info.filename[7:] 284 if IsSymlink(info): 285 symlinks.append((input_zip.read(info.filename), 286 "/system/" + basefilename)) 287 else: 288 info2 = copy.copy(info) 289 fn = info2.filename = "system/" + basefilename 290 if substitute and fn in substitute and substitute[fn] is None: 291 continue 292 if output_zip is not None: 293 if substitute and fn in substitute: 294 data = substitute[fn] 295 else: 296 data = input_zip.read(info.filename) 297 if info.filename.startswith("SYSTEM/lib/") and IsRegular(info): 298 retouch_files.append(("/system/" + basefilename, 299 sha.sha(data).hexdigest())) 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, retouch_files) 308 309 310def 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 318def AppendAssertions(script, input_zip): 319 device = GetBuildProp("ro.product.device", input_zip) 320 script.AssertDevice(device) 321 322 323def MakeRecoveryPatch(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 d = common.Difference(recovery_img, boot_img) 340 _, _, patch = d.ComputePatch() 341 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch) 342 Item.Get("system/recovery-from-boot.p", dir=False) 343 344 boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict) 345 recovery_type, recovery_device = GetTypeAndDevice("/recovery", OPTIONS.info_dict) 346 347 # Images with different content will have a different first page, so 348 # we check to see if this recovery has already been installed by 349 # testing just the first 2k. 350 HEADER_SIZE = 2048 351 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest() 352 sh = """#!/system/bin/sh 353if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then 354 log -t recovery "Installing new recovery image" 355 applypatch %(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 356else 357 log -t recovery "Recovery image already installed" 358fi 359""" % { 'boot_size': boot_img.size, 360 'boot_sha1': boot_img.sha1, 361 'header_size': HEADER_SIZE, 362 'header_sha1': header_sha1, 363 'recovery_size': recovery_img.size, 364 'recovery_sha1': recovery_img.sha1, 365 'boot_type': boot_type, 366 'boot_device': boot_device, 367 'recovery_type': recovery_type, 368 'recovery_device': recovery_device, 369 } 370 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh) 371 return Item.Get("system/etc/install-recovery.sh", dir=False) 372 373 374def WriteFullOTAPackage(input_zip, output_zip): 375 # TODO: how to determine this? We don't know what version it will 376 # be installed on top of. For now, we expect the API just won't 377 # change very often. 378 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict) 379 380 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip), 381 "pre-device": GetBuildProp("ro.product.device", input_zip), 382 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip), 383 } 384 385 device_specific = common.DeviceSpecificParams( 386 input_zip=input_zip, 387 input_version=OPTIONS.info_dict["recovery_api_version"], 388 output_zip=output_zip, 389 script=script, 390 input_tmp=OPTIONS.input_tmp, 391 metadata=metadata) 392 393 if not OPTIONS.omit_prereq: 394 ts = GetBuildProp("ro.build.date.utc", input_zip) 395 script.AssertOlderBuild(ts) 396 397 AppendAssertions(script, input_zip) 398 device_specific.FullOTA_Assertions() 399 400 script.ShowProgress(0.5, 0) 401 402 if OPTIONS.wipe_user_data: 403 script.FormatPartition("/data") 404 405 script.FormatPartition("/system") 406 script.Mount("/system") 407 script.UnpackPackageDir("recovery", "/system") 408 script.UnpackPackageDir("system", "/system") 409 410 (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip) 411 script.MakeSymlinks(symlinks) 412 if OPTIONS.aslr_mode: 413 script.RetouchBinaries(retouch_files) 414 else: 415 script.UndoRetouchBinaries(retouch_files) 416 417 boot_img = common.File("boot.img", common.BuildBootableImage( 418 os.path.join(OPTIONS.input_tmp, "BOOT"))) 419 recovery_img = common.File("recovery.img", common.BuildBootableImage( 420 os.path.join(OPTIONS.input_tmp, "RECOVERY"))) 421 MakeRecoveryPatch(output_zip, recovery_img, boot_img) 422 423 Item.GetMetadata(input_zip) 424 Item.Get("system").SetPermissions(script) 425 426 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) 427 common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 428 script.ShowProgress(0.2, 0) 429 430 script.ShowProgress(0.2, 10) 431 script.WriteRawImage("/boot", "boot.img") 432 433 script.ShowProgress(0.1, 0) 434 device_specific.FullOTA_InstallEnd() 435 436 if OPTIONS.extra_script is not None: 437 script.AppendExtra(OPTIONS.extra_script) 438 439 script.UnmountAll() 440 script.AddToZip(input_zip, output_zip) 441 WriteMetadata(metadata, output_zip) 442 443 444def WriteMetadata(metadata, output_zip): 445 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", 446 "".join(["%s=%s\n" % kv 447 for kv in sorted(metadata.iteritems())])) 448 449 450 451 452def LoadSystemFiles(z): 453 """Load all the files from SYSTEM/... in a given target-files 454 ZipFile, and return a dict of {filename: File object}.""" 455 out = {} 456 retouch_files = [] 457 for info in z.infolist(): 458 if info.filename.startswith("SYSTEM/") and not IsSymlink(info): 459 basefilename = info.filename[7:] 460 fn = "system/" + basefilename 461 data = z.read(info.filename) 462 out[fn] = common.File(fn, data) 463 if info.filename.startswith("SYSTEM/lib/") and IsRegular(info): 464 retouch_files.append(("/system/" + basefilename, 465 out[fn].sha1)) 466 return (out, retouch_files) 467 468 469def GetBuildProp(property, z): 470 """Return the fingerprint of the build of a given target-files 471 ZipFile object.""" 472 bp = z.read("SYSTEM/build.prop") 473 if not property: 474 return bp 475 m = re.search(re.escape(property) + r"=(.*)\n", bp) 476 if not m: 477 raise common.ExternalError("couldn't find %s in build.prop" % (property,)) 478 return m.group(1).strip() 479 480 481def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): 482 source_version = OPTIONS.source_info_dict["recovery_api_version"] 483 target_version = OPTIONS.target_info_dict["recovery_api_version"] 484 485 if source_version == 0: 486 print ("WARNING: generating edify script for a source that " 487 "can't install it.") 488 script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict) 489 490 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip), 491 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip), 492 } 493 494 device_specific = common.DeviceSpecificParams( 495 source_zip=source_zip, 496 source_version=source_version, 497 target_zip=target_zip, 498 target_version=target_version, 499 output_zip=output_zip, 500 script=script, 501 metadata=metadata) 502 503 print "Loading target..." 504 (target_data, target_retouch_files) = LoadSystemFiles(target_zip) 505 print "Loading source..." 506 (source_data, source_retouch_files) = LoadSystemFiles(source_zip) 507 508 verbatim_targets = [] 509 patch_list = [] 510 diffs = [] 511 largest_source_size = 0 512 for fn in sorted(target_data.keys()): 513 tf = target_data[fn] 514 assert fn == tf.name 515 sf = source_data.get(fn, None) 516 517 if sf is None or fn in OPTIONS.require_verbatim: 518 # This file should be included verbatim 519 if fn in OPTIONS.prohibit_verbatim: 520 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,)) 521 print "send", fn, "verbatim" 522 tf.AddToZip(output_zip) 523 verbatim_targets.append((fn, tf.size)) 524 elif tf.sha1 != sf.sha1: 525 # File is different; consider sending as a patch 526 diffs.append(common.Difference(tf, sf)) 527 else: 528 # Target file identical to source. 529 pass 530 531 common.ComputeDifferences(diffs) 532 533 for diff in diffs: 534 tf, sf, d = diff.GetPatch() 535 if d is None or len(d) > tf.size * OPTIONS.patch_threshold: 536 # patch is almost as big as the file; don't bother patching 537 tf.AddToZip(output_zip) 538 verbatim_targets.append((tf.name, tf.size)) 539 else: 540 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d) 541 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest())) 542 largest_source_size = max(largest_source_size, sf.size) 543 544 source_fp = GetBuildProp("ro.build.fingerprint", source_zip) 545 target_fp = GetBuildProp("ro.build.fingerprint", target_zip) 546 metadata["pre-build"] = source_fp 547 metadata["post-build"] = target_fp 548 549 script.Mount("/system") 550 script.AssertSomeFingerprint(source_fp, target_fp) 551 552 source_boot = common.File("/tmp/boot.img", 553 common.BuildBootableImage( 554 os.path.join(OPTIONS.source_tmp, "BOOT"))) 555 target_boot = common.File("/tmp/boot.img", 556 common.BuildBootableImage( 557 os.path.join(OPTIONS.target_tmp, "BOOT"))) 558 updating_boot = (source_boot.data != target_boot.data) 559 560 source_recovery = common.File("system/recovery.img", 561 common.BuildBootableImage( 562 os.path.join(OPTIONS.source_tmp, "RECOVERY"))) 563 target_recovery = common.File("system/recovery.img", 564 common.BuildBootableImage( 565 os.path.join(OPTIONS.target_tmp, "RECOVERY"))) 566 updating_recovery = (source_recovery.data != target_recovery.data) 567 568 # Here's how we divide up the progress bar: 569 # 0.1 for verifying the start state (PatchCheck calls) 570 # 0.8 for applying patches (ApplyPatch calls) 571 # 0.1 for unpacking verbatim files, symlinking, and doing the 572 # device-specific commands. 573 574 AppendAssertions(script, target_zip) 575 device_specific.IncrementalOTA_Assertions() 576 577 script.Print("Verifying current system...") 578 579 script.ShowProgress(0.1, 0) 580 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1) 581 if updating_boot: 582 total_verify_size += source_boot.size 583 so_far = 0 584 585 for fn, tf, sf, size, patch_sha in patch_list: 586 script.PatchCheck("/"+fn, tf.sha1, sf.sha1) 587 so_far += sf.size 588 script.SetProgress(so_far / total_verify_size) 589 590 if updating_boot: 591 d = common.Difference(target_boot, source_boot) 592 _, _, d = d.ComputePatch() 593 print "boot target: %d source: %d diff: %d" % ( 594 target_boot.size, source_boot.size, len(d)) 595 596 common.ZipWriteStr(output_zip, "patch/boot.img.p", d) 597 598 boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict) 599 600 script.PatchCheck("%s:%s:%d:%s:%d:%s" % 601 (boot_type, boot_device, 602 source_boot.size, source_boot.sha1, 603 target_boot.size, target_boot.sha1)) 604 so_far += source_boot.size 605 script.SetProgress(so_far / total_verify_size) 606 607 if patch_list or updating_recovery or updating_boot: 608 script.CacheFreeSpaceCheck(largest_source_size) 609 610 device_specific.IncrementalOTA_VerifyEnd() 611 612 script.Comment("---- start making changes here ----") 613 614 if OPTIONS.wipe_user_data: 615 script.Print("Erasing user data...") 616 script.FormatPartition("/data") 617 618 script.Print("Removing unneeded files...") 619 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] + 620 ["/"+i for i in sorted(source_data) 621 if i not in target_data] + 622 ["/system/recovery.img"]) 623 624 script.ShowProgress(0.8, 0) 625 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1) 626 if updating_boot: 627 total_patch_size += target_boot.size 628 so_far = 0 629 630 script.Print("Patching system files...") 631 for fn, tf, sf, size, _ in patch_list: 632 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p") 633 so_far += tf.size 634 script.SetProgress(so_far / total_patch_size) 635 636 if updating_boot: 637 # Produce the boot image by applying a patch to the current 638 # contents of the boot partition, and write it back to the 639 # partition. 640 script.Print("Patching boot image...") 641 script.ApplyPatch("%s:%s:%d:%s:%d:%s" 642 % (boot_type, boot_device, 643 source_boot.size, source_boot.sha1, 644 target_boot.size, target_boot.sha1), 645 "-", 646 target_boot.size, target_boot.sha1, 647 source_boot.sha1, "patch/boot.img.p") 648 so_far += target_boot.size 649 script.SetProgress(so_far / total_patch_size) 650 print "boot image changed; including." 651 else: 652 print "boot image unchanged; skipping." 653 654 if updating_recovery: 655 # Is it better to generate recovery as a patch from the current 656 # boot image, or from the previous recovery image? For large 657 # updates with significant kernel changes, probably the former. 658 # For small updates where the kernel hasn't changed, almost 659 # certainly the latter. We pick the first option. Future 660 # complicated schemes may let us effectively use both. 661 # 662 # A wacky possibility: as long as there is room in the boot 663 # partition, include the binaries and image files from recovery in 664 # the boot image (though not in the ramdisk) so they can be used 665 # as fodder for constructing the recovery image. 666 MakeRecoveryPatch(output_zip, 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, target_retouch_dummies) = 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, source_retouch_dummies) = 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 if OPTIONS.aslr_mode: 719 script.RetouchBinaries(target_retouch_files) 720 else: 721 script.UndoRetouchBinaries(target_retouch_files) 722 723 # Now that the symlinks are created, we can set all the 724 # permissions. 725 script.AppendScript(temp_script) 726 727 # Do device-specific installation (eg, write radio image). 728 device_specific.IncrementalOTA_InstallEnd() 729 730 if OPTIONS.extra_script is not None: 731 script.AppendExtra(OPTIONS.extra_script) 732 733 script.AddToZip(target_zip, output_zip) 734 WriteMetadata(metadata, output_zip) 735 736 737def main(argv): 738 739 def option_handler(o, a): 740 if o in ("-b", "--board_config"): 741 pass # deprecated 742 elif o in ("-k", "--package_key"): 743 OPTIONS.package_key = a 744 elif o in ("-i", "--incremental_from"): 745 OPTIONS.incremental_source = a 746 elif o in ("-w", "--wipe_user_data"): 747 OPTIONS.wipe_user_data = True 748 elif o in ("-n", "--no_prereq"): 749 OPTIONS.omit_prereq = True 750 elif o in ("-e", "--extra_script"): 751 OPTIONS.extra_script = a 752 elif o in ("-a", "--aslr_mode"): 753 if a in ("on", "On", "true", "True", "yes", "Yes"): 754 OPTIONS.aslr_mode = True 755 else: 756 OPTIONS.aslr_mode = False 757 elif o in ("--worker_threads"): 758 OPTIONS.worker_threads = int(a) 759 else: 760 return False 761 return True 762 763 args = common.ParseOptions(argv, __doc__, 764 extra_opts="b:k:i:d:wne:a:", 765 extra_long_opts=["board_config=", 766 "package_key=", 767 "incremental_from=", 768 "wipe_user_data", 769 "no_prereq", 770 "extra_script=", 771 "worker_threads=", 772 "aslr_mode=", 773 ], 774 extra_option_handler=option_handler) 775 776 if len(args) != 2: 777 common.Usage(__doc__) 778 sys.exit(1) 779 780 if OPTIONS.extra_script is not None: 781 OPTIONS.extra_script = open(OPTIONS.extra_script).read() 782 783 print "unzipping target target-files..." 784 OPTIONS.input_tmp = common.UnzipTemp(args[0]) 785 786 OPTIONS.target_tmp = OPTIONS.input_tmp 787 input_zip = zipfile.ZipFile(args[0], "r") 788 OPTIONS.info_dict = common.LoadInfoDict(input_zip) 789 if OPTIONS.verbose: 790 print "--- target info ---" 791 common.DumpInfoDict(OPTIONS.info_dict) 792 793 if OPTIONS.device_specific is None: 794 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None) 795 if OPTIONS.device_specific is not None: 796 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific) 797 print "using device-specific extensions in", OPTIONS.device_specific 798 799 if OPTIONS.package_key: 800 temp_zip_file = tempfile.NamedTemporaryFile() 801 output_zip = zipfile.ZipFile(temp_zip_file, "w", 802 compression=zipfile.ZIP_DEFLATED) 803 else: 804 output_zip = zipfile.ZipFile(args[1], "w", 805 compression=zipfile.ZIP_DEFLATED) 806 807 if OPTIONS.incremental_source is None: 808 WriteFullOTAPackage(input_zip, output_zip) 809 else: 810 print "unzipping source target-files..." 811 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source) 812 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r") 813 OPTIONS.target_info_dict = OPTIONS.info_dict 814 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip) 815 if OPTIONS.verbose: 816 print "--- source info ---" 817 common.DumpInfoDict(OPTIONS.source_info_dict) 818 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 819 820 output_zip.close() 821 if OPTIONS.package_key: 822 SignOutput(temp_zip_file.name, args[1]) 823 temp_zip_file.close() 824 825 common.Cleanup() 826 827 print "done." 828 829 830if __name__ == '__main__': 831 try: 832 main(sys.argv[1:]) 833 except common.ExternalError, e: 834 print 835 print " ERROR: %s" % (e,) 836 print 837 sys.exit(1) 838