3 # Copyright (C) 2008 The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
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.
22 Usage: ota_from_target_files [flags] input_target_files output_ota_package
24 -b (--board_config) <file>
27 -k (--package_key) <key>
28 Key to use to sign the package (default is
29 "build/target/product/security/testkey").
31 -i (--incremental_from) <file>
32 Generate an incremental OTA using the given target-files zip as
36 Generate an OTA package that will wipe the user data partition
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).
44 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
47 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
55 if sys.hexversion < 0x02040000:
56 print >> sys.stderr, "Python 2.4 or newer is required."
71 import amend_generator
72 import edify_generator
75 OPTIONS = common.OPTIONS
76 OPTIONS.package_key = "build/target/product/security/testkey"
77 OPTIONS.incremental_source = None
78 OPTIONS.require_verbatim = set()
79 OPTIONS.prohibit_verbatim = set(("system/build.prop",))
80 OPTIONS.patch_threshold = 0.95
81 OPTIONS.wipe_user_data = False
82 OPTIONS.omit_prereq = False
83 OPTIONS.extra_script = None
84 OPTIONS.script_mode = 'auto'
85 OPTIONS.worker_threads = 3
87 def MostPopularKey(d, default):
88 """Given a dict, return the key corresponding to the largest
89 value. Returns 'default' if the dict is empty."""
90 x = [(v, k) for (k, v) in d.iteritems()]
91 if not x: return default
97 """Return true if the zipfile.ZipInfo object passed in represents a
99 return (info.external_attr >> 16) == 0120777
104 """Items represent the metadata (user, group, mode) of files and
105 directories in the system image."""
107 def __init__(self, name, dir=False):
115 self.parent = Item.Get(os.path.dirname(name), dir=True)
116 self.parent.children.append(self)
122 def Dump(self, indent=0):
123 if self.uid is not None:
124 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
126 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
128 print "%s%s" % (" "*indent, self.descendants)
129 print "%s%s" % (" "*indent, self.best_subtree)
130 for i in self.children:
131 i.Dump(indent=indent+1)
134 def Get(cls, name, dir=False):
135 if name not in cls.ITEMS:
136 cls.ITEMS[name] = Item(name, dir=dir)
137 return cls.ITEMS[name]
140 def GetMetadata(cls, input_zip):
143 # See if the target_files contains a record of what the uid,
144 # gid, and mode is supposed to be.
145 output = input_zip.read("META/filesystem_config.txt")
147 # Run the external 'fs_config' program to determine the desired
148 # uid, gid, and mode for every Item object. Note this uses the
149 # one in the client now, which might not be the same as the one
150 # used when this target_files was built.
151 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
152 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 suffix = { False: "", True: "/" }
154 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
155 for i in cls.ITEMS.itervalues() if i.name])
156 output, error = p.communicate(input)
159 for line in output.split("\n"):
160 if not line: continue
161 name, uid, gid, mode = line.split()
162 i = cls.ITEMS.get(name, None)
166 i.mode = int(mode, 8)
168 i.children.sort(key=lambda i: i.name)
170 # set metadata for the files generated by this script.
171 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
172 if i: i.uid, i.gid, i.mode = 0, 0, 0644
173 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
174 if i: i.uid, i.gid, i.mode = 0, 0, 0544
176 def CountChildMetadata(self):
177 """Count up the (uid, gid, mode) tuples for all children and
178 determine the best strategy for using set_perm_recursive and
179 set_perm to correctly chown/chmod all the files to their desired
180 values. Recursively calls itself for all descendants.
182 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
183 all descendants of this node. (dmode or fmode may be None.) Also
184 sets the best_subtree of each directory Item to the (uid, gid,
185 dmode, fmode) tuple that will match the most descendants of that
190 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
191 for i in self.children:
193 for k, v in i.CountChildMetadata().iteritems():
194 d[k] = d.get(k, 0) + v
196 k = (i.uid, i.gid, None, i.mode)
197 d[k] = d.get(k, 0) + 1
199 # Find the (uid, gid, dmode, fmode) tuple that matches the most
202 # First, find the (uid, gid) pair that matches the most
205 for (uid, gid, _, _), count in d.iteritems():
206 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
207 ug = MostPopularKey(ug, (0, 0))
209 # Now find the dmode and fmode that match the most descendants
210 # with that (uid, gid), and choose those.
211 best_dmode = (0, 0755)
212 best_fmode = (0, 0644)
213 for k, count in d.iteritems():
214 if k[:2] != ug: continue
215 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
216 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
217 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
221 def SetPermissions(self, script):
222 """Append set_perm/set_perm_recursive commands to 'script' to
223 set all permissions, users, and groups for the tree of files
226 self.CountChildMetadata()
228 def recurse(item, current):
229 # current is the (uid, gid, dmode, fmode) tuple that the current
230 # item (and all its children) have already been set to. We only
231 # need to issue set_perm/set_perm_recursive commands if we're
232 # supposed to be something different.
234 if current != item.best_subtree:
235 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
236 current = item.best_subtree
238 if item.uid != current[0] or item.gid != current[1] or \
239 item.mode != current[2]:
240 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
242 for i in item.children:
245 if item.uid != current[0] or item.gid != current[1] or \
246 item.mode != current[3]:
247 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
249 recurse(self, (-1, -1, -1, -1))
252 def CopySystemFiles(input_zip, output_zip=None,
254 """Copies files underneath system/ in the input zip to the output
255 zip. Populates the Item class with their metadata, and returns a
256 list of symlinks. output_zip may be None, in which case the copy is
257 skipped (but the other side effects still happen). substitute is an
258 optional dict of {output filename: contents} to be output instead of
264 for info in input_zip.infolist():
265 if info.filename.startswith("SYSTEM/"):
266 basefilename = info.filename[7:]
268 symlinks.append((input_zip.read(info.filename),
269 "/system/" + basefilename))
271 info2 = copy.copy(info)
272 fn = info2.filename = "system/" + basefilename
273 if substitute and fn in substitute and substitute[fn] is None:
275 if output_zip is not None:
276 if substitute and fn in substitute:
277 data = substitute[fn]
279 data = input_zip.read(info.filename)
280 output_zip.writestr(info2, data)
282 Item.Get(fn[:-1], dir=True)
284 Item.Get(fn, dir=False)
290 def SignOutput(temp_zip_name, output_zip_name):
291 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
292 pw = key_passwords[OPTIONS.package_key]
294 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
298 def AppendAssertions(script, input_zip):
299 device = GetBuildProp("ro.product.device", input_zip)
300 script.AssertDevice(device)
303 def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
304 """Generate a binary patch that creates the recovery image starting
305 with the boot image. (Most of the space in these images is just the
306 kernel, which is identical for the two, so the resulting patch
307 should be efficient.) Add it to the output zip, along with a shell
308 script that is run from init.rc on first boot to actually do the
309 patching and install the new recovery image.
311 recovery_img and boot_img should be File objects for the
312 corresponding images.
314 Returns an Item for the shell script, which must be made
318 d = Difference(recovery_img, boot_img)
319 _, _, patch = d.ComputePatch()
320 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
321 Item.Get("system/recovery-from-boot.p", dir=False)
323 # Images with different content will have a different first page, so
324 # we check to see if this recovery has already been installed by
325 # testing just the first 2k.
327 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
328 sh = """#!/system/bin/sh
329 if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
330 log -t recovery "Installing new recovery image"
331 applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
333 log -t recovery "Recovery image already installed"
335 """ % { 'boot_size': boot_img.size,
336 'boot_sha1': boot_img.sha1,
337 'header_size': HEADER_SIZE,
338 'header_sha1': header_sha1,
339 'recovery_size': recovery_img.size,
340 'recovery_sha1': recovery_img.sha1 }
341 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
342 return Item.Get("system/etc/install-recovery.sh", dir=False)
345 def WriteFullOTAPackage(input_zip, output_zip):
346 if OPTIONS.script_mode == "auto":
347 script = both_generator.BothGenerator(2)
348 elif OPTIONS.script_mode == "amend":
349 script = amend_generator.AmendGenerator()
351 # TODO: how to determine this? We don't know what version it will
352 # be installed on top of. For now, we expect the API just won't
354 script = edify_generator.EdifyGenerator(2)
356 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
357 "pre-device": GetBuildProp("ro.product.device", input_zip),
360 device_specific = common.DeviceSpecificParams(
362 input_version=GetRecoveryAPIVersion(input_zip),
363 output_zip=output_zip,
365 input_tmp=OPTIONS.input_tmp,
368 if not OPTIONS.omit_prereq:
369 ts = GetBuildProp("ro.build.date.utc", input_zip)
370 script.AssertOlderBuild(ts)
372 AppendAssertions(script, input_zip)
373 device_specific.FullOTA_Assertions()
375 script.ShowProgress(0.5, 0)
377 if OPTIONS.wipe_user_data:
378 script.FormatPartition("userdata")
380 script.FormatPartition("system")
381 script.Mount("MTD", "system", "/system")
382 script.UnpackPackageDir("recovery", "/system")
383 script.UnpackPackageDir("system", "/system")
385 symlinks = CopySystemFiles(input_zip, output_zip)
386 script.MakeSymlinks(symlinks)
388 boot_img = File("boot.img", common.BuildBootableImage(
389 os.path.join(OPTIONS.input_tmp, "BOOT")))
390 recovery_img = File("recovery.img", common.BuildBootableImage(
391 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
392 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
394 Item.GetMetadata(input_zip)
395 Item.Get("system").SetPermissions(script)
397 common.CheckSize(boot_img.data, "boot.img")
398 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
399 script.ShowProgress(0.2, 0)
401 script.ShowProgress(0.2, 10)
402 script.WriteRawImage("boot", "boot.img")
404 script.ShowProgress(0.1, 0)
405 device_specific.FullOTA_InstallEnd()
407 if OPTIONS.extra_script is not None:
408 script.AppendExtra(OPTIONS.extra_script)
411 script.AddToZip(input_zip, output_zip)
412 WriteMetadata(metadata, output_zip)
415 def WriteMetadata(metadata, output_zip):
416 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
417 "".join(["%s=%s\n" % kv
418 for kv in sorted(metadata.iteritems())]))
422 def __init__(self, name, data):
425 self.size = len(data)
426 self.sha1 = sha.sha(data).hexdigest()
428 def WriteToTemp(self):
429 t = tempfile.NamedTemporaryFile()
434 def AddToZip(self, z):
435 common.ZipWriteStr(z, self.name, self.data)
438 def LoadSystemFiles(z):
439 """Load all the files from SYSTEM/... in a given target-files
440 ZipFile, and return a dict of {filename: File object}."""
442 for info in z.infolist():
443 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
444 fn = "system/" + info.filename[7:]
445 data = z.read(info.filename)
446 out[fn] = File(fn, data)
450 DIFF_PROGRAM_BY_EXT = {
452 ".zip" : ["imgdiff", "-z"],
453 ".jar" : ["imgdiff", "-z"],
454 ".apk" : ["imgdiff", "-z"],
459 class Difference(object):
460 def __init__(self, tf, sf):
465 def ComputePatch(self):
466 """Compute the patch (as a string of data) needed to turn sf into
467 tf. Returns the same tuple as GetPatch()."""
472 ext = os.path.splitext(tf.name)[1]
473 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
475 ttemp = tf.WriteToTemp()
476 stemp = sf.WriteToTemp()
478 ext = os.path.splitext(tf.name)[1]
481 ptemp = tempfile.NamedTemporaryFile()
482 if isinstance(diff_program, list):
483 cmd = copy.copy(diff_program)
486 cmd.append(stemp.name)
487 cmd.append(ttemp.name)
488 cmd.append(ptemp.name)
489 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
490 _, err = p.communicate()
491 if err or p.returncode != 0:
492 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
501 return self.tf, self.sf, self.patch
505 """Return a tuple (target_file, source_file, patch_data).
506 patch_data may be None if ComputePatch hasn't been called, or if
507 computing the patch failed."""
508 return self.tf, self.sf, self.patch
511 def ComputeDifferences(diffs):
512 """Call ComputePatch on all the Difference objects in 'diffs'."""
513 print len(diffs), "diffs to compute"
515 # Do the largest files first, to try and reduce the long-pole effect.
516 by_size = [(i.tf.size, i) for i in diffs]
517 by_size.sort(reverse=True)
518 by_size = [i[1] for i in by_size]
520 lock = threading.Lock()
521 diff_iter = iter(by_size) # accessed under lock
530 dur = time.time() - start
533 tf, sf, patch = d.GetPatch()
534 if sf.name == tf.name:
537 name = "%s (%s)" % (tf.name, sf.name)
539 print "patching failed! %s" % (name,)
541 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
542 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
548 # start worker threads; wait for them all to finish.
549 threads = [threading.Thread(target=worker)
550 for i in range(OPTIONS.worker_threads)]
557 def GetBuildProp(property, z):
558 """Return the fingerprint of the build of a given target-files
560 bp = z.read("SYSTEM/build.prop")
563 m = re.search(re.escape(property) + r"=(.*)\n", bp)
565 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
566 return m.group(1).strip()
569 def GetRecoveryAPIVersion(zip):
570 """Returns the version of the recovery API. Version 0 is the older
571 amend code (no separate binary)."""
573 version = zip.read("META/recovery-api-version.txt")
577 # version one didn't have the recovery-api-version.txt file, but
578 # it did include an updater binary.
579 zip.getinfo("OTA/bin/updater")
585 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
586 source_version = GetRecoveryAPIVersion(source_zip)
587 target_version = GetRecoveryAPIVersion(target_zip)
589 if OPTIONS.script_mode == 'amend':
590 script = amend_generator.AmendGenerator()
591 elif OPTIONS.script_mode == 'edify':
592 if source_version == 0:
593 print ("WARNING: generating edify script for a source that "
595 script = edify_generator.EdifyGenerator(source_version)
596 elif OPTIONS.script_mode == 'auto':
597 if source_version > 0:
598 script = edify_generator.EdifyGenerator(source_version)
600 script = amend_generator.AmendGenerator()
602 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
604 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
607 device_specific = common.DeviceSpecificParams(
608 source_zip=source_zip,
609 source_version=source_version,
610 target_zip=target_zip,
611 target_version=target_version,
612 output_zip=output_zip,
616 print "Loading target..."
617 target_data = LoadSystemFiles(target_zip)
618 print "Loading source..."
619 source_data = LoadSystemFiles(source_zip)
621 verbatim_targets = []
624 largest_source_size = 0
625 for fn in sorted(target_data.keys()):
628 sf = source_data.get(fn, None)
630 if sf is None or fn in OPTIONS.require_verbatim:
631 # This file should be included verbatim
632 if fn in OPTIONS.prohibit_verbatim:
633 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
634 print "send", fn, "verbatim"
635 tf.AddToZip(output_zip)
636 verbatim_targets.append((fn, tf.size))
637 elif tf.sha1 != sf.sha1:
638 # File is different; consider sending as a patch
639 diffs.append(Difference(tf, sf))
641 # Target file identical to source.
644 ComputeDifferences(diffs)
647 tf, sf, d = diff.GetPatch()
648 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
649 # patch is almost as big as the file; don't bother patching
650 tf.AddToZip(output_zip)
651 verbatim_targets.append((tf.name, tf.size))
653 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
654 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
655 largest_source_size = max(largest_source_size, sf.size)
657 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
658 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
659 metadata["pre-build"] = source_fp
660 metadata["post-build"] = target_fp
662 script.Mount("MTD", "system", "/system")
663 script.AssertSomeFingerprint(source_fp, target_fp)
665 source_boot = File("/tmp/boot.img",
666 common.BuildBootableImage(
667 os.path.join(OPTIONS.source_tmp, "BOOT")))
668 target_boot = File("/tmp/boot.img",
669 common.BuildBootableImage(
670 os.path.join(OPTIONS.target_tmp, "BOOT")))
671 updating_boot = (source_boot.data != target_boot.data)
673 source_recovery = File("system/recovery.img",
674 common.BuildBootableImage(
675 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
676 target_recovery = File("system/recovery.img",
677 common.BuildBootableImage(
678 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
679 updating_recovery = (source_recovery.data != target_recovery.data)
681 # Here's how we divide up the progress bar:
682 # 0.1 for verifying the start state (PatchCheck calls)
683 # 0.8 for applying patches (ApplyPatch calls)
684 # 0.1 for unpacking verbatim files, symlinking, and doing the
685 # device-specific commands.
687 AppendAssertions(script, target_zip)
688 device_specific.IncrementalOTA_Assertions()
690 script.Print("Verifying current system...")
692 script.ShowProgress(0.1, 0)
693 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
695 total_verify_size += source_boot.size
698 for fn, tf, sf, size, patch_sha in patch_list:
699 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
701 script.SetProgress(so_far / total_verify_size)
704 d = Difference(target_boot, source_boot)
705 _, _, d = d.ComputePatch()
706 print "boot target: %d source: %d diff: %d" % (
707 target_boot.size, source_boot.size, len(d))
709 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
711 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
712 (source_boot.size, source_boot.sha1,
713 target_boot.size, target_boot.sha1))
714 so_far += source_boot.size
715 script.SetProgress(so_far / total_verify_size)
717 if patch_list or updating_recovery or updating_boot:
718 script.CacheFreeSpaceCheck(largest_source_size)
720 device_specific.IncrementalOTA_VerifyEnd()
722 script.Comment("---- start making changes here ----")
724 if OPTIONS.wipe_user_data:
725 script.Print("Erasing user data...")
726 script.FormatPartition("userdata")
728 script.Print("Removing unneeded files...")
729 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
730 ["/"+i for i in sorted(source_data)
731 if i not in target_data] +
732 ["/system/recovery.img"])
734 script.ShowProgress(0.8, 0)
735 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
737 total_patch_size += target_boot.size
740 script.Print("Patching system files...")
741 for fn, tf, sf, size, _ in patch_list:
742 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
744 script.SetProgress(so_far / total_patch_size)
747 # Produce the boot image by applying a patch to the current
748 # contents of the boot partition, and write it back to the
750 script.Print("Patching boot image...")
751 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
752 % (source_boot.size, source_boot.sha1,
753 target_boot.size, target_boot.sha1),
755 target_boot.size, target_boot.sha1,
756 source_boot.sha1, "patch/boot.img.p")
757 so_far += target_boot.size
758 script.SetProgress(so_far / total_patch_size)
759 print "boot image changed; including."
761 print "boot image unchanged; skipping."
763 if updating_recovery:
764 # Is it better to generate recovery as a patch from the current
765 # boot image, or from the previous recovery image? For large
766 # updates with significant kernel changes, probably the former.
767 # For small updates where the kernel hasn't changed, almost
768 # certainly the latter. We pick the first option. Future
769 # complicated schemes may let us effectively use both.
771 # A wacky possibility: as long as there is room in the boot
772 # partition, include the binaries and image files from recovery in
773 # the boot image (though not in the ramdisk) so they can be used
774 # as fodder for constructing the recovery image.
775 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
776 script.DeleteFiles(["/system/recovery-from-boot.p",
777 "/system/etc/install-recovery.sh"])
778 print "recovery image changed; including as patch from boot."
780 print "recovery image unchanged; skipping."
782 script.ShowProgress(0.1, 10)
784 target_symlinks = CopySystemFiles(target_zip, None)
786 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
787 temp_script = script.MakeTemporary()
788 Item.GetMetadata(target_zip)
789 Item.Get("system").SetPermissions(temp_script)
791 # Note that this call will mess up the tree of Items, so make sure
792 # we're done with it.
793 source_symlinks = CopySystemFiles(source_zip, None)
794 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
796 # Delete all the symlinks in source that aren't in target. This
797 # needs to happen before verbatim files are unpacked, in case a
798 # symlink in the source is replaced by a real file in the target.
800 for dest, link in source_symlinks:
801 if link not in target_symlinks_d:
802 to_delete.append(link)
803 script.DeleteFiles(to_delete)
806 script.Print("Unpacking new files...")
807 script.UnpackPackageDir("system", "/system")
809 if updating_recovery:
810 script.Print("Unpacking new recovery...")
811 script.UnpackPackageDir("recovery", "/system")
813 script.Print("Symlinks and permissions...")
815 # Create all the symlinks that don't already exist, or point to
816 # somewhere different than what we want. Delete each symlink before
817 # creating it, since the 'symlink' command won't overwrite.
819 for dest, link in target_symlinks:
820 if link in source_symlinks_d:
821 if dest != source_symlinks_d[link]:
822 to_create.append((dest, link))
824 to_create.append((dest, link))
825 script.DeleteFiles([i[1] for i in to_create])
826 script.MakeSymlinks(to_create)
828 # Now that the symlinks are created, we can set all the
830 script.AppendScript(temp_script)
832 # Do device-specific installation (eg, write radio image).
833 device_specific.IncrementalOTA_InstallEnd()
835 if OPTIONS.extra_script is not None:
836 scirpt.AppendExtra(OPTIONS.extra_script)
838 script.AddToZip(target_zip, output_zip)
839 WriteMetadata(metadata, output_zip)
844 def option_handler(o, a):
845 if o in ("-b", "--board_config"):
847 elif o in ("-k", "--package_key"):
848 OPTIONS.package_key = a
849 elif o in ("-i", "--incremental_from"):
850 OPTIONS.incremental_source = a
851 elif o in ("-w", "--wipe_user_data"):
852 OPTIONS.wipe_user_data = True
853 elif o in ("-n", "--no_prereq"):
854 OPTIONS.omit_prereq = True
855 elif o in ("-e", "--extra_script"):
856 OPTIONS.extra_script = a
857 elif o in ("-m", "--script_mode"):
858 OPTIONS.script_mode = a
859 elif o in ("--worker_threads"):
860 OPTIONS.worker_threads = int(a)
865 args = common.ParseOptions(argv, __doc__,
866 extra_opts="b:k:i:d:wne:m:",
867 extra_long_opts=["board_config=",
875 extra_option_handler=option_handler)
878 common.Usage(__doc__)
881 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
882 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
884 if OPTIONS.extra_script is not None:
885 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
887 print "unzipping target target-files..."
888 OPTIONS.input_tmp = common.UnzipTemp(args[0])
890 if OPTIONS.device_specific is None:
891 # look for the device-specific tools extension location in the input
893 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
894 ds = f.read().strip()
897 ds = os.path.normpath(ds)
898 print "using device-specific extensions in", ds
899 OPTIONS.device_specific = ds
901 if e.errno == errno.ENOENT:
902 # nothing specified in the file
907 common.LoadMaxSizes()
908 if not OPTIONS.max_image_size:
910 print " WARNING: Failed to load max image sizes; will not enforce"
911 print " image size limits."
914 OPTIONS.target_tmp = OPTIONS.input_tmp
915 input_zip = zipfile.ZipFile(args[0], "r")
916 if OPTIONS.package_key:
917 temp_zip_file = tempfile.NamedTemporaryFile()
918 output_zip = zipfile.ZipFile(temp_zip_file, "w",
919 compression=zipfile.ZIP_DEFLATED)
921 output_zip = zipfile.ZipFile(args[1], "w",
922 compression=zipfile.ZIP_DEFLATED)
924 if OPTIONS.incremental_source is None:
925 WriteFullOTAPackage(input_zip, output_zip)
927 print "unzipping source target-files..."
928 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
929 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
930 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
933 if OPTIONS.package_key:
934 SignOutput(temp_zip_file.name, args[1])
935 temp_zip_file.close()
942 if __name__ == '__main__':
945 except common.ExternalError, e:
947 print " ERROR: %s" % (e,)