• 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"""
48
49import sys
50
51if sys.hexversion < 0x02040000:
52  print >> sys.stderr, "Python 2.4 or newer is required."
53  sys.exit(1)
54
55import copy
56import errno
57import os
58import re
59import sha
60import subprocess
61import tempfile
62import time
63import zipfile
64
65import common
66import edify_generator
67
68OPTIONS = common.OPTIONS
69OPTIONS.package_key = "build/target/product/security/testkey"
70OPTIONS.incremental_source = None
71OPTIONS.require_verbatim = set()
72OPTIONS.prohibit_verbatim = set(("system/build.prop",))
73OPTIONS.patch_threshold = 0.95
74OPTIONS.wipe_user_data = False
75OPTIONS.omit_prereq = False
76OPTIONS.extra_script = None
77OPTIONS.worker_threads = 3
78
79def MostPopularKey(d, default):
80  """Given a dict, return the key corresponding to the largest
81  value.  Returns 'default' if the dict is empty."""
82  x = [(v, k) for (k, v) in d.iteritems()]
83  if not x: return default
84  x.sort()
85  return x[-1][1]
86
87
88def IsSymlink(info):
89  """Return true if the zipfile.ZipInfo object passed in represents a
90  symlink."""
91  return (info.external_attr >> 16) == 0120777
92
93
94class Item:
95  """Items represent the metadata (user, group, mode) of files and
96  directories in the system image."""
97  ITEMS = {}
98  def __init__(self, name, dir=False):
99    self.name = name
100    self.uid = None
101    self.gid = None
102    self.mode = None
103    self.dir = dir
104
105    if name:
106      self.parent = Item.Get(os.path.dirname(name), dir=True)
107      self.parent.children.append(self)
108    else:
109      self.parent = None
110    if dir:
111      self.children = []
112
113  def Dump(self, indent=0):
114    if self.uid is not None:
115      print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
116    else:
117      print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
118    if self.dir:
119      print "%s%s" % ("  "*indent, self.descendants)
120      print "%s%s" % ("  "*indent, self.best_subtree)
121      for i in self.children:
122        i.Dump(indent=indent+1)
123
124  @classmethod
125  def Get(cls, name, dir=False):
126    if name not in cls.ITEMS:
127      cls.ITEMS[name] = Item(name, dir=dir)
128    return cls.ITEMS[name]
129
130  @classmethod
131  def GetMetadata(cls, input_zip):
132
133    try:
134      # See if the target_files contains a record of what the uid,
135      # gid, and mode is supposed to be.
136      output = input_zip.read("META/filesystem_config.txt")
137    except KeyError:
138      # Run the external 'fs_config' program to determine the desired
139      # uid, gid, and mode for every Item object.  Note this uses the
140      # one in the client now, which might not be the same as the one
141      # used when this target_files was built.
142      p = common.Run(["fs_config"], stdin=subprocess.PIPE,
143                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144      suffix = { False: "", True: "/" }
145      input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
146                       for i in cls.ITEMS.itervalues() if i.name])
147      output, error = p.communicate(input)
148      assert not error
149
150    for line in output.split("\n"):
151      if not line: continue
152      name, uid, gid, mode = line.split()
153      i = cls.ITEMS.get(name, None)
154      if i is not None:
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    # set metadata for the files generated by this script.
162    i = cls.ITEMS.get("system/recovery-from-boot.p", None)
163    if i: i.uid, i.gid, i.mode = 0, 0, 0644
164    i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
165    if i: i.uid, i.gid, i.mode = 0, 0, 0544
166
167  def CountChildMetadata(self):
168    """Count up the (uid, gid, mode) tuples for all children and
169    determine the best strategy for using set_perm_recursive and
170    set_perm to correctly chown/chmod all the files to their desired
171    values.  Recursively calls itself for all descendants.
172
173    Returns a dict of {(uid, gid, dmode, fmode): count} counting up
174    all descendants of this node.  (dmode or fmode may be None.)  Also
175    sets the best_subtree of each directory Item to the (uid, gid,
176    dmode, fmode) tuple that will match the most descendants of that
177    Item.
178    """
179
180    assert self.dir
181    d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
182    for i in self.children:
183      if i.dir:
184        for k, v in i.CountChildMetadata().iteritems():
185          d[k] = d.get(k, 0) + v
186      else:
187        k = (i.uid, i.gid, None, i.mode)
188        d[k] = d.get(k, 0) + 1
189
190    # Find the (uid, gid, dmode, fmode) tuple that matches the most
191    # descendants.
192
193    # First, find the (uid, gid) pair that matches the most
194    # descendants.
195    ug = {}
196    for (uid, gid, _, _), count in d.iteritems():
197      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
198    ug = MostPopularKey(ug, (0, 0))
199
200    # Now find the dmode and fmode that match the most descendants
201    # with that (uid, gid), and choose those.
202    best_dmode = (0, 0755)
203    best_fmode = (0, 0644)
204    for k, count in d.iteritems():
205      if k[:2] != ug: continue
206      if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
207      if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
208    self.best_subtree = ug + (best_dmode[1], best_fmode[1])
209
210    return d
211
212  def SetPermissions(self, script):
213    """Append set_perm/set_perm_recursive commands to 'script' to
214    set all permissions, users, and groups for the tree of files
215    rooted at 'self'."""
216
217    self.CountChildMetadata()
218
219    def recurse(item, current):
220      # current is the (uid, gid, dmode, fmode) tuple that the current
221      # item (and all its children) have already been set to.  We only
222      # need to issue set_perm/set_perm_recursive commands if we're
223      # supposed to be something different.
224      if item.dir:
225        if current != item.best_subtree:
226          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
227          current = item.best_subtree
228
229        if item.uid != current[0] or item.gid != current[1] or \
230           item.mode != current[2]:
231          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
232
233        for i in item.children:
234          recurse(i, current)
235      else:
236        if item.uid != current[0] or item.gid != current[1] or \
237               item.mode != current[3]:
238          script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
239
240    recurse(self, (-1, -1, -1, -1))
241
242
243def CopySystemFiles(input_zip, output_zip=None,
244                    substitute=None):
245  """Copies files underneath system/ in the input zip to the output
246  zip.  Populates the Item class with their metadata, and returns a
247  list of symlinks.  output_zip may be None, in which case the copy is
248  skipped (but the other side effects still happen).  substitute is an
249  optional dict of {output filename: contents} to be output instead of
250  certain input files.
251  """
252
253  symlinks = []
254
255  for info in input_zip.infolist():
256    if info.filename.startswith("SYSTEM/"):
257      basefilename = info.filename[7:]
258      if IsSymlink(info):
259        symlinks.append((input_zip.read(info.filename),
260                         "/system/" + basefilename))
261      else:
262        info2 = copy.copy(info)
263        fn = info2.filename = "system/" + basefilename
264        if substitute and fn in substitute and substitute[fn] is None:
265          continue
266        if output_zip is not None:
267          if substitute and fn in substitute:
268            data = substitute[fn]
269          else:
270            data = input_zip.read(info.filename)
271          output_zip.writestr(info2, data)
272        if fn.endswith("/"):
273          Item.Get(fn[:-1], dir=True)
274        else:
275          Item.Get(fn, dir=False)
276
277  symlinks.sort()
278  return symlinks
279
280
281def SignOutput(temp_zip_name, output_zip_name):
282  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
283  pw = key_passwords[OPTIONS.package_key]
284
285  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
286                  whole_file=True)
287
288
289def AppendAssertions(script, input_zip):
290  device = GetBuildProp("ro.product.device", input_zip)
291  script.AssertDevice(device)
292
293
294def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
295  """Generate a binary patch that creates the recovery image starting
296  with the boot image.  (Most of the space in these images is just the
297  kernel, which is identical for the two, so the resulting patch
298  should be efficient.)  Add it to the output zip, along with a shell
299  script that is run from init.rc on first boot to actually do the
300  patching and install the new recovery image.
301
302  recovery_img and boot_img should be File objects for the
303  corresponding images.
304
305  Returns an Item for the shell script, which must be made
306  executable.
307  """
308
309  d = common.Difference(recovery_img, boot_img)
310  _, _, patch = d.ComputePatch()
311  common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
312  Item.Get("system/recovery-from-boot.p", dir=False)
313
314  boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
315  recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
316
317  # Images with different content will have a different first page, so
318  # we check to see if this recovery has already been installed by
319  # testing just the first 2k.
320  HEADER_SIZE = 2048
321  header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
322  sh = """#!/system/bin/sh
323if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then
324  log -t recovery "Installing new recovery image"
325  applypatch %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
326else
327  log -t recovery "Recovery image already installed"
328fi
329""" % { 'boot_size': boot_img.size,
330        'boot_sha1': boot_img.sha1,
331        'header_size': HEADER_SIZE,
332        'header_sha1': header_sha1,
333        'recovery_size': recovery_img.size,
334        'recovery_sha1': recovery_img.sha1,
335        'boot_type': boot_type,
336        'boot_device': boot_device,
337        'recovery_type': recovery_type,
338        'recovery_device': recovery_device,
339        }
340  common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
341  return Item.Get("system/etc/install-recovery.sh", dir=False)
342
343
344def WriteFullOTAPackage(input_zip, output_zip):
345  # TODO: how to determine this?  We don't know what version it will
346  # be installed on top of.  For now, we expect the API just won't
347  # change very often.
348  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
349
350  metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
351              "pre-device": GetBuildProp("ro.product.device", input_zip),
352              "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
353              }
354
355  device_specific = common.DeviceSpecificParams(
356      input_zip=input_zip,
357      input_version=OPTIONS.info_dict["recovery_api_version"],
358      output_zip=output_zip,
359      script=script,
360      input_tmp=OPTIONS.input_tmp,
361      metadata=metadata,
362      info_dict=OPTIONS.info_dict)
363
364  if not OPTIONS.omit_prereq:
365    ts = GetBuildProp("ro.build.date.utc", input_zip)
366    script.AssertOlderBuild(ts)
367
368  AppendAssertions(script, input_zip)
369  device_specific.FullOTA_Assertions()
370
371  script.ShowProgress(0.5, 0)
372
373  if OPTIONS.wipe_user_data:
374    script.FormatPartition("/data")
375
376  script.FormatPartition("/system")
377  script.Mount("/system")
378  script.UnpackPackageDir("recovery", "/system")
379  script.UnpackPackageDir("system", "/system")
380
381  symlinks = CopySystemFiles(input_zip, output_zip)
382  script.MakeSymlinks(symlinks)
383
384  boot_img = common.File("boot.img", common.BuildBootableImage(
385      os.path.join(OPTIONS.input_tmp, "BOOT")))
386  recovery_img = common.File("recovery.img", common.BuildBootableImage(
387      os.path.join(OPTIONS.input_tmp, "RECOVERY")))
388  MakeRecoveryPatch(output_zip, recovery_img, boot_img)
389
390  Item.GetMetadata(input_zip)
391  Item.Get("system").SetPermissions(script)
392
393  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
394  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
395  script.ShowProgress(0.2, 0)
396
397  script.ShowProgress(0.2, 10)
398  script.WriteRawImage("/boot", "boot.img")
399
400  script.ShowProgress(0.1, 0)
401  device_specific.FullOTA_InstallEnd()
402
403  if OPTIONS.extra_script is not None:
404    script.AppendExtra(OPTIONS.extra_script)
405
406  script.UnmountAll()
407  script.AddToZip(input_zip, output_zip)
408  WriteMetadata(metadata, output_zip)
409
410
411def WriteMetadata(metadata, output_zip):
412  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
413                     "".join(["%s=%s\n" % kv
414                              for kv in sorted(metadata.iteritems())]))
415
416
417
418
419def LoadSystemFiles(z):
420  """Load all the files from SYSTEM/... in a given target-files
421  ZipFile, and return a dict of {filename: File object}."""
422  out = {}
423  for info in z.infolist():
424    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
425      fn = "system/" + info.filename[7:]
426      data = z.read(info.filename)
427      out[fn] = common.File(fn, data)
428  return out
429
430
431def GetBuildProp(property, z):
432  """Return the fingerprint of the build of a given target-files
433  ZipFile object."""
434  bp = z.read("SYSTEM/build.prop")
435  if not property:
436    return bp
437  m = re.search(re.escape(property) + r"=(.*)\n", bp)
438  if not m:
439    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
440  return m.group(1).strip()
441
442
443def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
444  source_version = OPTIONS.source_info_dict["recovery_api_version"]
445  target_version = OPTIONS.target_info_dict["recovery_api_version"]
446
447  if source_version == 0:
448    print ("WARNING: generating edify script for a source that "
449           "can't install it.")
450  script = edify_generator.EdifyGenerator(source_version, OPTIONS.info_dict)
451
452  metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
453              "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
454              }
455
456  device_specific = common.DeviceSpecificParams(
457      source_zip=source_zip,
458      source_version=source_version,
459      target_zip=target_zip,
460      target_version=target_version,
461      output_zip=output_zip,
462      script=script,
463      metadata=metadata,
464      info_dict=OPTIONS.info_dict)
465
466  print "Loading target..."
467  target_data = LoadSystemFiles(target_zip)
468  print "Loading source..."
469  source_data = LoadSystemFiles(source_zip)
470
471  verbatim_targets = []
472  patch_list = []
473  diffs = []
474  largest_source_size = 0
475  for fn in sorted(target_data.keys()):
476    tf = target_data[fn]
477    assert fn == tf.name
478    sf = source_data.get(fn, None)
479
480    if sf is None or fn in OPTIONS.require_verbatim:
481      # This file should be included verbatim
482      if fn in OPTIONS.prohibit_verbatim:
483        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
484      print "send", fn, "verbatim"
485      tf.AddToZip(output_zip)
486      verbatim_targets.append((fn, tf.size))
487    elif tf.sha1 != sf.sha1:
488      # File is different; consider sending as a patch
489      diffs.append(common.Difference(tf, sf))
490    else:
491      # Target file identical to source.
492      pass
493
494  common.ComputeDifferences(diffs)
495
496  for diff in diffs:
497    tf, sf, d = diff.GetPatch()
498    if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
499      # patch is almost as big as the file; don't bother patching
500      tf.AddToZip(output_zip)
501      verbatim_targets.append((tf.name, tf.size))
502    else:
503      common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
504      patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
505      largest_source_size = max(largest_source_size, sf.size)
506
507  source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
508  target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
509  metadata["pre-build"] = source_fp
510  metadata["post-build"] = target_fp
511
512  script.Mount("/system")
513  script.AssertSomeFingerprint(source_fp, target_fp)
514
515  source_boot = common.File("/tmp/boot.img",
516                            common.BuildBootableImage(
517                                os.path.join(OPTIONS.source_tmp, "BOOT")))
518  target_boot = common.File("/tmp/boot.img",
519                            common.BuildBootableImage(
520                                os.path.join(OPTIONS.target_tmp, "BOOT")))
521  updating_boot = (source_boot.data != target_boot.data)
522
523  source_recovery = common.File("system/recovery.img",
524                                common.BuildBootableImage(
525                                    os.path.join(OPTIONS.source_tmp, "RECOVERY")))
526  target_recovery = common.File("system/recovery.img",
527                                common.BuildBootableImage(
528                                    os.path.join(OPTIONS.target_tmp, "RECOVERY")))
529  updating_recovery = (source_recovery.data != target_recovery.data)
530
531  # Here's how we divide up the progress bar:
532  #  0.1 for verifying the start state (PatchCheck calls)
533  #  0.8 for applying patches (ApplyPatch calls)
534  #  0.1 for unpacking verbatim files, symlinking, and doing the
535  #      device-specific commands.
536
537  AppendAssertions(script, target_zip)
538  device_specific.IncrementalOTA_Assertions()
539
540  script.Print("Verifying current system...")
541
542  script.ShowProgress(0.1, 0)
543  total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
544  if updating_boot:
545    total_verify_size += source_boot.size
546  so_far = 0
547
548  for fn, tf, sf, size, patch_sha in patch_list:
549    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
550    so_far += sf.size
551    script.SetProgress(so_far / total_verify_size)
552
553  if updating_boot:
554    d = common.Difference(target_boot, source_boot)
555    _, _, d = d.ComputePatch()
556    print "boot      target: %d  source: %d  diff: %d" % (
557        target_boot.size, source_boot.size, len(d))
558
559    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
560
561    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
562
563    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
564                      (boot_type, boot_device,
565                       source_boot.size, source_boot.sha1,
566                       target_boot.size, target_boot.sha1))
567    so_far += source_boot.size
568    script.SetProgress(so_far / total_verify_size)
569
570  if patch_list or updating_recovery or updating_boot:
571    script.CacheFreeSpaceCheck(largest_source_size)
572
573  device_specific.IncrementalOTA_VerifyEnd()
574
575  script.Comment("---- start making changes here ----")
576
577  if OPTIONS.wipe_user_data:
578    script.Print("Erasing user data...")
579    script.FormatPartition("/data")
580
581  script.Print("Removing unneeded files...")
582  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
583                     ["/"+i for i in sorted(source_data)
584                            if i not in target_data] +
585                     ["/system/recovery.img"])
586
587  script.ShowProgress(0.8, 0)
588  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
589  if updating_boot:
590    total_patch_size += target_boot.size
591  so_far = 0
592
593  script.Print("Patching system files...")
594  for fn, tf, sf, size, _ in patch_list:
595    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
596    so_far += tf.size
597    script.SetProgress(so_far / total_patch_size)
598
599  if updating_boot:
600    # Produce the boot image by applying a patch to the current
601    # contents of the boot partition, and write it back to the
602    # partition.
603    script.Print("Patching boot image...")
604    script.ApplyPatch("%s:%s:%d:%s:%d:%s"
605                      % (boot_type, boot_device,
606                         source_boot.size, source_boot.sha1,
607                         target_boot.size, target_boot.sha1),
608                      "-",
609                      target_boot.size, target_boot.sha1,
610                      source_boot.sha1, "patch/boot.img.p")
611    so_far += target_boot.size
612    script.SetProgress(so_far / total_patch_size)
613    print "boot image changed; including."
614  else:
615    print "boot image unchanged; skipping."
616
617  if updating_recovery:
618    # Is it better to generate recovery as a patch from the current
619    # boot image, or from the previous recovery image?  For large
620    # updates with significant kernel changes, probably the former.
621    # For small updates where the kernel hasn't changed, almost
622    # certainly the latter.  We pick the first option.  Future
623    # complicated schemes may let us effectively use both.
624    #
625    # A wacky possibility: as long as there is room in the boot
626    # partition, include the binaries and image files from recovery in
627    # the boot image (though not in the ramdisk) so they can be used
628    # as fodder for constructing the recovery image.
629    MakeRecoveryPatch(output_zip, target_recovery, target_boot)
630    script.DeleteFiles(["/system/recovery-from-boot.p",
631                        "/system/etc/install-recovery.sh"])
632    print "recovery image changed; including as patch from boot."
633  else:
634    print "recovery image unchanged; skipping."
635
636  script.ShowProgress(0.1, 10)
637
638  target_symlinks = CopySystemFiles(target_zip, None)
639
640  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
641  temp_script = script.MakeTemporary()
642  Item.GetMetadata(target_zip)
643  Item.Get("system").SetPermissions(temp_script)
644
645  # Note that this call will mess up the tree of Items, so make sure
646  # we're done with it.
647  source_symlinks = CopySystemFiles(source_zip, None)
648  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
649
650  # Delete all the symlinks in source that aren't in target.  This
651  # needs to happen before verbatim files are unpacked, in case a
652  # symlink in the source is replaced by a real file in the target.
653  to_delete = []
654  for dest, link in source_symlinks:
655    if link not in target_symlinks_d:
656      to_delete.append(link)
657  script.DeleteFiles(to_delete)
658
659  if verbatim_targets:
660    script.Print("Unpacking new files...")
661    script.UnpackPackageDir("system", "/system")
662
663  if updating_recovery:
664    script.Print("Unpacking new recovery...")
665    script.UnpackPackageDir("recovery", "/system")
666
667  script.Print("Symlinks and permissions...")
668
669  # Create all the symlinks that don't already exist, or point to
670  # somewhere different than what we want.  Delete each symlink before
671  # creating it, since the 'symlink' command won't overwrite.
672  to_create = []
673  for dest, link in target_symlinks:
674    if link in source_symlinks_d:
675      if dest != source_symlinks_d[link]:
676        to_create.append((dest, link))
677    else:
678      to_create.append((dest, link))
679  script.DeleteFiles([i[1] for i in to_create])
680  script.MakeSymlinks(to_create)
681
682  # Now that the symlinks are created, we can set all the
683  # permissions.
684  script.AppendScript(temp_script)
685
686  # Do device-specific installation (eg, write radio image).
687  device_specific.IncrementalOTA_InstallEnd()
688
689  if OPTIONS.extra_script is not None:
690    scirpt.AppendExtra(OPTIONS.extra_script)
691
692  script.AddToZip(target_zip, output_zip)
693  WriteMetadata(metadata, output_zip)
694
695
696def main(argv):
697
698  def option_handler(o, a):
699    if o in ("-b", "--board_config"):
700      pass   # deprecated
701    elif o in ("-k", "--package_key"):
702      OPTIONS.package_key = a
703    elif o in ("-i", "--incremental_from"):
704      OPTIONS.incremental_source = a
705    elif o in ("-w", "--wipe_user_data"):
706      OPTIONS.wipe_user_data = True
707    elif o in ("-n", "--no_prereq"):
708      OPTIONS.omit_prereq = True
709    elif o in ("-e", "--extra_script"):
710      OPTIONS.extra_script = a
711    elif o in ("--worker_threads"):
712      OPTIONS.worker_threads = int(a)
713    else:
714      return False
715    return True
716
717  args = common.ParseOptions(argv, __doc__,
718                             extra_opts="b:k:i:d:wne:",
719                             extra_long_opts=["board_config=",
720                                              "package_key=",
721                                              "incremental_from=",
722                                              "wipe_user_data",
723                                              "no_prereq",
724                                              "extra_script=",
725                                              "worker_threads="],
726                             extra_option_handler=option_handler)
727
728  if len(args) != 2:
729    common.Usage(__doc__)
730    sys.exit(1)
731
732  if OPTIONS.extra_script is not None:
733    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
734
735  print "unzipping target target-files..."
736  OPTIONS.input_tmp = common.UnzipTemp(args[0])
737
738  OPTIONS.target_tmp = OPTIONS.input_tmp
739  input_zip = zipfile.ZipFile(args[0], "r")
740  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
741  if OPTIONS.verbose:
742    print "--- target info ---"
743    common.DumpInfoDict(OPTIONS.info_dict)
744
745  if OPTIONS.device_specific is None:
746    OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
747  if OPTIONS.device_specific is not None:
748    OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
749    print "using device-specific extensions in", OPTIONS.device_specific
750
751  if OPTIONS.package_key:
752    temp_zip_file = tempfile.NamedTemporaryFile()
753    output_zip = zipfile.ZipFile(temp_zip_file, "w",
754                                 compression=zipfile.ZIP_DEFLATED)
755  else:
756    output_zip = zipfile.ZipFile(args[1], "w",
757                                 compression=zipfile.ZIP_DEFLATED)
758
759  if OPTIONS.incremental_source is None:
760    WriteFullOTAPackage(input_zip, output_zip)
761  else:
762    print "unzipping source target-files..."
763    OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
764    source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
765    OPTIONS.target_info_dict = OPTIONS.info_dict
766    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
767    if OPTIONS.verbose:
768      print "--- source info ---"
769      common.DumpInfoDict(OPTIONS.source_info_dict)
770    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
771
772  output_zip.close()
773  if OPTIONS.package_key:
774    SignOutput(temp_zip_file.name, args[1])
775    temp_zip_file.close()
776
777  common.Cleanup()
778
779  print "done."
780
781
782if __name__ == '__main__':
783  try:
784    common.CloseInheritedPipes()
785    main(sys.argv[1:])
786  except common.ExternalError, e:
787    print
788    print "   ERROR: %s" % (e,)
789    print
790    sys.exit(1)
791