OSDN Git Service

am 1a0f6725: (-s ours) Reconcile with jb-release - do not merge
[android-x86/build.git] / tools / releasetools / ota_from_target_files
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.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
260 def 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
298 def 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
306 def AppendAssertions(script, input_zip):
307   device = GetBuildProp("ro.product.device", input_zip)
308   script.AssertDevice(device)
309
310
311 def MakeRecoveryPatch(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   d = common.Difference(recovery_img, boot_img)
328   _, _, patch = d.ComputePatch()
329   common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
330   Item.Get("system/recovery-from-boot.p", dir=False)
331
332   boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
333   recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
334
335   sh = """#!/system/bin/sh
336 if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
337   log -t recovery "Installing new recovery image"
338   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
339 else
340   log -t recovery "Recovery image already installed"
341 fi
342 """ % { 'boot_size': boot_img.size,
343         'boot_sha1': boot_img.sha1,
344         'recovery_size': recovery_img.size,
345         'recovery_sha1': recovery_img.sha1,
346         'boot_type': boot_type,
347         'boot_device': boot_device,
348         'recovery_type': recovery_type,
349         'recovery_device': recovery_device,
350         }
351   common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
352   return Item.Get("system/etc/install-recovery.sh", dir=False)
353
354
355 def WriteFullOTAPackage(input_zip, output_zip):
356   # TODO: how to determine this?  We don't know what version it will
357   # be installed on top of.  For now, we expect the API just won't
358   # change very often.
359   script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
360
361   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
362               "pre-device": GetBuildProp("ro.product.device", input_zip),
363               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
364               }
365
366   device_specific = common.DeviceSpecificParams(
367       input_zip=input_zip,
368       input_version=OPTIONS.info_dict["recovery_api_version"],
369       output_zip=output_zip,
370       script=script,
371       input_tmp=OPTIONS.input_tmp,
372       metadata=metadata,
373       info_dict=OPTIONS.info_dict)
374
375   if not OPTIONS.omit_prereq:
376     ts = GetBuildProp("ro.build.date.utc", input_zip)
377     script.AssertOlderBuild(ts)
378
379   AppendAssertions(script, input_zip)
380   device_specific.FullOTA_Assertions()
381   device_specific.FullOTA_InstallBegin()
382
383   script.ShowProgress(0.5, 0)
384
385   if OPTIONS.wipe_user_data:
386     script.FormatPartition("/data")
387
388   if "selinux_fc" in OPTIONS.info_dict:
389     WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
390
391   script.FormatPartition("/system")
392   script.Mount("/system")
393   script.UnpackPackageDir("recovery", "/system")
394   script.UnpackPackageDir("system", "/system")
395
396   symlinks = CopySystemFiles(input_zip, output_zip)
397   script.MakeSymlinks(symlinks)
398
399   boot_img = common.GetBootableImage("boot.img", "boot.img",
400                                      OPTIONS.input_tmp, "BOOT")
401   recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
402                                          OPTIONS.input_tmp, "RECOVERY")
403   MakeRecoveryPatch(output_zip, recovery_img, boot_img)
404
405   Item.GetMetadata(input_zip)
406   Item.Get("system").SetPermissions(script)
407
408   common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
409   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
410   script.ShowProgress(0.2, 0)
411
412   script.ShowProgress(0.2, 10)
413   script.WriteRawImage("/boot", "boot.img")
414
415   script.ShowProgress(0.1, 0)
416   device_specific.FullOTA_InstallEnd()
417
418   if OPTIONS.extra_script is not None:
419     script.AppendExtra(OPTIONS.extra_script)
420
421   script.UnmountAll()
422   script.AddToZip(input_zip, output_zip)
423   WriteMetadata(metadata, output_zip)
424
425 def WritePolicyConfig(file_context, output_zip):
426   f = open(file_context, 'r');
427   basename = os.path.basename(file_context)
428   common.ZipWriteStr(output_zip, basename, f.read())
429
430
431 def WriteMetadata(metadata, output_zip):
432   common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
433                      "".join(["%s=%s\n" % kv
434                               for kv in sorted(metadata.iteritems())]))
435
436 def LoadSystemFiles(z):
437   """Load all the files from SYSTEM/... in a given target-files
438   ZipFile, and return a dict of {filename: File object}."""
439   out = {}
440   for info in z.infolist():
441     if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
442       basefilename = info.filename[7:]
443       fn = "system/" + basefilename
444       data = z.read(info.filename)
445       out[fn] = common.File(fn, data)
446   return out
447
448
449 def GetBuildProp(property, z):
450   """Return the fingerprint of the build of a given target-files
451   ZipFile object."""
452   bp = z.read("SYSTEM/build.prop")
453   if not property:
454     return bp
455   m = re.search(re.escape(property) + r"=(.*)\n", bp)
456   if not m:
457     raise common.ExternalError("couldn't find %s in build.prop" % (property,))
458   return m.group(1).strip()
459
460
461 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
462   source_version = OPTIONS.source_info_dict["recovery_api_version"]
463   target_version = OPTIONS.target_info_dict["recovery_api_version"]
464
465   if source_version == 0:
466     print ("WARNING: generating edify script for a source that "
467            "can't install it.")
468   script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict)
469
470   metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
471               "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
472               }
473
474   device_specific = common.DeviceSpecificParams(
475       source_zip=source_zip,
476       source_version=source_version,
477       target_zip=target_zip,
478       target_version=target_version,
479       output_zip=output_zip,
480       script=script,
481       metadata=metadata,
482       info_dict=OPTIONS.info_dict)
483
484   print "Loading target..."
485   target_data = LoadSystemFiles(target_zip)
486   print "Loading source..."
487   source_data = LoadSystemFiles(source_zip)
488
489   verbatim_targets = []
490   patch_list = []
491   diffs = []
492   largest_source_size = 0
493   for fn in sorted(target_data.keys()):
494     tf = target_data[fn]
495     assert fn == tf.name
496     sf = source_data.get(fn, None)
497
498     if sf is None or fn in OPTIONS.require_verbatim:
499       # This file should be included verbatim
500       if fn in OPTIONS.prohibit_verbatim:
501         raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
502       print "send", fn, "verbatim"
503       tf.AddToZip(output_zip)
504       verbatim_targets.append((fn, tf.size))
505     elif tf.sha1 != sf.sha1:
506       # File is different; consider sending as a patch
507       diffs.append(common.Difference(tf, sf))
508     else:
509       # Target file identical to source.
510       pass
511
512   common.ComputeDifferences(diffs)
513
514   for diff in diffs:
515     tf, sf, d = diff.GetPatch()
516     if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
517       # patch is almost as big as the file; don't bother patching
518       tf.AddToZip(output_zip)
519       verbatim_targets.append((tf.name, tf.size))
520     else:
521       common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
522       patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest()))
523       largest_source_size = max(largest_source_size, sf.size)
524
525   source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
526   target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
527   metadata["pre-build"] = source_fp
528   metadata["post-build"] = target_fp
529
530   script.Mount("/system")
531   script.AssertSomeFingerprint(source_fp, target_fp)
532
533   source_boot = common.GetBootableImage(
534       "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
535       OPTIONS.source_info_dict)
536   target_boot = common.GetBootableImage(
537       "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
538   updating_boot = (source_boot.data != target_boot.data)
539
540   source_recovery = common.GetBootableImage(
541       "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
542       OPTIONS.source_info_dict)
543   target_recovery = common.GetBootableImage(
544       "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
545   updating_recovery = (source_recovery.data != target_recovery.data)
546
547   # Here's how we divide up the progress bar:
548   #  0.1 for verifying the start state (PatchCheck calls)
549   #  0.8 for applying patches (ApplyPatch calls)
550   #  0.1 for unpacking verbatim files, symlinking, and doing the
551   #      device-specific commands.
552
553   AppendAssertions(script, target_zip)
554   device_specific.IncrementalOTA_Assertions()
555
556   script.Print("Verifying current system...")
557
558   device_specific.IncrementalOTA_VerifyBegin()
559
560   script.ShowProgress(0.1, 0)
561   total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
562   if updating_boot:
563     total_verify_size += source_boot.size
564   so_far = 0
565
566   for fn, tf, sf, size, patch_sha in patch_list:
567     script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
568     so_far += sf.size
569     script.SetProgress(so_far / total_verify_size)
570
571   if updating_boot:
572     d = common.Difference(target_boot, source_boot)
573     _, _, d = d.ComputePatch()
574     print "boot      target: %d  source: %d  diff: %d" % (
575         target_boot.size, source_boot.size, len(d))
576
577     common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
578
579     boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
580
581     script.PatchCheck("%s:%s:%d:%s:%d:%s" %
582                       (boot_type, boot_device,
583                        source_boot.size, source_boot.sha1,
584                        target_boot.size, target_boot.sha1))
585     so_far += source_boot.size
586     script.SetProgress(so_far / total_verify_size)
587
588   if patch_list or updating_recovery or updating_boot:
589     script.CacheFreeSpaceCheck(largest_source_size)
590
591   device_specific.IncrementalOTA_VerifyEnd()
592
593   script.Comment("---- start making changes here ----")
594
595   device_specific.IncrementalOTA_InstallBegin()
596
597   if OPTIONS.wipe_user_data:
598     script.Print("Erasing user data...")
599     script.FormatPartition("/data")
600
601   script.Print("Removing unneeded files...")
602   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
603                      ["/"+i for i in sorted(source_data)
604                             if i not in target_data] +
605                      ["/system/recovery.img"])
606
607   script.ShowProgress(0.8, 0)
608   total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
609   if updating_boot:
610     total_patch_size += target_boot.size
611   so_far = 0
612
613   script.Print("Patching system files...")
614   deferred_patch_list = []
615   for item in patch_list:
616     fn, tf, sf, size, _ = item
617     if tf.name == "system/build.prop":
618       deferred_patch_list.append(item)
619       continue
620     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
621     so_far += tf.size
622     script.SetProgress(so_far / total_patch_size)
623
624   if updating_boot:
625     # Produce the boot image by applying a patch to the current
626     # contents of the boot partition, and write it back to the
627     # partition.
628     script.Print("Patching boot image...")
629     script.ApplyPatch("%s:%s:%d:%s:%d:%s"
630                       % (boot_type, boot_device,
631                          source_boot.size, source_boot.sha1,
632                          target_boot.size, target_boot.sha1),
633                       "-",
634                       target_boot.size, target_boot.sha1,
635                       source_boot.sha1, "patch/boot.img.p")
636     so_far += target_boot.size
637     script.SetProgress(so_far / total_patch_size)
638     print "boot image changed; including."
639   else:
640     print "boot image unchanged; skipping."
641
642   if updating_recovery:
643     # Is it better to generate recovery as a patch from the current
644     # boot image, or from the previous recovery image?  For large
645     # updates with significant kernel changes, probably the former.
646     # For small updates where the kernel hasn't changed, almost
647     # certainly the latter.  We pick the first option.  Future
648     # complicated schemes may let us effectively use both.
649     #
650     # A wacky possibility: as long as there is room in the boot
651     # partition, include the binaries and image files from recovery in
652     # the boot image (though not in the ramdisk) so they can be used
653     # as fodder for constructing the recovery image.
654     MakeRecoveryPatch(output_zip, target_recovery, target_boot)
655     script.DeleteFiles(["/system/recovery-from-boot.p",
656                         "/system/etc/install-recovery.sh"])
657     print "recovery image changed; including as patch from boot."
658   else:
659     print "recovery image unchanged; skipping."
660
661   script.ShowProgress(0.1, 10)
662
663   target_symlinks = CopySystemFiles(target_zip, None)
664
665   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
666   temp_script = script.MakeTemporary()
667   Item.GetMetadata(target_zip)
668   Item.Get("system").SetPermissions(temp_script)
669
670   # Note that this call will mess up the tree of Items, so make sure
671   # we're done with it.
672   source_symlinks = CopySystemFiles(source_zip, None)
673   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
674
675   # Delete all the symlinks in source that aren't in target.  This
676   # needs to happen before verbatim files are unpacked, in case a
677   # symlink in the source is replaced by a real file in the target.
678   to_delete = []
679   for dest, link in source_symlinks:
680     if link not in target_symlinks_d:
681       to_delete.append(link)
682   script.DeleteFiles(to_delete)
683
684   if verbatim_targets:
685     script.Print("Unpacking new files...")
686     script.UnpackPackageDir("system", "/system")
687
688   if updating_recovery:
689     script.Print("Unpacking new recovery...")
690     script.UnpackPackageDir("recovery", "/system")
691
692   script.Print("Symlinks and permissions...")
693
694   # Create all the symlinks that don't already exist, or point to
695   # somewhere different than what we want.  Delete each symlink before
696   # creating it, since the 'symlink' command won't overwrite.
697   to_create = []
698   for dest, link in target_symlinks:
699     if link in source_symlinks_d:
700       if dest != source_symlinks_d[link]:
701         to_create.append((dest, link))
702     else:
703       to_create.append((dest, link))
704   script.DeleteFiles([i[1] for i in to_create])
705   script.MakeSymlinks(to_create)
706
707   # Now that the symlinks are created, we can set all the
708   # permissions.
709   script.AppendScript(temp_script)
710
711   # Do device-specific installation (eg, write radio image).
712   device_specific.IncrementalOTA_InstallEnd()
713
714   if OPTIONS.extra_script is not None:
715     script.AppendExtra(OPTIONS.extra_script)
716
717   # Patch the build.prop file last, so if something fails but the
718   # device can still come up, it appears to be the old build and will
719   # get set the OTA package again to retry.
720   script.Print("Patching remaining system files...")
721   for item in deferred_patch_list:
722     fn, tf, sf, size, _ = item
723     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
724   script.SetPermissions("/system/build.prop", 0, 0, 0644)
725
726   script.AddToZip(target_zip, output_zip)
727   WriteMetadata(metadata, output_zip)
728
729
730 def main(argv):
731
732   def option_handler(o, a):
733     if o in ("-b", "--board_config"):
734       pass   # deprecated
735     elif o in ("-k", "--package_key"):
736       OPTIONS.package_key = a
737     elif o in ("-i", "--incremental_from"):
738       OPTIONS.incremental_source = a
739     elif o in ("-w", "--wipe_user_data"):
740       OPTIONS.wipe_user_data = True
741     elif o in ("-n", "--no_prereq"):
742       OPTIONS.omit_prereq = True
743     elif o in ("-e", "--extra_script"):
744       OPTIONS.extra_script = a
745     elif o in ("-a", "--aslr_mode"):
746       if a in ("on", "On", "true", "True", "yes", "Yes"):
747         OPTIONS.aslr_mode = True
748       else:
749         OPTIONS.aslr_mode = False
750     elif o in ("--worker_threads"):
751       OPTIONS.worker_threads = int(a)
752     else:
753       return False
754     return True
755
756   args = common.ParseOptions(argv, __doc__,
757                              extra_opts="b:k:i:d:wne:a:",
758                              extra_long_opts=["board_config=",
759                                               "package_key=",
760                                               "incremental_from=",
761                                               "wipe_user_data",
762                                               "no_prereq",
763                                               "extra_script=",
764                                               "worker_threads=",
765                                               "aslr_mode=",
766                                               ],
767                              extra_option_handler=option_handler)
768
769   if len(args) != 2:
770     common.Usage(__doc__)
771     sys.exit(1)
772
773   if OPTIONS.extra_script is not None:
774     OPTIONS.extra_script = open(OPTIONS.extra_script).read()
775
776   print "unzipping target target-files..."
777   OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
778
779   OPTIONS.target_tmp = OPTIONS.input_tmp
780   OPTIONS.info_dict = common.LoadInfoDict(input_zip)
781   if OPTIONS.verbose:
782     print "--- target info ---"
783     common.DumpInfoDict(OPTIONS.info_dict)
784
785   if OPTIONS.device_specific is None:
786     OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
787   if OPTIONS.device_specific is not None:
788     OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
789     print "using device-specific extensions in", OPTIONS.device_specific
790
791   temp_zip_file = tempfile.NamedTemporaryFile()
792   output_zip = zipfile.ZipFile(temp_zip_file, "w",
793                                compression=zipfile.ZIP_DEFLATED)
794
795   if OPTIONS.incremental_source is None:
796     WriteFullOTAPackage(input_zip, output_zip)
797     if OPTIONS.package_key is None:
798       OPTIONS.package_key = OPTIONS.info_dict.get(
799           "default_system_dev_certificate",
800           "build/target/product/security/testkey")
801   else:
802     print "unzipping source target-files..."
803     OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
804     OPTIONS.target_info_dict = OPTIONS.info_dict
805     OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
806     if OPTIONS.package_key is None:
807       OPTIONS.package_key = OPTIONS.source_info_dict.get(
808           "default_system_dev_certificate",
809           "build/target/product/security/testkey")
810     if OPTIONS.verbose:
811       print "--- source info ---"
812       common.DumpInfoDict(OPTIONS.source_info_dict)
813     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
814
815   output_zip.close()
816
817   SignOutput(temp_zip_file.name, args[1])
818   temp_zip_file.close()
819
820   common.Cleanup()
821
822   print "done."
823
824
825 if __name__ == '__main__':
826   try:
827     common.CloseInheritedPipes()
828     main(sys.argv[1:])
829   except common.ExternalError, e:
830     print
831     print "   ERROR: %s" % (e,)
832     print
833     sys.exit(1)