OSDN Git Service

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