blob: 932d5b031a51b47175367768ddde7a4cfb56c7b0 [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>
Doug Zongkerfdd8e692009-08-03 17:27:48 -070025 Deprecated.
Doug Zongkereef39442009-04-02 12:14:19 -070026
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
Doug Zongkerdbfaae52009-04-21 17:12:54 -070035 -w (--wipe_user_data)
36 Generate an OTA package that will wipe the user data partition
37 when installed.
38
Doug Zongker962069c2009-04-23 11:41:58 -070039 -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
Doug Zongker1c390a22009-05-14 19:06:36 -070044 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
46
Doug Zongkerc494d7c2009-06-18 08:43:44 -070047 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
50
Doug Zongkereef39442009-04-02 12:14:19 -070051"""
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
Doug Zongkerc18736b2009-09-30 09:20:32 -070060import errno
Doug Zongkereef39442009-04-02 12:14:19 -070061import os
62import re
63import sha
64import subprocess
65import tempfile
Doug Zongker761e6422009-09-25 10:45:39 -070066import threading
Doug Zongkereef39442009-04-02 12:14:19 -070067import time
68import zipfile
69
70import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070071import amend_generator
72import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070073import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070074
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
Doug Zongkerdbfaae52009-04-21 17:12:54 -070081OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070082OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070083OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070084OPTIONS.script_mode = 'auto'
Doug Zongker761e6422009-09-25 10:45:39 -070085OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070086
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
Doug Zongker283e2a12010-03-15 17:52:32 -0700140 def GetMetadata(cls, input_zip):
141
142 try:
143 # See if the target_files contains a record of what the uid,
144 # gid, and mode is supposed to be.
145 output = input_zip.read("META/filesystem_config.txt")
146 except KeyError:
147 # Run the external 'fs_config' program to determine the desired
148 # uid, gid, and mode for every Item object. Note this uses the
149 # one in the client now, which might not be the same as the one
150 # used when this target_files was built.
151 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
152 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 suffix = { False: "", True: "/" }
154 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
155 for i in cls.ITEMS.itervalues() if i.name])
Doug Zongker3475d362010-03-17 16:39:30 -0700156 output, error = p.communicate(input)
Doug Zongker283e2a12010-03-15 17:52:32 -0700157 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700158
159 for line in output.split("\n"):
160 if not line: continue
161 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700162 i = cls.ITEMS.get(name, None)
163 if i is not None:
164 i.uid = int(uid)
165 i.gid = int(gid)
166 i.mode = int(mode, 8)
167 if i.dir:
168 i.children.sort(key=lambda i: i.name)
169
170 # set metadata for the files generated by this script.
171 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
172 if i: i.uid, i.gid, i.mode = 0, 0, 0644
173 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
174 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700175
176 def CountChildMetadata(self):
177 """Count up the (uid, gid, mode) tuples for all children and
178 determine the best strategy for using set_perm_recursive and
179 set_perm to correctly chown/chmod all the files to their desired
180 values. Recursively calls itself for all descendants.
181
182 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
183 all descendants of this node. (dmode or fmode may be None.) Also
184 sets the best_subtree of each directory Item to the (uid, gid,
185 dmode, fmode) tuple that will match the most descendants of that
186 Item.
187 """
188
189 assert self.dir
190 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
191 for i in self.children:
192 if i.dir:
193 for k, v in i.CountChildMetadata().iteritems():
194 d[k] = d.get(k, 0) + v
195 else:
196 k = (i.uid, i.gid, None, i.mode)
197 d[k] = d.get(k, 0) + 1
198
199 # Find the (uid, gid, dmode, fmode) tuple that matches the most
200 # descendants.
201
202 # First, find the (uid, gid) pair that matches the most
203 # descendants.
204 ug = {}
205 for (uid, gid, _, _), count in d.iteritems():
206 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
207 ug = MostPopularKey(ug, (0, 0))
208
209 # Now find the dmode and fmode that match the most descendants
210 # with that (uid, gid), and choose those.
211 best_dmode = (0, 0755)
212 best_fmode = (0, 0644)
213 for k, count in d.iteritems():
214 if k[:2] != ug: continue
215 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
216 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
217 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
218
219 return d
220
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700221 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700222 """Append set_perm/set_perm_recursive commands to 'script' to
223 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700224 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700225
226 self.CountChildMetadata()
227
228 def recurse(item, current):
229 # current is the (uid, gid, dmode, fmode) tuple that the current
230 # item (and all its children) have already been set to. We only
231 # need to issue set_perm/set_perm_recursive commands if we're
232 # supposed to be something different.
233 if item.dir:
234 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700235 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700236 current = item.best_subtree
237
238 if item.uid != current[0] or item.gid != current[1] or \
239 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700240 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700241
242 for i in item.children:
243 recurse(i, current)
244 else:
245 if item.uid != current[0] or item.gid != current[1] or \
246 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700247 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700248
249 recurse(self, (-1, -1, -1, -1))
250
251
252def CopySystemFiles(input_zip, output_zip=None,
253 substitute=None):
254 """Copies files underneath system/ in the input zip to the output
255 zip. Populates the Item class with their metadata, and returns a
256 list of symlinks. output_zip may be None, in which case the copy is
257 skipped (but the other side effects still happen). substitute is an
258 optional dict of {output filename: contents} to be output instead of
259 certain input files.
260 """
261
262 symlinks = []
263
264 for info in input_zip.infolist():
265 if info.filename.startswith("SYSTEM/"):
266 basefilename = info.filename[7:]
267 if IsSymlink(info):
268 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700269 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700270 else:
271 info2 = copy.copy(info)
272 fn = info2.filename = "system/" + basefilename
273 if substitute and fn in substitute and substitute[fn] is None:
274 continue
275 if output_zip is not None:
276 if substitute and fn in substitute:
277 data = substitute[fn]
278 else:
279 data = input_zip.read(info.filename)
280 output_zip.writestr(info2, data)
281 if fn.endswith("/"):
282 Item.Get(fn[:-1], dir=True)
283 else:
284 Item.Get(fn, dir=False)
285
286 symlinks.sort()
287 return symlinks
288
289
Doug Zongkereef39442009-04-02 12:14:19 -0700290def SignOutput(temp_zip_name, output_zip_name):
291 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
292 pw = key_passwords[OPTIONS.package_key]
293
Doug Zongker951495f2009-08-14 12:44:19 -0700294 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
295 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700296
297
Doug Zongkereef39442009-04-02 12:14:19 -0700298def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700299 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700300 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700301
Doug Zongkereef39442009-04-02 12:14:19 -0700302
Doug Zongker73ef8252009-07-23 15:12:53 -0700303def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
304 """Generate a binary patch that creates the recovery image starting
305 with the boot image. (Most of the space in these images is just the
306 kernel, which is identical for the two, so the resulting patch
307 should be efficient.) Add it to the output zip, along with a shell
308 script that is run from init.rc on first boot to actually do the
309 patching and install the new recovery image.
310
311 recovery_img and boot_img should be File objects for the
312 corresponding images.
313
314 Returns an Item for the shell script, which must be made
315 executable.
316 """
317
Doug Zongker761e6422009-09-25 10:45:39 -0700318 d = Difference(recovery_img, boot_img)
319 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700320 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700321 Item.Get("system/recovery-from-boot.p", dir=False)
322
323 # Images with different content will have a different first page, so
324 # we check to see if this recovery has already been installed by
325 # testing just the first 2k.
326 HEADER_SIZE = 2048
327 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
328 sh = """#!/system/bin/sh
329if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
330 log -t recovery "Installing new recovery image"
331 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
332else
333 log -t recovery "Recovery image already installed"
334fi
335""" % { 'boot_size': boot_img.size,
336 'boot_sha1': boot_img.sha1,
337 'header_size': HEADER_SIZE,
338 'header_sha1': header_sha1,
339 'recovery_size': recovery_img.size,
340 'recovery_sha1': recovery_img.sha1 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700341 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700342 return Item.Get("system/etc/install-recovery.sh", dir=False)
343
344
Doug Zongkereef39442009-04-02 12:14:19 -0700345def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700346 if OPTIONS.script_mode == "auto":
347 script = both_generator.BothGenerator(2)
348 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700349 script = amend_generator.AmendGenerator()
350 else:
351 # TODO: how to determine this? We don't know what version it will
352 # be installed on top of. For now, we expect the API just won't
353 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700354 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700355
Doug Zongker2ea21062010-04-28 16:05:21 -0700356 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
357 "pre-device": GetBuildProp("ro.product.device", input_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700358 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700359 }
360
Doug Zongker05d3dea2009-06-22 11:32:31 -0700361 device_specific = common.DeviceSpecificParams(
362 input_zip=input_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800363 input_version=GetRecoveryAPIVersion(input_zip),
Doug Zongker05d3dea2009-06-22 11:32:31 -0700364 output_zip=output_zip,
365 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700366 input_tmp=OPTIONS.input_tmp,
367 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700368
Doug Zongker962069c2009-04-23 11:41:58 -0700369 if not OPTIONS.omit_prereq:
370 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700371 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700372
373 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700374 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700375
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700376 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700377
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700378 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700379 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700380
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700381 script.FormatPartition("system")
382 script.Mount("MTD", "system", "/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700383 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700384 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700385
386 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Doug Zongker73ef8252009-07-23 15:12:53 -0700389 boot_img = File("boot.img", common.BuildBootableImage(
390 os.path.join(OPTIONS.input_tmp, "BOOT")))
391 recovery_img = File("recovery.img", common.BuildBootableImage(
392 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700393 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700394
Doug Zongker283e2a12010-03-15 17:52:32 -0700395 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700396 Item.Get("system").SetPermissions(script)
397
398 common.CheckSize(boot_img.data, "boot.img")
399 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700400 script.ShowProgress(0.2, 0)
401
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700402 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700403 script.WriteRawImage("boot", "boot.img")
404
405 script.ShowProgress(0.1, 0)
406 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700407
Doug Zongker1c390a22009-05-14 19:06:36 -0700408 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700409 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700410
Doug Zongker14833602010-02-02 13:12:04 -0800411 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700412 script.AddToZip(input_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700413 WriteMetadata(metadata, output_zip)
414
415
416def WriteMetadata(metadata, output_zip):
417 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
418 "".join(["%s=%s\n" % kv
419 for kv in sorted(metadata.iteritems())]))
Doug Zongkereef39442009-04-02 12:14:19 -0700420
421
422class File(object):
423 def __init__(self, name, data):
424 self.name = name
425 self.data = data
426 self.size = len(data)
427 self.sha1 = sha.sha(data).hexdigest()
428
429 def WriteToTemp(self):
430 t = tempfile.NamedTemporaryFile()
431 t.write(self.data)
432 t.flush()
433 return t
434
435 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700436 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700437
438
439def LoadSystemFiles(z):
440 """Load all the files from SYSTEM/... in a given target-files
441 ZipFile, and return a dict of {filename: File object}."""
442 out = {}
443 for info in z.infolist():
444 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
445 fn = "system/" + info.filename[7:]
446 data = z.read(info.filename)
447 out[fn] = File(fn, data)
448 return out
449
450
Doug Zongker761e6422009-09-25 10:45:39 -0700451DIFF_PROGRAM_BY_EXT = {
452 ".gz" : "imgdiff",
453 ".zip" : ["imgdiff", "-z"],
454 ".jar" : ["imgdiff", "-z"],
455 ".apk" : ["imgdiff", "-z"],
456 ".img" : "imgdiff",
457 }
Doug Zongkereef39442009-04-02 12:14:19 -0700458
Doug Zongkereef39442009-04-02 12:14:19 -0700459
Doug Zongker761e6422009-09-25 10:45:39 -0700460class Difference(object):
461 def __init__(self, tf, sf):
462 self.tf = tf
463 self.sf = sf
464 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700465
Doug Zongker761e6422009-09-25 10:45:39 -0700466 def ComputePatch(self):
467 """Compute the patch (as a string of data) needed to turn sf into
468 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700469
Doug Zongker761e6422009-09-25 10:45:39 -0700470 tf = self.tf
471 sf = self.sf
472
473 ext = os.path.splitext(tf.name)[1]
474 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
475
476 ttemp = tf.WriteToTemp()
477 stemp = sf.WriteToTemp()
478
479 ext = os.path.splitext(tf.name)[1]
480
481 try:
482 ptemp = tempfile.NamedTemporaryFile()
483 if isinstance(diff_program, list):
484 cmd = copy.copy(diff_program)
485 else:
486 cmd = [diff_program]
487 cmd.append(stemp.name)
488 cmd.append(ttemp.name)
489 cmd.append(ptemp.name)
490 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
491 _, err = p.communicate()
492 if err or p.returncode != 0:
493 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
494 return None
495 diff = ptemp.read()
496 finally:
497 ptemp.close()
498 stemp.close()
499 ttemp.close()
500
501 self.patch = diff
502 return self.tf, self.sf, self.patch
503
504
505 def GetPatch(self):
506 """Return a tuple (target_file, source_file, patch_data).
507 patch_data may be None if ComputePatch hasn't been called, or if
508 computing the patch failed."""
509 return self.tf, self.sf, self.patch
510
511
512def ComputeDifferences(diffs):
513 """Call ComputePatch on all the Difference objects in 'diffs'."""
514 print len(diffs), "diffs to compute"
515
516 # Do the largest files first, to try and reduce the long-pole effect.
517 by_size = [(i.tf.size, i) for i in diffs]
518 by_size.sort(reverse=True)
519 by_size = [i[1] for i in by_size]
520
521 lock = threading.Lock()
522 diff_iter = iter(by_size) # accessed under lock
523
524 def worker():
525 try:
526 lock.acquire()
527 for d in diff_iter:
528 lock.release()
529 start = time.time()
530 d.ComputePatch()
531 dur = time.time() - start
532 lock.acquire()
533
534 tf, sf, patch = d.GetPatch()
535 if sf.name == tf.name:
536 name = tf.name
537 else:
538 name = "%s (%s)" % (tf.name, sf.name)
539 if patch is None:
540 print "patching failed! %s" % (name,)
541 else:
542 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
543 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
544 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700545 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700546 print e
547 raise
548
549 # start worker threads; wait for them all to finish.
550 threads = [threading.Thread(target=worker)
551 for i in range(OPTIONS.worker_threads)]
552 for th in threads:
553 th.start()
554 while threads:
555 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700556
557
558def GetBuildProp(property, z):
559 """Return the fingerprint of the build of a given target-files
560 ZipFile object."""
561 bp = z.read("SYSTEM/build.prop")
562 if not property:
563 return bp
564 m = re.search(re.escape(property) + r"=(.*)\n", bp)
565 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700566 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700567 return m.group(1).strip()
568
569
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700570def GetRecoveryAPIVersion(zip):
571 """Returns the version of the recovery API. Version 0 is the older
572 amend code (no separate binary)."""
573 try:
574 version = zip.read("META/recovery-api-version.txt")
575 return int(version)
576 except KeyError:
577 try:
578 # version one didn't have the recovery-api-version.txt file, but
579 # it did include an updater binary.
580 zip.getinfo("OTA/bin/updater")
581 return 1
582 except KeyError:
583 return 0
584
Doug Zongker15604b82009-09-01 17:53:34 -0700585
Doug Zongkereef39442009-04-02 12:14:19 -0700586def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700587 source_version = GetRecoveryAPIVersion(source_zip)
Doug Zongker14833602010-02-02 13:12:04 -0800588 target_version = GetRecoveryAPIVersion(target_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700589
590 if OPTIONS.script_mode == 'amend':
591 script = amend_generator.AmendGenerator()
592 elif OPTIONS.script_mode == 'edify':
593 if source_version == 0:
594 print ("WARNING: generating edify script for a source that "
595 "can't install it.")
596 script = edify_generator.EdifyGenerator(source_version)
597 elif OPTIONS.script_mode == 'auto':
598 if source_version > 0:
599 script = edify_generator.EdifyGenerator(source_version)
600 else:
601 script = amend_generator.AmendGenerator()
602 else:
603 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700604
Doug Zongker2ea21062010-04-28 16:05:21 -0700605 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700606 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700607 }
608
Doug Zongker05d3dea2009-06-22 11:32:31 -0700609 device_specific = common.DeviceSpecificParams(
610 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800611 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700612 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800613 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700614 output_zip=output_zip,
Doug Zongker2ea21062010-04-28 16:05:21 -0700615 script=script,
616 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700617
Doug Zongkereef39442009-04-02 12:14:19 -0700618 print "Loading target..."
619 target_data = LoadSystemFiles(target_zip)
620 print "Loading source..."
621 source_data = LoadSystemFiles(source_zip)
622
623 verbatim_targets = []
624 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700625 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700626 largest_source_size = 0
627 for fn in sorted(target_data.keys()):
628 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700629 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700630 sf = source_data.get(fn, None)
631
632 if sf is None or fn in OPTIONS.require_verbatim:
633 # This file should be included verbatim
634 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700635 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700636 print "send", fn, "verbatim"
637 tf.AddToZip(output_zip)
638 verbatim_targets.append((fn, tf.size))
639 elif tf.sha1 != sf.sha1:
640 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700641 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700642 else:
643 # Target file identical to source.
644 pass
645
Doug Zongker761e6422009-09-25 10:45:39 -0700646 ComputeDifferences(diffs)
647
648 for diff in diffs:
649 tf, sf, d = diff.GetPatch()
650 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
651 # patch is almost as big as the file; don't bother patching
652 tf.AddToZip(output_zip)
653 verbatim_targets.append((tf.name, tf.size))
654 else:
655 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800656 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700657 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700658
659 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
660 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700661 metadata["pre-build"] = source_fp
662 metadata["post-build"] = target_fp
Doug Zongkereef39442009-04-02 12:14:19 -0700663
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700664 script.Mount("MTD", "system", "/system")
665 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700666
Doug Zongker5da317e2009-06-02 13:38:17 -0700667 source_boot = File("/tmp/boot.img",
668 common.BuildBootableImage(
669 os.path.join(OPTIONS.source_tmp, "BOOT")))
670 target_boot = File("/tmp/boot.img",
671 common.BuildBootableImage(
672 os.path.join(OPTIONS.target_tmp, "BOOT")))
673 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700674
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700675 source_recovery = File("system/recovery.img",
676 common.BuildBootableImage(
677 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
678 target_recovery = File("system/recovery.img",
679 common.BuildBootableImage(
680 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
681 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700682
Doug Zongker881dd402009-09-20 14:03:55 -0700683 # Here's how we divide up the progress bar:
684 # 0.1 for verifying the start state (PatchCheck calls)
685 # 0.8 for applying patches (ApplyPatch calls)
686 # 0.1 for unpacking verbatim files, symlinking, and doing the
687 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700688
689 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700690 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700691
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700692 script.Print("Verifying current system...")
693
Doug Zongker881dd402009-09-20 14:03:55 -0700694 script.ShowProgress(0.1, 0)
695 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
696 if updating_boot:
697 total_verify_size += source_boot.size
698 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700699
Doug Zongker5a482092010-02-17 16:09:18 -0800700 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700701 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700702 so_far += sf.size
703 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700704
Doug Zongker5da317e2009-06-02 13:38:17 -0700705 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700706 d = Difference(target_boot, source_boot)
707 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700708 print "boot target: %d source: %d diff: %d" % (
709 target_boot.size, source_boot.size, len(d))
710
Doug Zongker048e7ca2009-06-15 14:31:53 -0700711 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700712
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700713 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
714 (source_boot.size, source_boot.sha1,
715 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700716 so_far += source_boot.size
717 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700718
719 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700720 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800721
Doug Zongker05d3dea2009-06-22 11:32:31 -0700722 device_specific.IncrementalOTA_VerifyEnd()
723
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700724 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700725
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700726 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700727 script.Print("Erasing user data...")
728 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700729
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700730 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700731 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
732 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700733 if i not in target_data] +
734 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700735
Doug Zongker881dd402009-09-20 14:03:55 -0700736 script.ShowProgress(0.8, 0)
737 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
738 if updating_boot:
739 total_patch_size += target_boot.size
740 so_far = 0
741
742 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800743 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800744 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700745 so_far += tf.size
746 script.SetProgress(so_far / total_patch_size)
747
Doug Zongkereef39442009-04-02 12:14:19 -0700748 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700749 # Produce the boot image by applying a patch to the current
750 # contents of the boot partition, and write it back to the
751 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700752 script.Print("Patching boot image...")
753 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
754 % (source_boot.size, source_boot.sha1,
755 target_boot.size, target_boot.sha1),
756 "-",
757 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800758 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700759 so_far += target_boot.size
760 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700761 print "boot image changed; including."
762 else:
763 print "boot image unchanged; skipping."
764
765 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700766 # Is it better to generate recovery as a patch from the current
767 # boot image, or from the previous recovery image? For large
768 # updates with significant kernel changes, probably the former.
769 # For small updates where the kernel hasn't changed, almost
770 # certainly the latter. We pick the first option. Future
771 # complicated schemes may let us effectively use both.
772 #
773 # A wacky possibility: as long as there is room in the boot
774 # partition, include the binaries and image files from recovery in
775 # the boot image (though not in the ramdisk) so they can be used
776 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700777 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800778 script.DeleteFiles(["/system/recovery-from-boot.p",
779 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700780 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700781 else:
782 print "recovery image unchanged; skipping."
783
Doug Zongker881dd402009-09-20 14:03:55 -0700784 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700785
786 target_symlinks = CopySystemFiles(target_zip, None)
787
788 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700789 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700790 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700791 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700792
793 # Note that this call will mess up the tree of Items, so make sure
794 # we're done with it.
795 source_symlinks = CopySystemFiles(source_zip, None)
796 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
797
798 # Delete all the symlinks in source that aren't in target. This
799 # needs to happen before verbatim files are unpacked, in case a
800 # symlink in the source is replaced by a real file in the target.
801 to_delete = []
802 for dest, link in source_symlinks:
803 if link not in target_symlinks_d:
804 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700805 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700806
807 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700808 script.Print("Unpacking new files...")
809 script.UnpackPackageDir("system", "/system")
810
Doug Zongker42265392010-02-12 10:21:00 -0800811 if updating_recovery:
812 script.Print("Unpacking new recovery...")
813 script.UnpackPackageDir("recovery", "/system")
814
Doug Zongker05d3dea2009-06-22 11:32:31 -0700815 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700816
817 # Create all the symlinks that don't already exist, or point to
818 # somewhere different than what we want. Delete each symlink before
819 # creating it, since the 'symlink' command won't overwrite.
820 to_create = []
821 for dest, link in target_symlinks:
822 if link in source_symlinks_d:
823 if dest != source_symlinks_d[link]:
824 to_create.append((dest, link))
825 else:
826 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700827 script.DeleteFiles([i[1] for i in to_create])
828 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700829
830 # Now that the symlinks are created, we can set all the
831 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700832 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700833
Doug Zongker881dd402009-09-20 14:03:55 -0700834 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700835 device_specific.IncrementalOTA_InstallEnd()
836
Doug Zongker1c390a22009-05-14 19:06:36 -0700837 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700838 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700839
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700840 script.AddToZip(target_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700841 WriteMetadata(metadata, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700842
843
844def main(argv):
845
846 def option_handler(o, a):
847 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700848 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700849 elif o in ("-k", "--package_key"):
850 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700851 elif o in ("-i", "--incremental_from"):
852 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700853 elif o in ("-w", "--wipe_user_data"):
854 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700855 elif o in ("-n", "--no_prereq"):
856 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700857 elif o in ("-e", "--extra_script"):
858 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700859 elif o in ("-m", "--script_mode"):
860 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700861 elif o in ("--worker_threads"):
862 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700863 else:
864 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700865 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700866
867 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700868 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700869 extra_long_opts=["board_config=",
870 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700871 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700872 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700873 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700874 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700875 "script_mode=",
876 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700877 extra_option_handler=option_handler)
878
879 if len(args) != 2:
880 common.Usage(__doc__)
881 sys.exit(1)
882
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700883 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
884 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
885
Doug Zongker1c390a22009-05-14 19:06:36 -0700886 if OPTIONS.extra_script is not None:
887 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
888
Doug Zongkereef39442009-04-02 12:14:19 -0700889 print "unzipping target target-files..."
890 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700891
Doug Zongkerc18736b2009-09-30 09:20:32 -0700892 if OPTIONS.device_specific is None:
893 # look for the device-specific tools extension location in the input
894 try:
895 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
896 ds = f.read().strip()
897 f.close()
898 if ds:
899 ds = os.path.normpath(ds)
900 print "using device-specific extensions in", ds
901 OPTIONS.device_specific = ds
902 except IOError, e:
903 if e.errno == errno.ENOENT:
904 # nothing specified in the file
905 pass
906 else:
907 raise
908
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700909 common.LoadMaxSizes()
910 if not OPTIONS.max_image_size:
911 print
912 print " WARNING: Failed to load max image sizes; will not enforce"
913 print " image size limits."
914 print
915
Doug Zongkereef39442009-04-02 12:14:19 -0700916 OPTIONS.target_tmp = OPTIONS.input_tmp
917 input_zip = zipfile.ZipFile(args[0], "r")
918 if OPTIONS.package_key:
919 temp_zip_file = tempfile.NamedTemporaryFile()
920 output_zip = zipfile.ZipFile(temp_zip_file, "w",
921 compression=zipfile.ZIP_DEFLATED)
922 else:
923 output_zip = zipfile.ZipFile(args[1], "w",
924 compression=zipfile.ZIP_DEFLATED)
925
926 if OPTIONS.incremental_source is None:
927 WriteFullOTAPackage(input_zip, output_zip)
928 else:
929 print "unzipping source target-files..."
930 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
931 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
932 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
933
934 output_zip.close()
935 if OPTIONS.package_key:
936 SignOutput(temp_zip_file.name, args[1])
937 temp_zip_file.close()
938
939 common.Cleanup()
940
941 print "done."
942
943
944if __name__ == '__main__':
945 try:
946 main(sys.argv[1:])
947 except common.ExternalError, e:
948 print
949 print " ERROR: %s" % (e,)
950 print
951 sys.exit(1)