blob: 69e9f5b5a6558fba611215d7da4afce404ba503e [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/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
Doug Zongkerdbfaae52009-04-21 17:12:54 -070036 -w (--wipe_user_data)
37 Generate an OTA package that will wipe the user data partition
38 when installed.
39
Doug Zongker962069c2009-04-23 11:41:58 -070040 -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
Doug Zongkereef39442009-04-02 12:14:19 -070045"""
46
47import sys
48
49if sys.hexversion < 0x02040000:
50 print >> sys.stderr, "Python 2.4 or newer is required."
51 sys.exit(1)
52
53import copy
54import os
55import re
56import sha
57import subprocess
58import tempfile
59import time
60import zipfile
61
62import common
63
64OPTIONS = common.OPTIONS
65OPTIONS.package_key = "build/target/product/security/testkey"
66OPTIONS.incremental_source = None
67OPTIONS.require_verbatim = set()
68OPTIONS.prohibit_verbatim = set(("system/build.prop",))
69OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070070OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070071OPTIONS.omit_prereq = False
Doug Zongkereef39442009-04-02 12:14:19 -070072
73def MostPopularKey(d, default):
74 """Given a dict, return the key corresponding to the largest
75 value. Returns 'default' if the dict is empty."""
76 x = [(v, k) for (k, v) in d.iteritems()]
77 if not x: return default
78 x.sort()
79 return x[-1][1]
80
81
82def IsSymlink(info):
83 """Return true if the zipfile.ZipInfo object passed in represents a
84 symlink."""
85 return (info.external_attr >> 16) == 0120777
86
87
88
89class Item:
90 """Items represent the metadata (user, group, mode) of files and
91 directories in the system image."""
92 ITEMS = {}
93 def __init__(self, name, dir=False):
94 self.name = name
95 self.uid = None
96 self.gid = None
97 self.mode = None
98 self.dir = dir
99
100 if name:
101 self.parent = Item.Get(os.path.dirname(name), dir=True)
102 self.parent.children.append(self)
103 else:
104 self.parent = None
105 if dir:
106 self.children = []
107
108 def Dump(self, indent=0):
109 if self.uid is not None:
110 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
111 else:
112 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
113 if self.dir:
114 print "%s%s" % (" "*indent, self.descendants)
115 print "%s%s" % (" "*indent, self.best_subtree)
116 for i in self.children:
117 i.Dump(indent=indent+1)
118
119 @classmethod
120 def Get(cls, name, dir=False):
121 if name not in cls.ITEMS:
122 cls.ITEMS[name] = Item(name, dir=dir)
123 return cls.ITEMS[name]
124
125 @classmethod
126 def GetMetadata(cls):
127 """Run the external 'fs_config' program to determine the desired
128 uid, gid, and mode for every Item object."""
129 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
130 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
131 suffix = { False: "", True: "/" }
132 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
133 for i in cls.ITEMS.itervalues() if i.name])
134 output, error = p.communicate(input)
135 assert not error
136
137 for line in output.split("\n"):
138 if not line: continue
139 name, uid, gid, mode = line.split()
140 i = cls.ITEMS[name]
141 i.uid = int(uid)
142 i.gid = int(gid)
143 i.mode = int(mode, 8)
144 if i.dir:
145 i.children.sort(key=lambda i: i.name)
146
147 def CountChildMetadata(self):
148 """Count up the (uid, gid, mode) tuples for all children and
149 determine the best strategy for using set_perm_recursive and
150 set_perm to correctly chown/chmod all the files to their desired
151 values. Recursively calls itself for all descendants.
152
153 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
154 all descendants of this node. (dmode or fmode may be None.) Also
155 sets the best_subtree of each directory Item to the (uid, gid,
156 dmode, fmode) tuple that will match the most descendants of that
157 Item.
158 """
159
160 assert self.dir
161 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
162 for i in self.children:
163 if i.dir:
164 for k, v in i.CountChildMetadata().iteritems():
165 d[k] = d.get(k, 0) + v
166 else:
167 k = (i.uid, i.gid, None, i.mode)
168 d[k] = d.get(k, 0) + 1
169
170 # Find the (uid, gid, dmode, fmode) tuple that matches the most
171 # descendants.
172
173 # First, find the (uid, gid) pair that matches the most
174 # descendants.
175 ug = {}
176 for (uid, gid, _, _), count in d.iteritems():
177 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
178 ug = MostPopularKey(ug, (0, 0))
179
180 # Now find the dmode and fmode that match the most descendants
181 # with that (uid, gid), and choose those.
182 best_dmode = (0, 0755)
183 best_fmode = (0, 0644)
184 for k, count in d.iteritems():
185 if k[:2] != ug: continue
186 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
187 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
188 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
189
190 return d
191
192 def SetPermissions(self, script, renamer=lambda x: x):
193 """Append set_perm/set_perm_recursive commands to 'script' to
194 set all permissions, users, and groups for the tree of files
195 rooted at 'self'. 'renamer' turns the filenames stored in the
196 tree of Items into the strings used in the script."""
197
198 self.CountChildMetadata()
199
200 def recurse(item, current):
201 # current is the (uid, gid, dmode, fmode) tuple that the current
202 # item (and all its children) have already been set to. We only
203 # need to issue set_perm/set_perm_recursive commands if we're
204 # supposed to be something different.
205 if item.dir:
206 if current != item.best_subtree:
207 script.append("set_perm_recursive %d %d 0%o 0%o %s" %
208 (item.best_subtree + (renamer(item.name),)))
209 current = item.best_subtree
210
211 if item.uid != current[0] or item.gid != current[1] or \
212 item.mode != current[2]:
213 script.append("set_perm %d %d 0%o %s" %
214 (item.uid, item.gid, item.mode, renamer(item.name)))
215
216 for i in item.children:
217 recurse(i, current)
218 else:
219 if item.uid != current[0] or item.gid != current[1] or \
220 item.mode != current[3]:
221 script.append("set_perm %d %d 0%o %s" %
222 (item.uid, item.gid, item.mode, renamer(item.name)))
223
224 recurse(self, (-1, -1, -1, -1))
225
226
227def CopySystemFiles(input_zip, output_zip=None,
228 substitute=None):
229 """Copies files underneath system/ in the input zip to the output
230 zip. Populates the Item class with their metadata, and returns a
231 list of symlinks. output_zip may be None, in which case the copy is
232 skipped (but the other side effects still happen). substitute is an
233 optional dict of {output filename: contents} to be output instead of
234 certain input files.
235 """
236
237 symlinks = []
238
239 for info in input_zip.infolist():
240 if info.filename.startswith("SYSTEM/"):
241 basefilename = info.filename[7:]
242 if IsSymlink(info):
243 symlinks.append((input_zip.read(info.filename),
244 "SYSTEM:" + basefilename))
245 else:
246 info2 = copy.copy(info)
247 fn = info2.filename = "system/" + basefilename
248 if substitute and fn in substitute and substitute[fn] is None:
249 continue
250 if output_zip is not None:
251 if substitute and fn in substitute:
252 data = substitute[fn]
253 else:
254 data = input_zip.read(info.filename)
255 output_zip.writestr(info2, data)
256 if fn.endswith("/"):
257 Item.Get(fn[:-1], dir=True)
258 else:
259 Item.Get(fn, dir=False)
260
261 symlinks.sort()
262 return symlinks
263
264
265def AddScript(script, output_zip):
266 now = time.localtime()
267 i = zipfile.ZipInfo("META-INF/com/google/android/update-script",
268 (now.tm_year, now.tm_mon, now.tm_mday,
269 now.tm_hour, now.tm_min, now.tm_sec))
270 output_zip.writestr(i, "\n".join(script) + "\n")
271
272
273def SignOutput(temp_zip_name, output_zip_name):
274 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
275 pw = key_passwords[OPTIONS.package_key]
276
277 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
278
279
280def SubstituteRoot(s):
281 if s == "system": return "SYSTEM:"
282 assert s.startswith("system/")
283 return "SYSTEM:" + s[7:]
284
285def FixPermissions(script):
286 Item.GetMetadata()
287 root = Item.Get("system")
288 root.SetPermissions(script, renamer=SubstituteRoot)
289
290def DeleteFiles(script, to_delete):
291 line = []
292 t = 0
293 for i in to_delete:
294 line.append(i)
295 t += len(i) + 1
296 if t > 80:
297 script.append("delete " + " ".join(line))
298 line = []
299 t = 0
300 if line:
301 script.append("delete " + " ".join(line))
302
303def AppendAssertions(script, input_zip):
304 script.append('assert compatible_with("0.2") == "true"')
305
306 device = GetBuildProp("ro.product.device", input_zip)
307 script.append('assert getprop("ro.product.device") == "%s" || '
308 'getprop("ro.build.product") == "%s"' % (device, device))
309
310 info = input_zip.read("OTA/android-info.txt")
311 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
312 if not m:
313 raise ExternalError("failed to find required bootloaders in "
314 "android-info.txt")
315 bootloaders = m.group(1).split("|")
316 script.append("assert " +
317 " || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
318 for b in bootloaders]))
319
320
321def IncludeBinary(name, input_zip, output_zip):
322 try:
323 data = input_zip.read(os.path.join("OTA/bin", name))
324 output_zip.writestr(name, data)
325 except IOError:
326 raise ExternalError('unable to include device binary "%s"' % (name,))
327
328
329def WriteFullOTAPackage(input_zip, output_zip):
330 script = []
331
Doug Zongker962069c2009-04-23 11:41:58 -0700332 if not OPTIONS.omit_prereq:
333 ts = GetBuildProp("ro.build.date.utc", input_zip)
334 script.append("run_program PACKAGE:check_prereq %s" % (ts,))
335 IncludeBinary("check_prereq", input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700336
337 AppendAssertions(script, input_zip)
338
339 script.append("format BOOT:")
340 script.append("show_progress 0.1 0")
341
342 output_zip.writestr("radio.img", input_zip.read("RADIO/image"))
343 script.append("write_radio_image PACKAGE:radio.img")
344 script.append("show_progress 0.5 0")
345
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700346 if OPTIONS.wipe_user_data:
347 script.append("format DATA:")
348
Doug Zongkereef39442009-04-02 12:14:19 -0700349 script.append("format SYSTEM:")
350 script.append("copy_dir PACKAGE:system SYSTEM:")
351
352 symlinks = CopySystemFiles(input_zip, output_zip)
353 script.extend(["symlink %s %s" % s for s in symlinks])
354
355 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
356 "system/recovery.img", output_zip)
357 Item.Get("system/recovery.img", dir=False)
358
359 FixPermissions(script)
360
361 common.AddBoot(output_zip)
362 script.append("show_progress 0.2 0")
363 script.append("write_raw_image PACKAGE:boot.img BOOT:")
364 script.append("show_progress 0.2 10")
365
366 AddScript(script, output_zip)
367
368
369class File(object):
370 def __init__(self, name, data):
371 self.name = name
372 self.data = data
373 self.size = len(data)
374 self.sha1 = sha.sha(data).hexdigest()
375
376 def WriteToTemp(self):
377 t = tempfile.NamedTemporaryFile()
378 t.write(self.data)
379 t.flush()
380 return t
381
382 def AddToZip(self, z):
383 z.writestr(self.name, self.data)
384
385
386def LoadSystemFiles(z):
387 """Load all the files from SYSTEM/... in a given target-files
388 ZipFile, and return a dict of {filename: File object}."""
389 out = {}
390 for info in z.infolist():
391 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
392 fn = "system/" + info.filename[7:]
393 data = z.read(info.filename)
394 out[fn] = File(fn, data)
395 return out
396
397
398def Difference(tf, sf):
399 """Return the patch (as a string of data) needed to turn sf into tf."""
400
401 ttemp = tf.WriteToTemp()
402 stemp = sf.WriteToTemp()
403
404 ext = os.path.splitext(tf.name)[1]
405
406 try:
407 ptemp = tempfile.NamedTemporaryFile()
408 p = common.Run(["bsdiff", stemp.name, ttemp.name, ptemp.name])
409 _, err = p.communicate()
410 if err:
411 raise ExternalError("failure running bsdiff:\n%s\n" % (err,))
412 diff = ptemp.read()
413 ptemp.close()
414 finally:
415 stemp.close()
416 ttemp.close()
417
418 return diff
419
420
421def GetBuildProp(property, z):
422 """Return the fingerprint of the build of a given target-files
423 ZipFile object."""
424 bp = z.read("SYSTEM/build.prop")
425 if not property:
426 return bp
427 m = re.search(re.escape(property) + r"=(.*)\n", bp)
428 if not m:
429 raise ExternalException("couldn't find %s in build.prop" % (property,))
430 return m.group(1).strip()
431
432
433def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
434 script = []
435
436 print "Loading target..."
437 target_data = LoadSystemFiles(target_zip)
438 print "Loading source..."
439 source_data = LoadSystemFiles(source_zip)
440
441 verbatim_targets = []
442 patch_list = []
443 largest_source_size = 0
444 for fn in sorted(target_data.keys()):
445 tf = target_data[fn]
446 sf = source_data.get(fn, None)
447
448 if sf is None or fn in OPTIONS.require_verbatim:
449 # This file should be included verbatim
450 if fn in OPTIONS.prohibit_verbatim:
451 raise ExternalError("\"%s\" must be sent verbatim" % (fn,))
452 print "send", fn, "verbatim"
453 tf.AddToZip(output_zip)
454 verbatim_targets.append((fn, tf.size))
455 elif tf.sha1 != sf.sha1:
456 # File is different; consider sending as a patch
457 d = Difference(tf, sf)
458 print fn, tf.size, len(d), (float(len(d)) / tf.size)
459 if len(d) > tf.size * OPTIONS.patch_threshold:
460 # patch is almost as big as the file; don't bother patching
461 tf.AddToZip(output_zip)
462 verbatim_targets.append((fn, tf.size))
463 else:
464 output_zip.writestr("patch/" + fn + ".p", d)
465 patch_list.append((fn, tf, sf, tf.size))
466 largest_source_size = max(largest_source_size, sf.size)
467 else:
468 # Target file identical to source.
469 pass
470
471 total_verbatim_size = sum([i[1] for i in verbatim_targets])
472 total_patched_size = sum([i[3] for i in patch_list])
473
474 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
475 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
476
477 script.append(('assert file_contains("SYSTEM:build.prop", '
478 '"ro.build.fingerprint=%s") == "true" || '
479 'file_contains("SYSTEM:build.prop", '
480 '"ro.build.fingerprint=%s") == "true"') %
481 (source_fp, target_fp))
482
483 source_boot = common.BuildBootableImage(
484 os.path.join(OPTIONS.source_tmp, "BOOT"))
485 target_boot = common.BuildBootableImage(
486 os.path.join(OPTIONS.target_tmp, "BOOT"))
487 updating_boot = (source_boot != target_boot)
488
489 source_recovery = common.BuildBootableImage(
490 os.path.join(OPTIONS.source_tmp, "RECOVERY"))
491 target_recovery = common.BuildBootableImage(
492 os.path.join(OPTIONS.target_tmp, "RECOVERY"))
493 updating_recovery = (source_recovery != target_recovery)
494
495 source_radio = source_zip.read("RADIO/image")
496 target_radio = target_zip.read("RADIO/image")
497 updating_radio = (source_radio != target_radio)
498
499 # The last 0.1 is reserved for creating symlinks, fixing
500 # permissions, and writing the boot image (if necessary).
501 progress_bar_total = 1.0
502 if updating_boot:
503 progress_bar_total -= 0.1
504 if updating_radio:
505 progress_bar_total -= 0.3
506
507 AppendAssertions(script, target_zip)
508
509 pb_verify = progress_bar_total * 0.3 * \
510 (total_patched_size /
511 float(total_patched_size+total_verbatim_size))
512
513 for i, (fn, tf, sf, size) in enumerate(patch_list):
514 if i % 5 == 0:
515 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
516 script.append("show_progress %f 1" %
517 (next_sizes * pb_verify / total_patched_size,))
518 script.append("run_program PACKAGE:applypatch -c /%s %s %s" %
519 (fn, tf.sha1, sf.sha1))
520
521 if patch_list:
522 script.append("run_program PACKAGE:applypatch -s %d" %
523 (largest_source_size,))
524 script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp")
525 IncludeBinary("applypatch", target_zip, output_zip)
526
527 script.append("\n# ---- start making changes here\n")
528
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700529 if OPTIONS.wipe_user_data:
530 script.append("format DATA:")
531
Doug Zongkereef39442009-04-02 12:14:19 -0700532 DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets])
533
534 if updating_boot:
535 script.append("format BOOT:")
536 output_zip.writestr("boot.img", target_boot)
537 print "boot image changed; including."
538 else:
539 print "boot image unchanged; skipping."
540
541 if updating_recovery:
542 output_zip.writestr("system/recovery.img", target_recovery)
543 print "recovery image changed; including."
544 else:
545 print "recovery image unchanged; skipping."
546
547 if updating_radio:
548 script.append("show_progress 0.3 10")
549 script.append("write_radio_image PACKAGE:radio.img")
550 output_zip.writestr("radio.img", target_radio)
551 print "radio image changed; including."
552 else:
553 print "radio image unchanged; skipping."
554
555 pb_apply = progress_bar_total * 0.7 * \
556 (total_patched_size /
557 float(total_patched_size+total_verbatim_size))
558 for i, (fn, tf, sf, size) in enumerate(patch_list):
559 if i % 5 == 0:
560 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
561 script.append("show_progress %f 1" %
562 (next_sizes * pb_apply / total_patched_size,))
563 script.append(("run_program PACKAGE:applypatch "
Doug Zongkeref85ea62009-05-08 13:46:25 -0700564 "/%s - %s %d %s:/tmp/patchtmp/%s.p") %
Doug Zongkereef39442009-04-02 12:14:19 -0700565 (fn, tf.sha1, tf.size, sf.sha1, fn))
566
567 target_symlinks = CopySystemFiles(target_zip, None)
568
569 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
570 temp_script = []
571 FixPermissions(temp_script)
572
573 # Note that this call will mess up the tree of Items, so make sure
574 # we're done with it.
575 source_symlinks = CopySystemFiles(source_zip, None)
576 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
577
578 # Delete all the symlinks in source that aren't in target. This
579 # needs to happen before verbatim files are unpacked, in case a
580 # symlink in the source is replaced by a real file in the target.
581 to_delete = []
582 for dest, link in source_symlinks:
583 if link not in target_symlinks_d:
584 to_delete.append(link)
585 DeleteFiles(script, to_delete)
586
587 if verbatim_targets:
588 pb_verbatim = progress_bar_total * \
589 (total_verbatim_size /
590 float(total_patched_size+total_verbatim_size))
591 script.append("show_progress %f 5" % (pb_verbatim,))
592 script.append("copy_dir PACKAGE:system SYSTEM:")
593
594 # Create all the symlinks that don't already exist, or point to
595 # somewhere different than what we want. Delete each symlink before
596 # creating it, since the 'symlink' command won't overwrite.
597 to_create = []
598 for dest, link in target_symlinks:
599 if link in source_symlinks_d:
600 if dest != source_symlinks_d[link]:
601 to_create.append((dest, link))
602 else:
603 to_create.append((dest, link))
604 DeleteFiles(script, [i[1] for i in to_create])
605 script.extend(["symlink %s %s" % s for s in to_create])
606
607 # Now that the symlinks are created, we can set all the
608 # permissions.
609 script.extend(temp_script)
610
611 if updating_boot:
612 script.append("show_progress 0.1 5")
613 script.append("write_raw_image PACKAGE:boot.img BOOT:")
614
615 AddScript(script, output_zip)
616
617
618def main(argv):
619
620 def option_handler(o, a):
621 if o in ("-b", "--board_config"):
622 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700623 elif o in ("-k", "--package_key"):
624 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700625 elif o in ("-i", "--incremental_from"):
626 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700627 elif o in ("-w", "--wipe_user_data"):
628 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700629 elif o in ("-n", "--no_prereq"):
630 OPTIONS.omit_prereq = True
Doug Zongkereef39442009-04-02 12:14:19 -0700631 else:
632 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700633 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700634
635 args = common.ParseOptions(argv, __doc__,
Doug Zongker962069c2009-04-23 11:41:58 -0700636 extra_opts="b:k:i:d:wn",
Doug Zongkereef39442009-04-02 12:14:19 -0700637 extra_long_opts=["board_config=",
638 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700639 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700640 "wipe_user_data",
641 "no_prereq"],
Doug Zongkereef39442009-04-02 12:14:19 -0700642 extra_option_handler=option_handler)
643
644 if len(args) != 2:
645 common.Usage(__doc__)
646 sys.exit(1)
647
648 if not OPTIONS.max_image_size:
649 print
650 print " WARNING: No board config specified; will not check image"
651 print " sizes against limits. Use -b to make sure the generated"
652 print " images don't exceed partition sizes."
653 print
654
655 print "unzipping target target-files..."
656 OPTIONS.input_tmp = common.UnzipTemp(args[0])
657 OPTIONS.target_tmp = OPTIONS.input_tmp
658 input_zip = zipfile.ZipFile(args[0], "r")
659 if OPTIONS.package_key:
660 temp_zip_file = tempfile.NamedTemporaryFile()
661 output_zip = zipfile.ZipFile(temp_zip_file, "w",
662 compression=zipfile.ZIP_DEFLATED)
663 else:
664 output_zip = zipfile.ZipFile(args[1], "w",
665 compression=zipfile.ZIP_DEFLATED)
666
667 if OPTIONS.incremental_source is None:
668 WriteFullOTAPackage(input_zip, output_zip)
669 else:
670 print "unzipping source target-files..."
671 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
672 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
673 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
674
675 output_zip.close()
676 if OPTIONS.package_key:
677 SignOutput(temp_zip_file.name, args[1])
678 temp_zip_file.close()
679
680 common.Cleanup()
681
682 print "done."
683
684
685if __name__ == '__main__':
686 try:
687 main(sys.argv[1:])
688 except common.ExternalError, e:
689 print
690 print " ERROR: %s" % (e,)
691 print
692 sys.exit(1)