ota_from_target_files.py revision e5ff5907bef42fa9f3eff2e90ad941423dcc91dc
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  sh = """#!/system/bin/sh
340if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
341  log -t recovery "Installing new recovery image"
342  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
343else
344  log -t recovery "Recovery image already installed"
345fi
346""" % { 'boot_size': boot_img.size,
347        'boot_sha1': boot_img.sha1,
348        'recovery_size': recovery_img.size,
349        'recovery_sha1': recovery_img.sha1,
350        'boot_type': boot_type,
351        'boot_device': boot_device,
352        'recovery_type': recovery_type,
353        'recovery_device': recovery_device,
354        }
355  common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
356  return Item.Get("system/etc/install-recovery.sh", dir=False)
357
358
359def WriteFullOTAPackage(input_zip, output_zip):
360  # TODO: how to determine this?  We don't know what version it will
361  # be installed on top of.  For now, we expect the API just won't
362  # change very often.
363  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
364
365  metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
366              "pre-device": GetBuildProp("ro.product.device", input_zip),
367              "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
368              }
369
370  device_specific = common.DeviceSpecificParams(
371      input_zip=input_zip,
372      input_version=OPTIONS.info_dict["recovery_api_version"],
373      output_zip=output_zip,
374      script=script,
375      input_tmp=OPTIONS.input_tmp,
376      metadata=metadata,
377      info_dict=OPTIONS.info_dict)
378
379  if not OPTIONS.omit_prereq:
380    ts = GetBuildProp("ro.build.date.utc", input_zip)
381    script.AssertOlderBuild(ts)
382
383  AppendAssertions(script, input_zip)
384  device_specific.FullOTA_Assertions()
385  device_specific.FullOTA_InstallBegin()
386
387  script.ShowProgress(0.5, 0)
388
389  if OPTIONS.wipe_user_data:
390    script.FormatPartition("/data")
391
392  script.FormatPartition("/system")
393  script.Mount("/system")
394  script.UnpackPackageDir("recovery", "/system")
395  script.UnpackPackageDir("system", "/system")
396
397  (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
398  script.MakeSymlinks(symlinks)
399  if OPTIONS.aslr_mode:
400    script.RetouchBinaries(retouch_files)
401  else:
402    script.UndoRetouchBinaries(retouch_files)
403
404  boot_img = common.GetBootableImage("boot.img", "boot.img",
405                                     OPTIONS.input_tmp, "BOOT")
406  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
407                                         OPTIONS.input_tmp, "RECOVERY")
408  MakeRecoveryPatch(output_zip, recovery_img, boot_img)
409
410  Item.GetMetadata(input_zip)
411  Item.Get("system").SetPermissions(script)
412
413  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
414  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
415  script.ShowProgress(0.2, 0)
416
417  script.ShowProgress(0.2, 10)
418  script.WriteRawImage("/boot", "boot.img")
419
420  script.ShowProgress(0.1, 0)
421  device_specific.FullOTA_InstallEnd()
422
423  if OPTIONS.extra_script is not None:
424    script.AppendExtra(OPTIONS.extra_script)
425
426  script.UnmountAll()
427  script.AddToZip(input_zip, output_zip)
428  WriteMetadata(metadata, output_zip)
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
436
437
438
439def LoadSystemFiles(z):
440  """Load all the files from SYSTEM/... in a given target-files
441  ZipFile, and return a dict of {filename: File object}."""
442  out = {}
443  retouch_files = []
444  for info in z.infolist():
445    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
446      basefilename = info.filename[7:]
447      fn = "system/" + basefilename
448      data = z.read(info.filename)
449      out[fn] = common.File(fn, data)
450      if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
451        retouch_files.append(("/system/" + basefilename,
452                              out[fn].sha1))
453  return (out, retouch_files)
454
455
456def GetBuildProp(property, z):
457  """Return the fingerprint of the build of a given target-files
458  ZipFile object."""
459  bp = z.read("SYSTEM/build.prop")
460  if not property:
461    return bp
462  m = re.search(re.escape(property) + r"=(.*)\n", bp)
463  if not m:
464    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
465  return m.group(1).strip()
466
467
468def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
469  source_version = OPTIONS.source_info_dict["recovery_api_version"]
470  target_version = OPTIONS.target_info_dict["recovery_api_version"]
471
472  if source_version == 0:
473    print ("WARNING: generating edify script for a source that "
474           "can't install it.")
475  script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict)
476
477  metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
478              "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
479              }
480
481  device_specific = common.DeviceSpecificParams(
482      source_zip=source_zip,
483      source_version=source_version,
484      target_zip=target_zip,
485      target_version=target_version,
486      output_zip=output_zip,
487      script=script,
488      metadata=metadata,
489      info_dict=OPTIONS.info_dict)
490
491  print "Loading target..."
492  (target_data, target_retouch_files) = LoadSystemFiles(target_zip)
493  print "Loading source..."
494  (source_data, source_retouch_files) = LoadSystemFiles(source_zip)
495
496  verbatim_targets = []
497  patch_list = []
498  diffs = []
499  largest_source_size = 0
500  for fn in sorted(target_data.keys()):
501    tf = target_data[fn]
502    assert fn == tf.name
503    sf = source_data.get(fn, None)
504
505    if sf is None or fn in OPTIONS.require_verbatim:
506      # This file should be included verbatim
507      if fn in OPTIONS.prohibit_verbatim:
508        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
509      print "send", fn, "verbatim"
510      tf.AddToZip(output_zip)
511      verbatim_targets.append((fn, tf.size))
512    elif tf.sha1 != sf.sha1:
513      # File is different; consider sending as a patch
514      diffs.append(common.Difference(tf, sf))
515    else:
516      # Target file identical to source.
517      pass
518
519  common.ComputeDifferences(diffs)
520
521  for diff in diffs:
522    tf, sf, d = diff.GetPatch()
523    if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
524      # patch is almost as big as the file; don't bother patching
525      tf.AddToZip(output_zip)
526      verbatim_targets.append((tf.name, tf.size))
527    else:
528      common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
529      patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest()))
530      largest_source_size = max(largest_source_size, sf.size)
531
532  source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
533  target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
534  metadata["pre-build"] = source_fp
535  metadata["post-build"] = target_fp
536
537  script.Mount("/system")
538  script.AssertSomeFingerprint(source_fp, target_fp)
539
540  source_boot = common.GetBootableImage(
541      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT")
542  target_boot = common.GetBootableImage(
543      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
544  updating_boot = (source_boot.data != target_boot.data)
545
546  source_recovery = common.GetBootableImage(
547      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY")
548  target_recovery = common.GetBootableImage(
549      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
550  updating_recovery = (source_recovery.data != target_recovery.data)
551
552  # Here's how we divide up the progress bar:
553  #  0.1 for verifying the start state (PatchCheck calls)
554  #  0.8 for applying patches (ApplyPatch calls)
555  #  0.1 for unpacking verbatim files, symlinking, and doing the
556  #      device-specific commands.
557
558  AppendAssertions(script, target_zip)
559  device_specific.IncrementalOTA_Assertions()
560
561  script.Print("Verifying current system...")
562
563  device_specific.IncrementalOTA_VerifyBegin()
564
565  script.ShowProgress(0.1, 0)
566  total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
567  if updating_boot:
568    total_verify_size += source_boot.size
569  so_far = 0
570
571  for fn, tf, sf, size, patch_sha in patch_list:
572    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
573    so_far += sf.size
574    script.SetProgress(so_far / total_verify_size)
575
576  if updating_boot:
577    d = common.Difference(target_boot, source_boot)
578    _, _, d = d.ComputePatch()
579    print "boot      target: %d  source: %d  diff: %d" % (
580        target_boot.size, source_boot.size, len(d))
581
582    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
583
584    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
585
586    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
587                      (boot_type, boot_device,
588                       source_boot.size, source_boot.sha1,
589                       target_boot.size, target_boot.sha1))
590    so_far += source_boot.size
591    script.SetProgress(so_far / total_verify_size)
592
593  if patch_list or updating_recovery or updating_boot:
594    script.CacheFreeSpaceCheck(largest_source_size)
595
596  device_specific.IncrementalOTA_VerifyEnd()
597
598  script.Comment("---- start making changes here ----")
599
600  device_specific.IncrementalOTA_InstallBegin()
601
602  if OPTIONS.wipe_user_data:
603    script.Print("Erasing user data...")
604    script.FormatPartition("/data")
605
606  script.Print("Removing unneeded files...")
607  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
608                     ["/"+i for i in sorted(source_data)
609                            if i not in target_data] +
610                     ["/system/recovery.img"])
611
612  script.ShowProgress(0.8, 0)
613  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
614  if updating_boot:
615    total_patch_size += target_boot.size
616  so_far = 0
617
618  script.Print("Patching system files...")
619  deferred_patch_list = []
620  for item in patch_list:
621    fn, tf, sf, size, _ = item
622    if tf.name == "system/build.prop":
623      deferred_patch_list.append(item)
624      continue
625    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
626    so_far += tf.size
627    script.SetProgress(so_far / total_patch_size)
628
629  if updating_boot:
630    # Produce the boot image by applying a patch to the current
631    # contents of the boot partition, and write it back to the
632    # partition.
633    script.Print("Patching boot image...")
634    script.ApplyPatch("%s:%s:%d:%s:%d:%s"
635                      % (boot_type, boot_device,
636                         source_boot.size, source_boot.sha1,
637                         target_boot.size, target_boot.sha1),
638                      "-",
639                      target_boot.size, target_boot.sha1,
640                      source_boot.sha1, "patch/boot.img.p")
641    so_far += target_boot.size
642    script.SetProgress(so_far / total_patch_size)
643    print "boot image changed; including."
644  else:
645    print "boot image unchanged; skipping."
646
647  if updating_recovery:
648    # Is it better to generate recovery as a patch from the current
649    # boot image, or from the previous recovery image?  For large
650    # updates with significant kernel changes, probably the former.
651    # For small updates where the kernel hasn't changed, almost
652    # certainly the latter.  We pick the first option.  Future
653    # complicated schemes may let us effectively use both.
654    #
655    # A wacky possibility: as long as there is room in the boot
656    # partition, include the binaries and image files from recovery in
657    # the boot image (though not in the ramdisk) so they can be used
658    # as fodder for constructing the recovery image.
659    MakeRecoveryPatch(output_zip, target_recovery, target_boot)
660    script.DeleteFiles(["/system/recovery-from-boot.p",
661                        "/system/etc/install-recovery.sh"])
662    print "recovery image changed; including as patch from boot."
663  else:
664    print "recovery image unchanged; skipping."
665
666  script.ShowProgress(0.1, 10)
667
668  (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None)
669
670  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
671  temp_script = script.MakeTemporary()
672  Item.GetMetadata(target_zip)
673  Item.Get("system").SetPermissions(temp_script)
674
675  # Note that this call will mess up the tree of Items, so make sure
676  # we're done with it.
677  (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None)
678  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
679
680  # Delete all the symlinks in source that aren't in target.  This
681  # needs to happen before verbatim files are unpacked, in case a
682  # symlink in the source is replaced by a real file in the target.
683  to_delete = []
684  for dest, link in source_symlinks:
685    if link not in target_symlinks_d:
686      to_delete.append(link)
687  script.DeleteFiles(to_delete)
688
689  if verbatim_targets:
690    script.Print("Unpacking new files...")
691    script.UnpackPackageDir("system", "/system")
692
693  if updating_recovery:
694    script.Print("Unpacking new recovery...")
695    script.UnpackPackageDir("recovery", "/system")
696
697  script.Print("Symlinks and permissions...")
698
699  # Create all the symlinks that don't already exist, or point to
700  # somewhere different than what we want.  Delete each symlink before
701  # creating it, since the 'symlink' command won't overwrite.
702  to_create = []
703  for dest, link in target_symlinks:
704    if link in source_symlinks_d:
705      if dest != source_symlinks_d[link]:
706        to_create.append((dest, link))
707    else:
708      to_create.append((dest, link))
709  script.DeleteFiles([i[1] for i in to_create])
710  script.MakeSymlinks(to_create)
711  if OPTIONS.aslr_mode:
712    script.RetouchBinaries(target_retouch_files)
713  else:
714    script.UndoRetouchBinaries(target_retouch_files)
715
716  # Now that the symlinks are created, we can set all the
717  # permissions.
718  script.AppendScript(temp_script)
719
720  # Do device-specific installation (eg, write radio image).
721  device_specific.IncrementalOTA_InstallEnd()
722
723  if OPTIONS.extra_script is not None:
724    script.AppendExtra(OPTIONS.extra_script)
725
726  # Patch the build.prop file last, so if something fails but the
727  # device can still come up, it appears to be the old build and will
728  # get set the OTA package again to retry.
729  script.Print("Patching remaining system files...")
730  for item in deferred_patch_list:
731    fn, tf, sf, size, _ = item
732    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
733  script.SetPermissions("/system/build.prop", 0, 0, 0644)
734
735  script.AddToZip(target_zip, output_zip)
736  WriteMetadata(metadata, output_zip)
737
738
739def main(argv):
740
741  def option_handler(o, a):
742    if o in ("-b", "--board_config"):
743      pass   # deprecated
744    elif o in ("-k", "--package_key"):
745      OPTIONS.package_key = a
746    elif o in ("-i", "--incremental_from"):
747      OPTIONS.incremental_source = a
748    elif o in ("-w", "--wipe_user_data"):
749      OPTIONS.wipe_user_data = True
750    elif o in ("-n", "--no_prereq"):
751      OPTIONS.omit_prereq = True
752    elif o in ("-e", "--extra_script"):
753      OPTIONS.extra_script = a
754    elif o in ("-a", "--aslr_mode"):
755      if a in ("on", "On", "true", "True", "yes", "Yes"):
756        OPTIONS.aslr_mode = True
757      else:
758        OPTIONS.aslr_mode = False
759    elif o in ("--worker_threads"):
760      OPTIONS.worker_threads = int(a)
761    else:
762      return False
763    return True
764
765  args = common.ParseOptions(argv, __doc__,
766                             extra_opts="b:k:i:d:wne:a:",
767                             extra_long_opts=["board_config=",
768                                              "package_key=",
769                                              "incremental_from=",
770                                              "wipe_user_data",
771                                              "no_prereq",
772                                              "extra_script=",
773                                              "worker_threads=",
774                                              "aslr_mode=",
775                                              ],
776                             extra_option_handler=option_handler)
777
778  if len(args) != 2:
779    common.Usage(__doc__)
780    sys.exit(1)
781
782  if OPTIONS.extra_script is not None:
783    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
784
785  print "unzipping target target-files..."
786  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
787
788  OPTIONS.target_tmp = OPTIONS.input_tmp
789  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
790  if OPTIONS.verbose:
791    print "--- target info ---"
792    common.DumpInfoDict(OPTIONS.info_dict)
793
794  if OPTIONS.device_specific is None:
795    OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
796  if OPTIONS.device_specific is not None:
797    OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
798    print "using device-specific extensions in", OPTIONS.device_specific
799
800  temp_zip_file = tempfile.NamedTemporaryFile()
801  output_zip = zipfile.ZipFile(temp_zip_file, "w",
802                               compression=zipfile.ZIP_DEFLATED)
803
804  if OPTIONS.incremental_source is None:
805    WriteFullOTAPackage(input_zip, output_zip)
806    if OPTIONS.package_key is None:
807      OPTIONS.package_key = OPTIONS.info_dict.get(
808          "default_system_dev_certificate",
809          "build/target/product/security/testkey")
810  else:
811    print "unzipping source target-files..."
812    OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
813    OPTIONS.target_info_dict = OPTIONS.info_dict
814    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
815    if OPTIONS.package_key is None:
816      OPTIONS.package_key = OPTIONS.source_info_dict.get(
817          "default_system_dev_certificate",
818          "build/target/product/security/testkey")
819    if OPTIONS.verbose:
820      print "--- source info ---"
821      common.DumpInfoDict(OPTIONS.source_info_dict)
822    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
823
824  output_zip.close()
825
826  SignOutput(temp_zip_file.name, args[1])
827  temp_zip_file.close()
828
829  common.Cleanup()
830
831  print "done."
832
833
834if __name__ == '__main__':
835  try:
836    common.CloseInheritedPipes()
837    main(sys.argv[1:])
838  except common.ExternalError, e:
839    print
840    print "   ERROR: %s" % (e,)
841    print
842    sys.exit(1)
843