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