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