• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Given a target-files zipfile, produces an OTA package that installs
19that build.  An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage:  ota_from_target_files [flags] input_target_files output_ota_package
23
24  -b  (--board_config)  <file>
25      Deprecated.
26
27  -k  (--package_key)  <key>
28      Key to use to sign the package (default is
29      "build/target/product/security/testkey").
30
31  -i  (--incremental_from)  <file>
32      Generate an incremental OTA using the given target-files zip as
33      the starting build.
34
35  -w  (--wipe_user_data)
36      Generate an OTA package that will wipe the user data partition
37      when installed.
38
39  -n  (--no_prereq)
40      Omit the timestamp prereq check normally included at the top of
41      the build scripts (used for developer OTA packages which
42      legitimately need to go back and forth).
43
44  -e  (--extra_script)  <file>
45      Insert the contents of file at the end of the update script.
46
47  -m  (--script_mode)  <mode>
48      Specify 'amend' or 'edify' scripts, or 'auto' to pick
49      automatically (this is the default).
50
51"""
52
53import sys
54
55if sys.hexversion < 0x02040000:
56  print >> sys.stderr, "Python 2.4 or newer is required."
57  sys.exit(1)
58
59import copy
60import errno
61import os
62import re
63import sha
64import subprocess
65import tempfile
66import threading
67import time
68import zipfile
69
70import common
71import amend_generator
72import edify_generator
73import both_generator
74
75OPTIONS = common.OPTIONS
76OPTIONS.package_key = "build/target/product/security/testkey"
77OPTIONS.incremental_source = None
78OPTIONS.require_verbatim = set()
79OPTIONS.prohibit_verbatim = set(("system/build.prop",))
80OPTIONS.patch_threshold = 0.95
81OPTIONS.wipe_user_data = False
82OPTIONS.omit_prereq = False
83OPTIONS.extra_script = None
84OPTIONS.script_mode = 'auto'
85OPTIONS.worker_threads = 3
86
87def MostPopularKey(d, default):
88  """Given a dict, return the key corresponding to the largest
89  value.  Returns 'default' if the dict is empty."""
90  x = [(v, k) for (k, v) in d.iteritems()]
91  if not x: return default
92  x.sort()
93  return x[-1][1]
94
95
96def IsSymlink(info):
97  """Return true if the zipfile.ZipInfo object passed in represents a
98  symlink."""
99  return (info.external_attr >> 16) == 0120777
100
101
102
103class Item:
104  """Items represent the metadata (user, group, mode) of files and
105  directories in the system image."""
106  ITEMS = {}
107  def __init__(self, name, dir=False):
108    self.name = name
109    self.uid = None
110    self.gid = None
111    self.mode = None
112    self.dir = dir
113
114    if name:
115      self.parent = Item.Get(os.path.dirname(name), dir=True)
116      self.parent.children.append(self)
117    else:
118      self.parent = None
119    if dir:
120      self.children = []
121
122  def Dump(self, indent=0):
123    if self.uid is not None:
124      print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
125    else:
126      print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
127    if self.dir:
128      print "%s%s" % ("  "*indent, self.descendants)
129      print "%s%s" % ("  "*indent, self.best_subtree)
130      for i in self.children:
131        i.Dump(indent=indent+1)
132
133  @classmethod
134  def Get(cls, name, dir=False):
135    if name not in cls.ITEMS:
136      cls.ITEMS[name] = Item(name, dir=dir)
137    return cls.ITEMS[name]
138
139  @classmethod
140  def GetMetadata(cls):
141    """Run the external 'fs_config' program to determine the desired
142    uid, gid, and mode for every Item object."""
143    p = common.Run(["fs_config"], stdin=subprocess.PIPE,
144                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
145    suffix = { False: "", True: "/" }
146    input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
147                     for i in cls.ITEMS.itervalues() if i.name])
148    output, error = p.communicate(input)
149    assert not error
150
151    for line in output.split("\n"):
152      if not line: continue
153      name, uid, gid, mode = line.split()
154      i = cls.ITEMS[name]
155      i.uid = int(uid)
156      i.gid = int(gid)
157      i.mode = int(mode, 8)
158      if i.dir:
159        i.children.sort(key=lambda i: i.name)
160
161  def CountChildMetadata(self):
162    """Count up the (uid, gid, mode) tuples for all children and
163    determine the best strategy for using set_perm_recursive and
164    set_perm to correctly chown/chmod all the files to their desired
165    values.  Recursively calls itself for all descendants.
166
167    Returns a dict of {(uid, gid, dmode, fmode): count} counting up
168    all descendants of this node.  (dmode or fmode may be None.)  Also
169    sets the best_subtree of each directory Item to the (uid, gid,
170    dmode, fmode) tuple that will match the most descendants of that
171    Item.
172    """
173
174    assert self.dir
175    d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
176    for i in self.children:
177      if i.dir:
178        for k, v in i.CountChildMetadata().iteritems():
179          d[k] = d.get(k, 0) + v
180      else:
181        k = (i.uid, i.gid, None, i.mode)
182        d[k] = d.get(k, 0) + 1
183
184    # Find the (uid, gid, dmode, fmode) tuple that matches the most
185    # descendants.
186
187    # First, find the (uid, gid) pair that matches the most
188    # descendants.
189    ug = {}
190    for (uid, gid, _, _), count in d.iteritems():
191      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
192    ug = MostPopularKey(ug, (0, 0))
193
194    # Now find the dmode and fmode that match the most descendants
195    # with that (uid, gid), and choose those.
196    best_dmode = (0, 0755)
197    best_fmode = (0, 0644)
198    for k, count in d.iteritems():
199      if k[:2] != ug: continue
200      if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
201      if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
202    self.best_subtree = ug + (best_dmode[1], best_fmode[1])
203
204    return d
205
206  def SetPermissions(self, script):
207    """Append set_perm/set_perm_recursive commands to 'script' to
208    set all permissions, users, and groups for the tree of files
209    rooted at 'self'."""
210
211    self.CountChildMetadata()
212
213    def recurse(item, current):
214      # current is the (uid, gid, dmode, fmode) tuple that the current
215      # item (and all its children) have already been set to.  We only
216      # need to issue set_perm/set_perm_recursive commands if we're
217      # supposed to be something different.
218      if item.dir:
219        if current != item.best_subtree:
220          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
221          current = item.best_subtree
222
223        if item.uid != current[0] or item.gid != current[1] or \
224           item.mode != current[2]:
225          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
226
227        for i in item.children:
228          recurse(i, current)
229      else:
230        if item.uid != current[0] or item.gid != current[1] or \
231               item.mode != current[3]:
232          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
233
234    recurse(self, (-1, -1, -1, -1))
235
236
237def CopySystemFiles(input_zip, output_zip=None,
238                    substitute=None):
239  """Copies files underneath system/ in the input zip to the output
240  zip.  Populates the Item class with their metadata, and returns a
241  list of symlinks.  output_zip may be None, in which case the copy is
242  skipped (but the other side effects still happen).  substitute is an
243  optional dict of {output filename: contents} to be output instead of
244  certain input files.
245  """
246
247  symlinks = []
248
249  for info in input_zip.infolist():
250    if info.filename.startswith("SYSTEM/"):
251      basefilename = info.filename[7:]
252      if IsSymlink(info):
253        symlinks.append((input_zip.read(info.filename),
254                         "/system/" + basefilename))
255      else:
256        info2 = copy.copy(info)
257        fn = info2.filename = "system/" + basefilename
258        if substitute and fn in substitute and substitute[fn] is None:
259          continue
260        if output_zip is not None:
261          if substitute and fn in substitute:
262            data = substitute[fn]
263          else:
264            data = input_zip.read(info.filename)
265          output_zip.writestr(info2, data)
266        if fn.endswith("/"):
267          Item.Get(fn[:-1], dir=True)
268        else:
269          Item.Get(fn, dir=False)
270
271  symlinks.sort()
272  return symlinks
273
274
275def SignOutput(temp_zip_name, output_zip_name):
276  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
277  pw = key_passwords[OPTIONS.package_key]
278
279  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
280                  whole_file=True)
281
282
283def AppendAssertions(script, input_zip):
284  device = GetBuildProp("ro.product.device", input_zip)
285  script.AssertDevice(device)
286
287
288def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
289  """Generate a binary patch that creates the recovery image starting
290  with the boot image.  (Most of the space in these images is just the
291  kernel, which is identical for the two, so the resulting patch
292  should be efficient.)  Add it to the output zip, along with a shell
293  script that is run from init.rc on first boot to actually do the
294  patching and install the new recovery image.
295
296  recovery_img and boot_img should be File objects for the
297  corresponding images.
298
299  Returns an Item for the shell script, which must be made
300  executable.
301  """
302
303  d = Difference(recovery_img, boot_img)
304  _, _, patch = d.ComputePatch()
305  common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
306  Item.Get("system/recovery-from-boot.p", dir=False)
307
308  # Images with different content will have a different first page, so
309  # we check to see if this recovery has already been installed by
310  # testing just the first 2k.
311  HEADER_SIZE = 2048
312  header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
313  sh = """#!/system/bin/sh
314if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
315  log -t recovery "Installing new recovery image"
316  applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
317else
318  log -t recovery "Recovery image already installed"
319fi
320""" % { 'boot_size': boot_img.size,
321        'boot_sha1': boot_img.sha1,
322        'header_size': HEADER_SIZE,
323        'header_sha1': header_sha1,
324        'recovery_size': recovery_img.size,
325        'recovery_sha1': recovery_img.sha1 }
326  common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
327  return Item.Get("system/etc/install-recovery.sh", dir=False)
328
329
330def WriteFullOTAPackage(input_zip, output_zip):
331  if OPTIONS.script_mode == "auto":
332    script = both_generator.BothGenerator(2)
333  elif OPTIONS.script_mode == "amend":
334    script = amend_generator.AmendGenerator()
335  else:
336    # TODO: how to determine this?  We don't know what version it will
337    # be installed on top of.  For now, we expect the API just won't
338    # change very often.
339    script = edify_generator.EdifyGenerator(2)
340
341  device_specific = common.DeviceSpecificParams(
342      input_zip=input_zip,
343      output_zip=output_zip,
344      script=script,
345      input_tmp=OPTIONS.input_tmp)
346
347  if not OPTIONS.omit_prereq:
348    ts = GetBuildProp("ro.build.date.utc", input_zip)
349    script.AssertOlderBuild(ts)
350
351  AppendAssertions(script, input_zip)
352  device_specific.FullOTA_Assertions()
353
354  script.ShowProgress(0.5, 0)
355
356  if OPTIONS.wipe_user_data:
357    script.FormatPartition("userdata")
358
359  script.FormatPartition("system")
360  script.Mount("MTD", "system", "/system")
361  script.UnpackPackageDir("recovery", "/system")
362  script.UnpackPackageDir("system", "/system")
363
364  symlinks = CopySystemFiles(input_zip, output_zip)
365  script.MakeSymlinks(symlinks)
366
367  boot_img = File("boot.img", common.BuildBootableImage(
368      os.path.join(OPTIONS.input_tmp, "BOOT")))
369  recovery_img = File("recovery.img", common.BuildBootableImage(
370      os.path.join(OPTIONS.input_tmp, "RECOVERY")))
371  i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
372
373  Item.GetMetadata()
374
375  # GetMetadata uses the data in android_filesystem_config.h to assign
376  # the uid/gid/mode of all files.  We want to override that for the
377  # recovery patching shell script to make it executable.
378  i.uid = 0
379  i.gid = 0
380  i.mode = 0544
381  Item.Get("system").SetPermissions(script)
382
383  common.CheckSize(boot_img.data, "boot.img")
384  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
385  script.ShowProgress(0.2, 0)
386
387  script.ShowProgress(0.2, 10)
388  script.WriteRawImage("boot", "boot.img")
389
390  script.ShowProgress(0.1, 0)
391  device_specific.FullOTA_InstallEnd()
392
393  if OPTIONS.extra_script is not None:
394    script.AppendExtra(OPTIONS.extra_script)
395
396  script.AddToZip(input_zip, output_zip)
397
398
399class File(object):
400  def __init__(self, name, data):
401    self.name = name
402    self.data = data
403    self.size = len(data)
404    self.sha1 = sha.sha(data).hexdigest()
405
406  def WriteToTemp(self):
407    t = tempfile.NamedTemporaryFile()
408    t.write(self.data)
409    t.flush()
410    return t
411
412  def AddToZip(self, z):
413    common.ZipWriteStr(z, self.name, self.data)
414
415
416def LoadSystemFiles(z):
417  """Load all the files from SYSTEM/... in a given target-files
418  ZipFile, and return a dict of {filename: File object}."""
419  out = {}
420  for info in z.infolist():
421    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
422      fn = "system/" + info.filename[7:]
423      data = z.read(info.filename)
424      out[fn] = File(fn, data)
425  return out
426
427
428DIFF_PROGRAM_BY_EXT = {
429    ".gz" : "imgdiff",
430    ".zip" : ["imgdiff", "-z"],
431    ".jar" : ["imgdiff", "-z"],
432    ".apk" : ["imgdiff", "-z"],
433    ".img" : "imgdiff",
434    }
435
436
437class Difference(object):
438  def __init__(self, tf, sf):
439    self.tf = tf
440    self.sf = sf
441    self.patch = None
442
443  def ComputePatch(self):
444    """Compute the patch (as a string of data) needed to turn sf into
445    tf.  Returns the same tuple as GetPatch()."""
446
447    tf = self.tf
448    sf = self.sf
449
450    ext = os.path.splitext(tf.name)[1]
451    diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
452
453    ttemp = tf.WriteToTemp()
454    stemp = sf.WriteToTemp()
455
456    ext = os.path.splitext(tf.name)[1]
457
458    try:
459      ptemp = tempfile.NamedTemporaryFile()
460      if isinstance(diff_program, list):
461        cmd = copy.copy(diff_program)
462      else:
463        cmd = [diff_program]
464      cmd.append(stemp.name)
465      cmd.append(ttemp.name)
466      cmd.append(ptemp.name)
467      p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
468      _, err = p.communicate()
469      if err or p.returncode != 0:
470        print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
471        return None
472      diff = ptemp.read()
473    finally:
474      ptemp.close()
475      stemp.close()
476      ttemp.close()
477
478    self.patch = diff
479    return self.tf, self.sf, self.patch
480
481
482  def GetPatch(self):
483    """Return a tuple (target_file, source_file, patch_data).
484    patch_data may be None if ComputePatch hasn't been called, or if
485    computing the patch failed."""
486    return self.tf, self.sf, self.patch
487
488
489def ComputeDifferences(diffs):
490  """Call ComputePatch on all the Difference objects in 'diffs'."""
491  print len(diffs), "diffs to compute"
492
493  # Do the largest files first, to try and reduce the long-pole effect.
494  by_size = [(i.tf.size, i) for i in diffs]
495  by_size.sort(reverse=True)
496  by_size = [i[1] for i in by_size]
497
498  lock = threading.Lock()
499  diff_iter = iter(by_size)   # accessed under lock
500
501  def worker():
502    try:
503      lock.acquire()
504      for d in diff_iter:
505        lock.release()
506        start = time.time()
507        d.ComputePatch()
508        dur = time.time() - start
509        lock.acquire()
510
511        tf, sf, patch = d.GetPatch()
512        if sf.name == tf.name:
513          name = tf.name
514        else:
515          name = "%s (%s)" % (tf.name, sf.name)
516        if patch is None:
517          print "patching failed!                                  %s" % (name,)
518        else:
519          print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
520              dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
521      lock.release()
522    except Exception, e:
523      print e
524      raise
525
526  # start worker threads; wait for them all to finish.
527  threads = [threading.Thread(target=worker)
528             for i in range(OPTIONS.worker_threads)]
529  for th in threads:
530    th.start()
531  while threads:
532    threads.pop().join()
533
534
535def GetBuildProp(property, z):
536  """Return the fingerprint of the build of a given target-files
537  ZipFile object."""
538  bp = z.read("SYSTEM/build.prop")
539  if not property:
540    return bp
541  m = re.search(re.escape(property) + r"=(.*)\n", bp)
542  if not m:
543    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
544  return m.group(1).strip()
545
546
547def GetRecoveryAPIVersion(zip):
548  """Returns the version of the recovery API.  Version 0 is the older
549  amend code (no separate binary)."""
550  try:
551    version = zip.read("META/recovery-api-version.txt")
552    return int(version)
553  except KeyError:
554    try:
555      # version one didn't have the recovery-api-version.txt file, but
556      # it did include an updater binary.
557      zip.getinfo("OTA/bin/updater")
558      return 1
559    except KeyError:
560      return 0
561
562
563def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
564  source_version = GetRecoveryAPIVersion(source_zip)
565
566  if OPTIONS.script_mode == 'amend':
567    script = amend_generator.AmendGenerator()
568  elif OPTIONS.script_mode == 'edify':
569    if source_version == 0:
570      print ("WARNING: generating edify script for a source that "
571             "can't install it.")
572    script = edify_generator.EdifyGenerator(source_version)
573  elif OPTIONS.script_mode == 'auto':
574    if source_version > 0:
575      script = edify_generator.EdifyGenerator(source_version)
576    else:
577      script = amend_generator.AmendGenerator()
578  else:
579    raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
580
581  device_specific = common.DeviceSpecificParams(
582      source_zip=source_zip,
583      target_zip=target_zip,
584      output_zip=output_zip,
585      script=script)
586
587  print "Loading target..."
588  target_data = LoadSystemFiles(target_zip)
589  print "Loading source..."
590  source_data = LoadSystemFiles(source_zip)
591
592  verbatim_targets = []
593  patch_list = []
594  diffs = []
595  largest_source_size = 0
596  for fn in sorted(target_data.keys()):
597    tf = target_data[fn]
598    assert fn == tf.name
599    sf = source_data.get(fn, None)
600
601    if sf is None or fn in OPTIONS.require_verbatim:
602      # This file should be included verbatim
603      if fn in OPTIONS.prohibit_verbatim:
604        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
605      print "send", fn, "verbatim"
606      tf.AddToZip(output_zip)
607      verbatim_targets.append((fn, tf.size))
608    elif tf.sha1 != sf.sha1:
609      # File is different; consider sending as a patch
610      diffs.append(Difference(tf, sf))
611    else:
612      # Target file identical to source.
613      pass
614
615  ComputeDifferences(diffs)
616
617  for diff in diffs:
618    tf, sf, d = diff.GetPatch()
619    if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
620      # patch is almost as big as the file; don't bother patching
621      tf.AddToZip(output_zip)
622      verbatim_targets.append((tf.name, tf.size))
623    else:
624      common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
625      patch_list.append((tf.name, tf, sf, tf.size))
626      largest_source_size = max(largest_source_size, sf.size)
627
628  source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
629  target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
630
631  script.Mount("MTD", "system", "/system")
632  script.AssertSomeFingerprint(source_fp, target_fp)
633
634  source_boot = File("/tmp/boot.img",
635                     common.BuildBootableImage(
636      os.path.join(OPTIONS.source_tmp, "BOOT")))
637  target_boot = File("/tmp/boot.img",
638                     common.BuildBootableImage(
639      os.path.join(OPTIONS.target_tmp, "BOOT")))
640  updating_boot = (source_boot.data != target_boot.data)
641
642  source_recovery = File("system/recovery.img",
643                         common.BuildBootableImage(
644      os.path.join(OPTIONS.source_tmp, "RECOVERY")))
645  target_recovery = File("system/recovery.img",
646                         common.BuildBootableImage(
647      os.path.join(OPTIONS.target_tmp, "RECOVERY")))
648  updating_recovery = (source_recovery.data != target_recovery.data)
649
650  # Here's how we divide up the progress bar:
651  #  0.1 for verifying the start state (PatchCheck calls)
652  #  0.8 for applying patches (ApplyPatch calls)
653  #  0.1 for unpacking verbatim files, symlinking, and doing the
654  #      device-specific commands.
655
656  AppendAssertions(script, target_zip)
657  device_specific.IncrementalOTA_Assertions()
658
659  script.Print("Verifying current system...")
660
661  script.ShowProgress(0.1, 0)
662  total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
663  if updating_boot:
664    total_verify_size += source_boot.size
665  so_far = 0
666
667  for fn, tf, sf, size in patch_list:
668    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
669    so_far += sf.size
670    script.SetProgress(so_far / total_verify_size)
671
672  if updating_boot:
673    d = Difference(target_boot, source_boot)
674    _, _, d = d.ComputePatch()
675    print "boot      target: %d  source: %d  diff: %d" % (
676        target_boot.size, source_boot.size, len(d))
677
678    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
679
680    script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
681                      (source_boot.size, source_boot.sha1,
682                       target_boot.size, target_boot.sha1))
683    so_far += source_boot.size
684    script.SetProgress(so_far / total_verify_size)
685
686  if patch_list or updating_recovery or updating_boot:
687    script.CacheFreeSpaceCheck(largest_source_size)
688    script.Print("Unpacking patches...")
689    script.UnpackPackageDir("patch", "/tmp/patchtmp")
690
691  device_specific.IncrementalOTA_VerifyEnd()
692
693  script.Comment("---- start making changes here ----")
694
695  if OPTIONS.wipe_user_data:
696    script.Print("Erasing user data...")
697    script.FormatPartition("userdata")
698
699  script.Print("Removing unneeded files...")
700  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
701                     ["/"+i for i in sorted(source_data)
702                            if i not in target_data] +
703                     ["/system/recovery.img"])
704
705  script.ShowProgress(0.8, 0)
706  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
707  if updating_boot:
708    total_patch_size += target_boot.size
709  so_far = 0
710
711  script.Print("Patching system files...")
712  for fn, tf, sf, size in patch_list:
713    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
714                      sf.sha1, "/tmp/patchtmp/"+fn+".p")
715    so_far += tf.size
716    script.SetProgress(so_far / total_patch_size)
717
718  if updating_boot:
719    # Produce the boot image by applying a patch to the current
720    # contents of the boot partition, and write it back to the
721    # partition.
722    script.Print("Patching boot image...")
723    script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
724                      % (source_boot.size, source_boot.sha1,
725                         target_boot.size, target_boot.sha1),
726                      "-",
727                      target_boot.size, target_boot.sha1,
728                      source_boot.sha1, "/tmp/patchtmp/boot.img.p")
729    so_far += target_boot.size
730    script.SetProgress(so_far / total_patch_size)
731    print "boot image changed; including."
732  else:
733    print "boot image unchanged; skipping."
734
735  if updating_recovery:
736    # Is it better to generate recovery as a patch from the current
737    # boot image, or from the previous recovery image?  For large
738    # updates with significant kernel changes, probably the former.
739    # For small updates where the kernel hasn't changed, almost
740    # certainly the latter.  We pick the first option.  Future
741    # complicated schemes may let us effectively use both.
742    #
743    # A wacky possibility: as long as there is room in the boot
744    # partition, include the binaries and image files from recovery in
745    # the boot image (though not in the ramdisk) so they can be used
746    # as fodder for constructing the recovery image.
747    recovery_sh_item = MakeRecoveryPatch(output_zip,
748                                         target_recovery, target_boot)
749    script.UnpackPackageDir("recovery", "/system")
750    print "recovery image changed; including as patch from boot."
751  else:
752    print "recovery image unchanged; skipping."
753
754  script.ShowProgress(0.1, 10)
755
756  target_symlinks = CopySystemFiles(target_zip, None)
757
758  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
759  temp_script = script.MakeTemporary()
760  Item.GetMetadata()
761  if updating_recovery:
762    recovery_sh_item.uid = 0
763    recovery_sh_item.gid = 0
764    recovery_sh_item.mode = 0544
765  Item.Get("system").SetPermissions(temp_script)
766
767  # Note that this call will mess up the tree of Items, so make sure
768  # we're done with it.
769  source_symlinks = CopySystemFiles(source_zip, None)
770  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
771
772  # Delete all the symlinks in source that aren't in target.  This
773  # needs to happen before verbatim files are unpacked, in case a
774  # symlink in the source is replaced by a real file in the target.
775  to_delete = []
776  for dest, link in source_symlinks:
777    if link not in target_symlinks_d:
778      to_delete.append(link)
779  script.DeleteFiles(to_delete)
780
781  if verbatim_targets:
782    script.Print("Unpacking new files...")
783    script.UnpackPackageDir("system", "/system")
784
785  script.Print("Symlinks and permissions...")
786
787  # Create all the symlinks that don't already exist, or point to
788  # somewhere different than what we want.  Delete each symlink before
789  # creating it, since the 'symlink' command won't overwrite.
790  to_create = []
791  for dest, link in target_symlinks:
792    if link in source_symlinks_d:
793      if dest != source_symlinks_d[link]:
794        to_create.append((dest, link))
795    else:
796      to_create.append((dest, link))
797  script.DeleteFiles([i[1] for i in to_create])
798  script.MakeSymlinks(to_create)
799
800  # Now that the symlinks are created, we can set all the
801  # permissions.
802  script.AppendScript(temp_script)
803
804  # Do device-specific installation (eg, write radio image).
805  device_specific.IncrementalOTA_InstallEnd()
806
807  if OPTIONS.extra_script is not None:
808    scirpt.AppendExtra(OPTIONS.extra_script)
809
810  script.AddToZip(target_zip, output_zip)
811
812
813def main(argv):
814
815  def option_handler(o, a):
816    if o in ("-b", "--board_config"):
817      pass   # deprecated
818    elif o in ("-k", "--package_key"):
819      OPTIONS.package_key = a
820    elif o in ("-i", "--incremental_from"):
821      OPTIONS.incremental_source = a
822    elif o in ("-w", "--wipe_user_data"):
823      OPTIONS.wipe_user_data = True
824    elif o in ("-n", "--no_prereq"):
825      OPTIONS.omit_prereq = True
826    elif o in ("-e", "--extra_script"):
827      OPTIONS.extra_script = a
828    elif o in ("-m", "--script_mode"):
829      OPTIONS.script_mode = a
830    elif o in ("--worker_threads"):
831      OPTIONS.worker_threads = int(a)
832    else:
833      return False
834    return True
835
836  args = common.ParseOptions(argv, __doc__,
837                             extra_opts="b:k:i:d:wne:m:",
838                             extra_long_opts=["board_config=",
839                                              "package_key=",
840                                              "incremental_from=",
841                                              "wipe_user_data",
842                                              "no_prereq",
843                                              "extra_script=",
844                                              "script_mode=",
845                                              "worker_threads="],
846                             extra_option_handler=option_handler)
847
848  if len(args) != 2:
849    common.Usage(__doc__)
850    sys.exit(1)
851
852  if OPTIONS.script_mode not in ("amend", "edify", "auto"):
853    raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
854
855  if OPTIONS.extra_script is not None:
856    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
857
858  print "unzipping target target-files..."
859  OPTIONS.input_tmp = common.UnzipTemp(args[0])
860
861  if OPTIONS.device_specific is None:
862    # look for the device-specific tools extension location in the input
863    try:
864      f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
865      ds = f.read().strip()
866      f.close()
867      if ds:
868        ds = os.path.normpath(ds)
869        print "using device-specific extensions in", ds
870        OPTIONS.device_specific = ds
871    except IOError, e:
872      if e.errno == errno.ENOENT:
873        # nothing specified in the file
874        pass
875      else:
876        raise
877
878  common.LoadMaxSizes()
879  if not OPTIONS.max_image_size:
880    print
881    print "  WARNING:  Failed to load max image sizes; will not enforce"
882    print "  image size limits."
883    print
884
885  OPTIONS.target_tmp = OPTIONS.input_tmp
886  input_zip = zipfile.ZipFile(args[0], "r")
887  if OPTIONS.package_key:
888    temp_zip_file = tempfile.NamedTemporaryFile()
889    output_zip = zipfile.ZipFile(temp_zip_file, "w",
890                                 compression=zipfile.ZIP_DEFLATED)
891  else:
892    output_zip = zipfile.ZipFile(args[1], "w",
893                                 compression=zipfile.ZIP_DEFLATED)
894
895  if OPTIONS.incremental_source is None:
896    WriteFullOTAPackage(input_zip, output_zip)
897  else:
898    print "unzipping source target-files..."
899    OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
900    source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
901    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
902
903  output_zip.close()
904  if OPTIONS.package_key:
905    SignOutput(temp_zip_file.name, args[1])
906    temp_zip_file.close()
907
908  common.Cleanup()
909
910  print "done."
911
912
913if __name__ == '__main__':
914  try:
915    main(sys.argv[1:])
916  except common.ExternalError, e:
917    print
918    print "   ERROR: %s" % (e,)
919    print
920    sys.exit(1)
921