ota_from_target_files.py revision 0d92f1f13ad89bf8ffbb75764bbe83452612792a
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(input_tmp, 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  diff_program = ["imgdiff"]
328  path = os.path.join(input_tmp, "SYSTEM", "etc", "recovery-resource.dat")
329  if os.path.exists(path):
330    diff_program.append("-b")
331    diff_program.append(path)
332    bonus_args = "-b /system/etc/recovery-resource.dat"
333  else:
334    bonus_args = ""
335
336  d = common.Difference(recovery_img, boot_img, diff_program=diff_program)
337  _, _, patch = d.ComputePatch()
338  common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
339  Item.Get("system/recovery-from-boot.p", dir=False)
340
341  boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
342  recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
343
344  sh = """#!/system/bin/sh
345if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
346  log -t recovery "Installing new recovery image"
347  applypatch %(bonus_args)s %(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        'recovery_size': recovery_img.size,
354        'recovery_sha1': recovery_img.sha1,
355        'boot_type': boot_type,
356        'boot_device': boot_device,
357        'recovery_type': recovery_type,
358        'recovery_device': recovery_device,
359        'bonus_args': bonus_args,
360        }
361  common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
362  return Item.Get("system/etc/install-recovery.sh", dir=False)
363
364
365def WriteFullOTAPackage(input_zip, output_zip):
366  # TODO: how to determine this?  We don't know what version it will
367  # be installed on top of.  For now, we expect the API just won't
368  # change very often.
369  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
370
371  metadata = {"post-build": GetBuildProp("ro.build.fingerprint",
372                                         OPTIONS.info_dict),
373              "pre-device": GetBuildProp("ro.product.device",
374                                         OPTIONS.info_dict),
375              "post-timestamp": GetBuildProp("ro.build.date.utc",
376                                             OPTIONS.info_dict),
377              }
378
379  device_specific = common.DeviceSpecificParams(
380      input_zip=input_zip,
381      input_version=OPTIONS.info_dict["recovery_api_version"],
382      output_zip=output_zip,
383      script=script,
384      input_tmp=OPTIONS.input_tmp,
385      metadata=metadata,
386      info_dict=OPTIONS.info_dict)
387
388  if not OPTIONS.omit_prereq:
389    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
390    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
391    script.AssertOlderBuild(ts, ts_text)
392
393  AppendAssertions(script, OPTIONS.info_dict)
394  device_specific.FullOTA_Assertions()
395  device_specific.FullOTA_InstallBegin()
396
397  script.ShowProgress(0.5, 0)
398
399  if OPTIONS.wipe_user_data:
400    script.FormatPartition("/data")
401
402  if "selinux_fc" in OPTIONS.info_dict:
403    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
404
405  script.FormatPartition("/system")
406  script.Mount("/system")
407  script.UnpackPackageDir("recovery", "/system")
408  script.UnpackPackageDir("system", "/system")
409
410  symlinks = CopySystemFiles(input_zip, output_zip)
411  script.MakeSymlinks(symlinks)
412
413  boot_img = common.GetBootableImage("boot.img", "boot.img",
414                                     OPTIONS.input_tmp, "BOOT")
415  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
416                                         OPTIONS.input_tmp, "RECOVERY")
417  MakeRecoveryPatch(OPTIONS.input_tmp, output_zip, recovery_img, boot_img)
418
419  Item.GetMetadata(input_zip)
420  Item.Get("system").SetPermissions(script)
421
422  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
423  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
424  script.ShowProgress(0.2, 0)
425
426  script.ShowProgress(0.2, 10)
427  script.WriteRawImage("/boot", "boot.img")
428
429  script.ShowProgress(0.1, 0)
430  device_specific.FullOTA_InstallEnd()
431
432  if OPTIONS.extra_script is not None:
433    script.AppendExtra(OPTIONS.extra_script)
434
435  script.UnmountAll()
436  script.AddToZip(input_zip, output_zip)
437  WriteMetadata(metadata, output_zip)
438
439def WritePolicyConfig(file_context, output_zip):
440  f = open(file_context, 'r');
441  basename = os.path.basename(file_context)
442  common.ZipWriteStr(output_zip, basename, f.read())
443
444
445def WriteMetadata(metadata, output_zip):
446  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
447                     "".join(["%s=%s\n" % kv
448                              for kv in sorted(metadata.iteritems())]))
449
450def LoadSystemFiles(z):
451  """Load all the files from SYSTEM/... in a given target-files
452  ZipFile, and return a dict of {filename: File object}."""
453  out = {}
454  for info in z.infolist():
455    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
456      basefilename = info.filename[7:]
457      fn = "system/" + basefilename
458      data = z.read(info.filename)
459      out[fn] = common.File(fn, data)
460  return out
461
462
463def GetBuildProp(prop, info_dict):
464  """Return the fingerprint of the build of a given target-files info_dict."""
465  try:
466    return info_dict.get("build.prop", {})[prop]
467  except KeyError:
468    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
469
470
471def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
472  source_version = OPTIONS.source_info_dict["recovery_api_version"]
473  target_version = OPTIONS.target_info_dict["recovery_api_version"]
474
475  if source_version == 0:
476    print ("WARNING: generating edify script for a source that "
477           "can't install it.")
478  script = edify_generator.EdifyGenerator(source_version,
479                                          OPTIONS.target_info_dict)
480
481  metadata = {"pre-device": GetBuildProp("ro.product.device",
482                                         OPTIONS.source_info_dict),
483              "post-timestamp": GetBuildProp("ro.build.date.utc",
484                                             OPTIONS.target_info_dict),
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 = LoadSystemFiles(target_zip)
499  print "Loading source..."
500  source_data = 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", OPTIONS.source_info_dict)
539  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
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      OPTIONS.source_info_dict)
549  target_boot = common.GetBootableImage(
550      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
551  updating_boot = (source_boot.data != target_boot.data)
552
553  source_recovery = common.GetBootableImage(
554      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
555      OPTIONS.source_info_dict)
556  target_recovery = common.GetBootableImage(
557      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
558  updating_recovery = (source_recovery.data != target_recovery.data)
559
560  # Here's how we divide up the progress bar:
561  #  0.1 for verifying the start state (PatchCheck calls)
562  #  0.8 for applying patches (ApplyPatch calls)
563  #  0.1 for unpacking verbatim files, symlinking, and doing the
564  #      device-specific commands.
565
566  AppendAssertions(script, OPTIONS.target_info_dict)
567  device_specific.IncrementalOTA_Assertions()
568
569  script.Print("Verifying current system...")
570
571  device_specific.IncrementalOTA_VerifyBegin()
572
573  script.ShowProgress(0.1, 0)
574  total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
575  if updating_boot:
576    total_verify_size += source_boot.size
577  so_far = 0
578
579  for fn, tf, sf, size, patch_sha in patch_list:
580    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
581    so_far += sf.size
582    script.SetProgress(so_far / total_verify_size)
583
584  if updating_boot:
585    d = common.Difference(target_boot, source_boot)
586    _, _, d = d.ComputePatch()
587    print "boot      target: %d  source: %d  diff: %d" % (
588        target_boot.size, source_boot.size, len(d))
589
590    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
591
592    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
593
594    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
595                      (boot_type, boot_device,
596                       source_boot.size, source_boot.sha1,
597                       target_boot.size, target_boot.sha1))
598    so_far += source_boot.size
599    script.SetProgress(so_far / total_verify_size)
600
601  if patch_list or updating_recovery or updating_boot:
602    script.CacheFreeSpaceCheck(largest_source_size)
603
604  device_specific.IncrementalOTA_VerifyEnd()
605
606  script.Comment("---- start making changes here ----")
607
608  device_specific.IncrementalOTA_InstallBegin()
609
610  if OPTIONS.wipe_user_data:
611    script.Print("Erasing user data...")
612    script.FormatPartition("/data")
613
614  script.Print("Removing unneeded files...")
615  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
616                     ["/"+i for i in sorted(source_data)
617                            if i not in target_data] +
618                     ["/system/recovery.img"])
619
620  script.ShowProgress(0.8, 0)
621  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
622  if updating_boot:
623    total_patch_size += target_boot.size
624  so_far = 0
625
626  script.Print("Patching system files...")
627  deferred_patch_list = []
628  for item in patch_list:
629    fn, tf, sf, size, _ = item
630    if tf.name == "system/build.prop":
631      deferred_patch_list.append(item)
632      continue
633    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
634    so_far += tf.size
635    script.SetProgress(so_far / total_patch_size)
636
637  if updating_boot:
638    # Produce the boot image by applying a patch to the current
639    # contents of the boot partition, and write it back to the
640    # partition.
641    script.Print("Patching boot image...")
642    script.ApplyPatch("%s:%s:%d:%s:%d:%s"
643                      % (boot_type, boot_device,
644                         source_boot.size, source_boot.sha1,
645                         target_boot.size, target_boot.sha1),
646                      "-",
647                      target_boot.size, target_boot.sha1,
648                      source_boot.sha1, "patch/boot.img.p")
649    so_far += target_boot.size
650    script.SetProgress(so_far / total_patch_size)
651    print "boot image changed; including."
652  else:
653    print "boot image unchanged; skipping."
654
655  if updating_recovery:
656    # Recovery is generated as a patch using both the boot image
657    # (which contains the same linux kernel as recovery) and the file
658    # /system/etc/recovery-resource.dat (which contains all the images
659    # used in the recovery UI) as sources.  This lets us minimize the
660    # size of the patch, which must be included in every OTA package.
661    #
662    # For older builds where recovery-resource.dat is not present, we
663    # use only the boot image as the source.
664
665    MakeRecoveryPatch(OPTIONS.target_tmp, output_zip,
666                      target_recovery, target_boot)
667    script.DeleteFiles(["/system/recovery-from-boot.p",
668                        "/system/etc/install-recovery.sh"])
669    print "recovery image changed; including as patch from boot."
670  else:
671    print "recovery image unchanged; skipping."
672
673  script.ShowProgress(0.1, 10)
674
675  target_symlinks = CopySystemFiles(target_zip, None)
676
677  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
678  temp_script = script.MakeTemporary()
679  Item.GetMetadata(target_zip)
680  Item.Get("system").SetPermissions(temp_script)
681
682  # Note that this call will mess up the tree of Items, so make sure
683  # we're done with it.
684  source_symlinks = CopySystemFiles(source_zip, None)
685  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
686
687  # Delete all the symlinks in source that aren't in target.  This
688  # needs to happen before verbatim files are unpacked, in case a
689  # symlink in the source is replaced by a real file in the target.
690  to_delete = []
691  for dest, link in source_symlinks:
692    if link not in target_symlinks_d:
693      to_delete.append(link)
694  script.DeleteFiles(to_delete)
695
696  if verbatim_targets:
697    script.Print("Unpacking new files...")
698    script.UnpackPackageDir("system", "/system")
699
700  if updating_recovery:
701    script.Print("Unpacking new recovery...")
702    script.UnpackPackageDir("recovery", "/system")
703
704  script.Print("Symlinks and permissions...")
705
706  # Create all the symlinks that don't already exist, or point to
707  # somewhere different than what we want.  Delete each symlink before
708  # creating it, since the 'symlink' command won't overwrite.
709  to_create = []
710  for dest, link in target_symlinks:
711    if link in source_symlinks_d:
712      if dest != source_symlinks_d[link]:
713        to_create.append((dest, link))
714    else:
715      to_create.append((dest, link))
716  script.DeleteFiles([i[1] for i in to_create])
717  script.MakeSymlinks(to_create)
718
719  # Now that the symlinks are created, we can set all the
720  # permissions.
721  script.AppendScript(temp_script)
722
723  # Do device-specific installation (eg, write radio image).
724  device_specific.IncrementalOTA_InstallEnd()
725
726  if OPTIONS.extra_script is not None:
727    script.AppendExtra(OPTIONS.extra_script)
728
729  # Patch the build.prop file last, so if something fails but the
730  # device can still come up, it appears to be the old build and will
731  # get set the OTA package again to retry.
732  script.Print("Patching remaining system files...")
733  for item in deferred_patch_list:
734    fn, tf, sf, size, _ = item
735    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
736  script.SetPermissions("/system/build.prop", 0, 0, 0644)
737
738  script.AddToZip(target_zip, output_zip)
739  WriteMetadata(metadata, output_zip)
740
741
742def main(argv):
743
744  def option_handler(o, a):
745    if o in ("-b", "--board_config"):
746      pass   # deprecated
747    elif o in ("-k", "--package_key"):
748      OPTIONS.package_key = a
749    elif o in ("-i", "--incremental_from"):
750      OPTIONS.incremental_source = a
751    elif o in ("-w", "--wipe_user_data"):
752      OPTIONS.wipe_user_data = True
753    elif o in ("-n", "--no_prereq"):
754      OPTIONS.omit_prereq = True
755    elif o in ("-e", "--extra_script"):
756      OPTIONS.extra_script = a
757    elif o in ("-a", "--aslr_mode"):
758      if a in ("on", "On", "true", "True", "yes", "Yes"):
759        OPTIONS.aslr_mode = True
760      else:
761        OPTIONS.aslr_mode = False
762    elif o in ("--worker_threads"):
763      OPTIONS.worker_threads = int(a)
764    else:
765      return False
766    return True
767
768  args = common.ParseOptions(argv, __doc__,
769                             extra_opts="b:k:i:d:wne:a:",
770                             extra_long_opts=["board_config=",
771                                              "package_key=",
772                                              "incremental_from=",
773                                              "wipe_user_data",
774                                              "no_prereq",
775                                              "extra_script=",
776                                              "worker_threads=",
777                                              "aslr_mode=",
778                                              ],
779                             extra_option_handler=option_handler)
780
781  if len(args) != 2:
782    common.Usage(__doc__)
783    sys.exit(1)
784
785  if OPTIONS.extra_script is not None:
786    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
787
788  print "unzipping target target-files..."
789  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
790
791  OPTIONS.target_tmp = OPTIONS.input_tmp
792  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
793
794  # If this image was originally labelled with SELinux contexts, make sure we
795  # also apply the labels in our new image. During building, the "file_contexts"
796  # is in the out/ directory tree, but for repacking from target-files.zip it's
797  # in the root directory of the ramdisk.
798  if "selinux_fc" in OPTIONS.info_dict:
799    OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK",
800        "file_contexts")
801
802  if OPTIONS.verbose:
803    print "--- target info ---"
804    common.DumpInfoDict(OPTIONS.info_dict)
805
806  if OPTIONS.device_specific is None:
807    OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
808  if OPTIONS.device_specific is not None:
809    OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
810    print "using device-specific extensions in", OPTIONS.device_specific
811
812  temp_zip_file = tempfile.NamedTemporaryFile()
813  output_zip = zipfile.ZipFile(temp_zip_file, "w",
814                               compression=zipfile.ZIP_DEFLATED)
815
816  if OPTIONS.incremental_source is None:
817    WriteFullOTAPackage(input_zip, output_zip)
818    if OPTIONS.package_key is None:
819      OPTIONS.package_key = OPTIONS.info_dict.get(
820          "default_system_dev_certificate",
821          "build/target/product/security/testkey")
822  else:
823    print "unzipping source target-files..."
824    OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
825    OPTIONS.target_info_dict = OPTIONS.info_dict
826    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
827    if OPTIONS.package_key is None:
828      OPTIONS.package_key = OPTIONS.source_info_dict.get(
829          "default_system_dev_certificate",
830          "build/target/product/security/testkey")
831    if OPTIONS.verbose:
832      print "--- source info ---"
833      common.DumpInfoDict(OPTIONS.source_info_dict)
834    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
835
836  output_zip.close()
837
838  SignOutput(temp_zip_file.name, args[1])
839  temp_zip_file.close()
840
841  common.Cleanup()
842
843  print "done."
844
845
846if __name__ == '__main__':
847  try:
848    common.CloseInheritedPipes()
849    main(sys.argv[1:])
850  except common.ExternalError, e:
851    print
852    print "   ERROR: %s" % (e,)
853    print
854    sys.exit(1)
855