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