OSDN Git Service

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