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