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