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