OSDN Git Service

Merge commit 'remotes/goog/donut' into donut-release
[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       Specifies a BoardConfig.mk file containing image max sizes
26       against which the generated image files are checked.
27
28   -k  (--package_key)  <key>
29       Key to use to sign the package (default is
30       "build/target/product/security/testkey").
31
32   -i  (--incremental_from)  <file>
33       Generate an incremental OTA using the given target-files zip as
34       the starting build.
35
36   -w  (--wipe_user_data)
37       Generate an OTA package that will wipe the user data partition
38       when installed.
39
40   -n  (--no_prereq)
41       Omit the timestamp prereq check normally included at the top of
42       the build scripts (used for developer OTA packages which
43       legitimately need to go back and forth).
44
45   -e  (--extra_script)  <file>
46       Insert the contents of file at the end of the update script.
47
48   -m  (--script_mode)  <mode>
49       Specify 'amend' or 'edify' scripts, or 'auto' to pick
50       automatically (this is the default).
51
52 """
53
54 import sys
55
56 if sys.hexversion < 0x02040000:
57   print >> sys.stderr, "Python 2.4 or newer is required."
58   sys.exit(1)
59
60 import copy
61 import os
62 import re
63 import sha
64 import subprocess
65 import tempfile
66 import time
67 import zipfile
68
69 import common
70 import amend_generator
71 import edify_generator
72 import both_generator
73
74 OPTIONS = common.OPTIONS
75 OPTIONS.package_key = "build/target/product/security/testkey"
76 OPTIONS.incremental_source = None
77 OPTIONS.require_verbatim = set()
78 OPTIONS.prohibit_verbatim = set(("system/build.prop",))
79 OPTIONS.patch_threshold = 0.95
80 OPTIONS.wipe_user_data = False
81 OPTIONS.omit_prereq = False
82 OPTIONS.extra_script = None
83 OPTIONS.script_mode = 'auto'
84
85 def MostPopularKey(d, default):
86   """Given a dict, return the key corresponding to the largest
87   value.  Returns 'default' if the dict is empty."""
88   x = [(v, k) for (k, v) in d.iteritems()]
89   if not x: return default
90   x.sort()
91   return x[-1][1]
92
93
94 def IsSymlink(info):
95   """Return true if the zipfile.ZipInfo object passed in represents a
96   symlink."""
97   return (info.external_attr >> 16) == 0120777
98
99
100
101 class Item:
102   """Items represent the metadata (user, group, mode) of files and
103   directories in the system image."""
104   ITEMS = {}
105   def __init__(self, name, dir=False):
106     self.name = name
107     self.uid = None
108     self.gid = None
109     self.mode = None
110     self.dir = dir
111
112     if name:
113       self.parent = Item.Get(os.path.dirname(name), dir=True)
114       self.parent.children.append(self)
115     else:
116       self.parent = None
117     if dir:
118       self.children = []
119
120   def Dump(self, indent=0):
121     if self.uid is not None:
122       print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
123     else:
124       print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
125     if self.dir:
126       print "%s%s" % ("  "*indent, self.descendants)
127       print "%s%s" % ("  "*indent, self.best_subtree)
128       for i in self.children:
129         i.Dump(indent=indent+1)
130
131   @classmethod
132   def Get(cls, name, dir=False):
133     if name not in cls.ITEMS:
134       cls.ITEMS[name] = Item(name, dir=dir)
135     return cls.ITEMS[name]
136
137   @classmethod
138   def GetMetadata(cls):
139     """Run the external 'fs_config' program to determine the desired
140     uid, gid, and mode for every Item object."""
141     p = common.Run(["fs_config"], stdin=subprocess.PIPE,
142                   stdout=subprocess.PIPE, stderr=subprocess.PIPE)
143     suffix = { False: "", True: "/" }
144     input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
145                      for i in cls.ITEMS.itervalues() if i.name])
146     output, error = p.communicate(input)
147     assert not error
148
149     for line in output.split("\n"):
150       if not line: continue
151       name, uid, gid, mode = line.split()
152       i = cls.ITEMS[name]
153       i.uid = int(uid)
154       i.gid = int(gid)
155       i.mode = int(mode, 8)
156       if i.dir:
157         i.children.sort(key=lambda i: i.name)
158
159   def CountChildMetadata(self):
160     """Count up the (uid, gid, mode) tuples for all children and
161     determine the best strategy for using set_perm_recursive and
162     set_perm to correctly chown/chmod all the files to their desired
163     values.  Recursively calls itself for all descendants.
164
165     Returns a dict of {(uid, gid, dmode, fmode): count} counting up
166     all descendants of this node.  (dmode or fmode may be None.)  Also
167     sets the best_subtree of each directory Item to the (uid, gid,
168     dmode, fmode) tuple that will match the most descendants of that
169     Item.
170     """
171
172     assert self.dir
173     d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
174     for i in self.children:
175       if i.dir:
176         for k, v in i.CountChildMetadata().iteritems():
177           d[k] = d.get(k, 0) + v
178       else:
179         k = (i.uid, i.gid, None, i.mode)
180         d[k] = d.get(k, 0) + 1
181
182     # Find the (uid, gid, dmode, fmode) tuple that matches the most
183     # descendants.
184
185     # First, find the (uid, gid) pair that matches the most
186     # descendants.
187     ug = {}
188     for (uid, gid, _, _), count in d.iteritems():
189       ug[(uid, gid)] = ug.get((uid, gid), 0) + count
190     ug = MostPopularKey(ug, (0, 0))
191
192     # Now find the dmode and fmode that match the most descendants
193     # with that (uid, gid), and choose those.
194     best_dmode = (0, 0755)
195     best_fmode = (0, 0644)
196     for k, count in d.iteritems():
197       if k[:2] != ug: continue
198       if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
199       if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
200     self.best_subtree = ug + (best_dmode[1], best_fmode[1])
201
202     return d
203
204   def SetPermissions(self, script):
205     """Append set_perm/set_perm_recursive commands to 'script' to
206     set all permissions, users, and groups for the tree of files
207     rooted at 'self'."""
208
209     self.CountChildMetadata()
210
211     def recurse(item, current):
212       # current is the (uid, gid, dmode, fmode) tuple that the current
213       # item (and all its children) have already been set to.  We only
214       # need to issue set_perm/set_perm_recursive commands if we're
215       # supposed to be something different.
216       if item.dir:
217         if current != item.best_subtree:
218           script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
219           current = item.best_subtree
220
221         if item.uid != current[0] or item.gid != current[1] or \
222            item.mode != current[2]:
223           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
224
225         for i in item.children:
226           recurse(i, current)
227       else:
228         if item.uid != current[0] or item.gid != current[1] or \
229                item.mode != current[3]:
230           script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
231
232     recurse(self, (-1, -1, -1, -1))
233
234
235 def CopySystemFiles(input_zip, output_zip=None,
236                     substitute=None):
237   """Copies files underneath system/ in the input zip to the output
238   zip.  Populates the Item class with their metadata, and returns a
239   list of symlinks.  output_zip may be None, in which case the copy is
240   skipped (but the other side effects still happen).  substitute is an
241   optional dict of {output filename: contents} to be output instead of
242   certain input files.
243   """
244
245   symlinks = []
246
247   for info in input_zip.infolist():
248     if info.filename.startswith("SYSTEM/"):
249       basefilename = info.filename[7:]
250       if IsSymlink(info):
251         symlinks.append((input_zip.read(info.filename),
252                          "/system/" + basefilename))
253       else:
254         info2 = copy.copy(info)
255         fn = info2.filename = "system/" + basefilename
256         if substitute and fn in substitute and substitute[fn] is None:
257           continue
258         if output_zip is not None:
259           if substitute and fn in substitute:
260             data = substitute[fn]
261           else:
262             data = input_zip.read(info.filename)
263           output_zip.writestr(info2, data)
264         if fn.endswith("/"):
265           Item.Get(fn[:-1], dir=True)
266         else:
267           Item.Get(fn, dir=False)
268
269   symlinks.sort()
270   return symlinks
271
272
273 def SignOutput(temp_zip_name, output_zip_name):
274   key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
275   pw = key_passwords[OPTIONS.package_key]
276
277   common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
278
279
280 def AppendAssertions(script, input_zip):
281   device = GetBuildProp("ro.product.device", input_zip)
282   script.AssertDevice(device)
283
284   info = input_zip.read("OTA/android-info.txt")
285   m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
286   if m:
287     bootloaders = m.group(1).split("|")
288     script.AssertSomeBootloader(*bootloaders)
289
290
291 def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
292   """Generate a binary patch that creates the recovery image starting
293   with the boot image.  (Most of the space in these images is just the
294   kernel, which is identical for the two, so the resulting patch
295   should be efficient.)  Add it to the output zip, along with a shell
296   script that is run from init.rc on first boot to actually do the
297   patching and install the new recovery image.
298
299   recovery_img and boot_img should be File objects for the
300   corresponding images.
301
302   Returns an Item for the shell script, which must be made
303   executable.
304   """
305
306   patch = Difference(recovery_img, boot_img, "imgdiff")
307   common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
308   Item.Get("system/recovery-from-boot.p", dir=False)
309
310   # Images with different content will have a different first page, so
311   # we check to see if this recovery has already been installed by
312   # testing just the first 2k.
313   HEADER_SIZE = 2048
314   header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
315   sh = """#!/system/bin/sh
316 if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
317   log -t recovery "Installing new recovery image"
318   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
319 else
320   log -t recovery "Recovery image already installed"
321 fi
322 """ % { 'boot_size': boot_img.size,
323         'boot_sha1': boot_img.sha1,
324         'header_size': HEADER_SIZE,
325         'header_sha1': header_sha1,
326         'recovery_size': recovery_img.size,
327         'recovery_sha1': recovery_img.sha1 }
328   common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
329   return Item.Get("system/etc/install-recovery.sh", dir=False)
330
331
332 def WriteFullOTAPackage(input_zip, output_zip):
333   if OPTIONS.script_mode == "auto":
334     script = both_generator.BothGenerator(2)
335   elif OPTIONS.script_mode == "amend":
336     script = amend_generator.AmendGenerator()
337   else:
338     # TODO: how to determine this?  We don't know what version it will
339     # be installed on top of.  For now, we expect the API just won't
340     # change very often.
341     script = edify_generator.EdifyGenerator(2)
342
343   if not OPTIONS.omit_prereq:
344     ts = GetBuildProp("ro.build.date.utc", input_zip)
345     script.AssertOlderBuild(ts)
346
347   AppendAssertions(script, input_zip)
348
349   script.ShowProgress(0.1, 0)
350
351   try:
352     common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
353     script.WriteFirmwareImage("radio", "radio.img")
354   except KeyError:
355     print "warning: no radio image in input target_files; not flashing radio"
356
357   script.ShowProgress(0.5, 0)
358
359   if OPTIONS.wipe_user_data:
360     script.FormatPartition("userdata")
361
362   script.FormatPartition("system")
363   script.Mount("MTD", "system", "/system")
364   script.UnpackPackageDir("system", "/system")
365
366   symlinks = CopySystemFiles(input_zip, output_zip)
367   script.MakeSymlinks(symlinks)
368
369   boot_img = File("boot.img", common.BuildBootableImage(
370       os.path.join(OPTIONS.input_tmp, "BOOT")))
371   recovery_img = File("recovery.img", common.BuildBootableImage(
372       os.path.join(OPTIONS.input_tmp, "RECOVERY")))
373   i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
374
375   Item.GetMetadata()
376
377   # GetMetadata uses the data in android_filesystem_config.h to assign
378   # the uid/gid/mode of all files.  We want to override that for the
379   # recovery patching shell script to make it executable.
380   i.uid = 0
381   i.gid = 0
382   i.mode = 0544
383   Item.Get("system").SetPermissions(script)
384
385   common.CheckSize(boot_img.data, "boot.img")
386   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
387   script.ShowProgress(0.2, 0)
388
389   script.WriteRawImage("boot", "boot.img")
390   script.ShowProgress(0.2, 10)
391
392   if OPTIONS.extra_script is not None:
393     script.AppendExtra(OPTIONS.extra_script)
394
395   script.AddToZip(input_zip, output_zip)
396
397
398 class File(object):
399   def __init__(self, name, data):
400     self.name = name
401     self.data = data
402     self.size = len(data)
403     self.sha1 = sha.sha(data).hexdigest()
404
405   def WriteToTemp(self):
406     t = tempfile.NamedTemporaryFile()
407     t.write(self.data)
408     t.flush()
409     return t
410
411   def AddToZip(self, z):
412     common.ZipWriteStr(z, self.name, self.data)
413
414
415 def LoadSystemFiles(z):
416   """Load all the files from SYSTEM/... in a given target-files
417   ZipFile, and return a dict of {filename: File object}."""
418   out = {}
419   for info in z.infolist():
420     if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
421       fn = "system/" + info.filename[7:]
422       data = z.read(info.filename)
423       out[fn] = File(fn, data)
424   return out
425
426
427 def Difference(tf, sf, diff_program):
428   """Return the patch (as a string of data) needed to turn sf into tf.
429   diff_program is the name of an external program (or list, if
430   additional arguments are desired) to run to generate the diff.
431   """
432
433   ttemp = tf.WriteToTemp()
434   stemp = sf.WriteToTemp()
435
436   ext = os.path.splitext(tf.name)[1]
437
438   try:
439     ptemp = tempfile.NamedTemporaryFile()
440     if isinstance(diff_program, list):
441       cmd = copy.copy(diff_program)
442     else:
443       cmd = [diff_program]
444     cmd.append(stemp.name)
445     cmd.append(ttemp.name)
446     cmd.append(ptemp.name)
447     p = common.Run(cmd)
448     _, err = p.communicate()
449     if err or p.returncode != 0:
450       print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
451       return None
452     diff = ptemp.read()
453   finally:
454     ptemp.close()
455     stemp.close()
456     ttemp.close()
457
458   return diff
459
460
461 def GetBuildProp(property, z):
462   """Return the fingerprint of the build of a given target-files
463   ZipFile object."""
464   bp = z.read("SYSTEM/build.prop")
465   if not property:
466     return bp
467   m = re.search(re.escape(property) + r"=(.*)\n", bp)
468   if not m:
469     raise common.ExternalError("couldn't find %s in build.prop" % (property,))
470   return m.group(1).strip()
471
472
473 def GetRecoveryAPIVersion(zip):
474   """Returns the version of the recovery API.  Version 0 is the older
475   amend code (no separate binary)."""
476   try:
477     version = zip.read("META/recovery-api-version.txt")
478     return int(version)
479   except KeyError:
480     try:
481       # version one didn't have the recovery-api-version.txt file, but
482       # it did include an updater binary.
483       zip.getinfo("OTA/bin/updater")
484       return 1
485     except KeyError:
486       return 0
487
488 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
489   source_version = GetRecoveryAPIVersion(source_zip)
490
491   if OPTIONS.script_mode == 'amend':
492     script = amend_generator.AmendGenerator()
493   elif OPTIONS.script_mode == 'edify':
494     if source_version == 0:
495       print ("WARNING: generating edify script for a source that "
496              "can't install it.")
497     script = edify_generator.EdifyGenerator(source_version)
498   elif OPTIONS.script_mode == 'auto':
499     if source_version > 0:
500       script = edify_generator.EdifyGenerator(source_version)
501     else:
502       script = amend_generator.AmendGenerator()
503   else:
504     raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
505
506   print "Loading target..."
507   target_data = LoadSystemFiles(target_zip)
508   print "Loading source..."
509   source_data = LoadSystemFiles(source_zip)
510
511   verbatim_targets = []
512   patch_list = []
513   largest_source_size = 0
514   for fn in sorted(target_data.keys()):
515     tf = target_data[fn]
516     sf = source_data.get(fn, None)
517
518     if sf is None or fn in OPTIONS.require_verbatim:
519       # This file should be included verbatim
520       if fn in OPTIONS.prohibit_verbatim:
521         raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
522       print "send", fn, "verbatim"
523       tf.AddToZip(output_zip)
524       verbatim_targets.append((fn, tf.size))
525     elif tf.sha1 != sf.sha1:
526       # File is different; consider sending as a patch
527       diff_method = "bsdiff"
528       if tf.name.endswith(".gz"):
529         diff_method = "imgdiff"
530       d = Difference(tf, sf, diff_method)
531       if d is not None:
532         print fn, tf.size, len(d), (float(len(d)) / tf.size)
533       if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
534         # patch is almost as big as the file; don't bother patching
535         tf.AddToZip(output_zip)
536         verbatim_targets.append((fn, tf.size))
537       else:
538         common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
539         patch_list.append((fn, tf, sf, tf.size))
540         largest_source_size = max(largest_source_size, sf.size)
541     else:
542       # Target file identical to source.
543       pass
544
545   total_verbatim_size = sum([i[1] for i in verbatim_targets])
546   total_patched_size = sum([i[3] for i in patch_list])
547
548   source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
549   target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
550
551   script.Mount("MTD", "system", "/system")
552   script.AssertSomeFingerprint(source_fp, target_fp)
553
554   source_boot = File("/tmp/boot.img",
555                      common.BuildBootableImage(
556       os.path.join(OPTIONS.source_tmp, "BOOT")))
557   target_boot = File("/tmp/boot.img",
558                      common.BuildBootableImage(
559       os.path.join(OPTIONS.target_tmp, "BOOT")))
560   updating_boot = (source_boot.data != target_boot.data)
561
562   source_recovery = File("system/recovery.img",
563                          common.BuildBootableImage(
564       os.path.join(OPTIONS.source_tmp, "RECOVERY")))
565   target_recovery = File("system/recovery.img",
566                          common.BuildBootableImage(
567       os.path.join(OPTIONS.target_tmp, "RECOVERY")))
568   updating_recovery = (source_recovery.data != target_recovery.data)
569
570   source_radio = source_zip.read("RADIO/image")
571   target_radio = target_zip.read("RADIO/image")
572   updating_radio = (source_radio != target_radio)
573
574   # The last 0.1 is reserved for creating symlinks, fixing
575   # permissions, and writing the boot image (if necessary).
576   progress_bar_total = 1.0
577   if updating_boot:
578     progress_bar_total -= 0.1
579   if updating_radio:
580     progress_bar_total -= 0.3
581
582   AppendAssertions(script, target_zip)
583
584   script.Print("Verifying current system...")
585
586   pb_verify = progress_bar_total * 0.3 * \
587               (total_patched_size /
588                float(total_patched_size+total_verbatim_size+1))
589
590   for i, (fn, tf, sf, size) in enumerate(patch_list):
591     if i % 5 == 0:
592       next_sizes = sum([i[3] for i in patch_list[i:i+5]])
593       script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
594
595     script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
596
597   if updating_boot:
598     d = Difference(target_boot, source_boot, "imgdiff")
599     print "boot      target: %d  source: %d  diff: %d" % (
600         target_boot.size, source_boot.size, len(d))
601
602     common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
603
604     script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
605                       (source_boot.size, source_boot.sha1,
606                        target_boot.size, target_boot.sha1))
607
608   if patch_list or updating_recovery or updating_boot:
609     script.CacheFreeSpaceCheck(largest_source_size)
610     script.Print("Unpacking patches...")
611     script.UnpackPackageDir("patch", "/tmp/patchtmp")
612
613   script.Comment("---- start making changes here ----")
614
615   if OPTIONS.wipe_user_data:
616     script.Print("Erasing user data...")
617     script.FormatPartition("userdata")
618
619   script.Print("Removing unneeded files...")
620   script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
621                      ["/"+i for i in sorted(source_data)
622                             if i not in target_data])
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("MTD:boot:%d:%s:%d:%s"
630                       % (source_boot.size, source_boot.sha1,
631                          target_boot.size, target_boot.sha1),
632                       "-",
633                       target_boot.size, target_boot.sha1,
634                       source_boot.sha1, "/tmp/patchtmp/boot.img.p")
635     print "boot image changed; including."
636   else:
637     print "boot image unchanged; skipping."
638
639   if updating_recovery:
640     # Is it better to generate recovery as a patch from the current
641     # boot image, or from the previous recovery image?  For large
642     # updates with significant kernel changes, probably the former.
643     # For small updates where the kernel hasn't changed, almost
644     # certainly the latter.  We pick the first option.  Future
645     # complicated schemes may let us effectively use both.
646     #
647     # A wacky possibility: as long as there is room in the boot
648     # partition, include the binaries and image files from recovery in
649     # the boot image (though not in the ramdisk) so they can be used
650     # as fodder for constructing the recovery image.
651     recovery_sh_item = MakeRecoveryPatch(output_zip,
652                                          target_recovery, target_boot)
653     print "recovery image changed; including as patch from boot."
654   else:
655     print "recovery image unchanged; skipping."
656
657   if updating_radio:
658     script.ShowProgress(0.3, 10)
659     script.Print("Writing radio image...")
660     script.WriteFirmwareImage("radio", "radio.img")
661     common.ZipWriteStr(output_zip, "radio.img", target_radio)
662     print "radio image changed; including."
663   else:
664     print "radio image unchanged; skipping."
665
666   script.Print("Patching system files...")
667   pb_apply = progress_bar_total * 0.7 * \
668              (total_patched_size /
669               float(total_patched_size+total_verbatim_size+1))
670   for i, (fn, tf, sf, size) in enumerate(patch_list):
671     if i % 5 == 0:
672       next_sizes = sum([i[3] for i in patch_list[i:i+5]])
673       script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
674     script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
675                       sf.sha1, "/tmp/patchtmp/"+fn+".p")
676
677   target_symlinks = CopySystemFiles(target_zip, None)
678
679   target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
680   temp_script = script.MakeTemporary()
681   Item.GetMetadata()
682   if updating_recovery:
683     recovery_sh_item.uid = 0
684     recovery_sh_item.gid = 0
685     recovery_sh_item.mode = 0544
686   Item.Get("system").SetPermissions(temp_script)
687
688   # Note that this call will mess up the tree of Items, so make sure
689   # we're done with it.
690   source_symlinks = CopySystemFiles(source_zip, None)
691   source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
692
693   # Delete all the symlinks in source that aren't in target.  This
694   # needs to happen before verbatim files are unpacked, in case a
695   # symlink in the source is replaced by a real file in the target.
696   to_delete = []
697   for dest, link in source_symlinks:
698     if link not in target_symlinks_d:
699       to_delete.append(link)
700   script.DeleteFiles(to_delete)
701
702   if verbatim_targets:
703     pb_verbatim = progress_bar_total * \
704                   (total_verbatim_size /
705                    float(total_patched_size+total_verbatim_size+1))
706     script.ShowProgress(pb_verbatim, 5)
707     script.Print("Unpacking new files...")
708     script.UnpackPackageDir("system", "/system")
709
710   script.Print("Finishing up...")
711
712   # Create all the symlinks that don't already exist, or point to
713   # somewhere different than what we want.  Delete each symlink before
714   # creating it, since the 'symlink' command won't overwrite.
715   to_create = []
716   for dest, link in target_symlinks:
717     if link in source_symlinks_d:
718       if dest != source_symlinks_d[link]:
719         to_create.append((dest, link))
720     else:
721       to_create.append((dest, link))
722   script.DeleteFiles([i[1] for i in to_create])
723   script.MakeSymlinks(to_create)
724
725   # Now that the symlinks are created, we can set all the
726   # permissions.
727   script.AppendScript(temp_script)
728
729   if OPTIONS.extra_script is not None:
730     scirpt.AppendExtra(OPTIONS.extra_script)
731
732   script.AddToZip(target_zip, output_zip)
733
734
735 def main(argv):
736
737   def option_handler(o, a):
738     if o in ("-b", "--board_config"):
739       common.LoadBoardConfig(a)
740     elif o in ("-k", "--package_key"):
741       OPTIONS.package_key = a
742     elif o in ("-i", "--incremental_from"):
743       OPTIONS.incremental_source = a
744     elif o in ("-w", "--wipe_user_data"):
745       OPTIONS.wipe_user_data = True
746     elif o in ("-n", "--no_prereq"):
747       OPTIONS.omit_prereq = True
748     elif o in ("-e", "--extra_script"):
749       OPTIONS.extra_script = a
750     elif o in ("-m", "--script_mode"):
751       OPTIONS.script_mode = a
752     else:
753       return False
754     return True
755
756   args = common.ParseOptions(argv, __doc__,
757                              extra_opts="b:k:i:d:wne:m:",
758                              extra_long_opts=["board_config=",
759                                               "package_key=",
760                                               "incremental_from=",
761                                               "wipe_user_data",
762                                               "no_prereq",
763                                               "extra_script=",
764                                               "script_mode="],
765                              extra_option_handler=option_handler)
766
767   if len(args) != 2:
768     common.Usage(__doc__)
769     sys.exit(1)
770
771   if not OPTIONS.max_image_size:
772     print
773     print "  WARNING:  No board config specified; will not check image"
774     print "  sizes against limits.  Use -b to make sure the generated"
775     print "  images don't exceed partition sizes."
776     print
777
778   if OPTIONS.script_mode not in ("amend", "edify", "auto"):
779     raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
780
781   if OPTIONS.extra_script is not None:
782     OPTIONS.extra_script = open(OPTIONS.extra_script).read()
783
784   print "unzipping target target-files..."
785   OPTIONS.input_tmp = common.UnzipTemp(args[0])
786   OPTIONS.target_tmp = OPTIONS.input_tmp
787   input_zip = zipfile.ZipFile(args[0], "r")
788   if OPTIONS.package_key:
789     temp_zip_file = tempfile.NamedTemporaryFile()
790     output_zip = zipfile.ZipFile(temp_zip_file, "w",
791                                  compression=zipfile.ZIP_DEFLATED)
792   else:
793     output_zip = zipfile.ZipFile(args[1], "w",
794                                  compression=zipfile.ZIP_DEFLATED)
795
796   if OPTIONS.incremental_source is None:
797     WriteFullOTAPackage(input_zip, output_zip)
798   else:
799     print "unzipping source target-files..."
800     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
801     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
802     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
803
804   output_zip.close()
805   if OPTIONS.package_key:
806     SignOutput(temp_zip_file.name, args[1])
807     temp_zip_file.close()
808
809   common.Cleanup()
810
811   print "done."
812
813
814 if __name__ == '__main__':
815   try:
816     main(sys.argv[1:])
817   except common.ExternalError, e:
818     print
819     print "   ERROR: %s" % (e,)
820     print
821     sys.exit(1)