blob: 7261b1babf9ebcf256f754fe52e72438f63992dc [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 Zongker1c390a22009-05-14 19:06:36 -070045 -e (--extra_script) <file>
46 Insert the contents of file at the end of the update script.
47
Doug Zongkerc494d7c2009-06-18 08:43:44 -070048 -m (--script_mode) <mode>
49 Specify 'amend' or 'edify' scripts, or 'auto' to pick
50 automatically (this is the default).
51
Doug Zongkereef39442009-04-02 12:14:19 -070052"""
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
Doug Zongkerc494d7c2009-06-18 08:43:44 -070070import amend_generator
71import edify_generator
Doug Zongkereef39442009-04-02 12:14:19 -070072
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
Doug Zongkerdbfaae52009-04-21 17:12:54 -070079OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070080OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070081OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070082OPTIONS.script_mode = 'auto'
Doug Zongkereef39442009-04-02 12:14:19 -070083
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
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700203 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700204 """Append set_perm/set_perm_recursive commands to 'script' to
205 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700206 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700207
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:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700217 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700218 current = item.best_subtree
219
220 if item.uid != current[0] or item.gid != current[1] or \
221 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700222 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700223
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]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700229 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700230
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),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700251 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700252 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
Doug Zongkereef39442009-04-02 12:14:19 -0700272def 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
Doug Zongkereef39442009-04-02 12:14:19 -0700279def FixPermissions(script):
280 Item.GetMetadata()
281 root = Item.Get("system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700282 root.SetPermissions(script)
Doug Zongkereef39442009-04-02 12:14:19 -0700283
Doug Zongkereef39442009-04-02 12:14:19 -0700284
285def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700286 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700287 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700288
289 info = input_zip.read("OTA/android-info.txt")
290 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker171f1cd2009-06-15 22:36:37 -0700291 if m:
292 bootloaders = m.group(1).split("|")
Doug Zongkerac4920a2009-06-18 13:42:20 -0700293 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700294
295
296def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700297 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)
Doug Zongkereef39442009-04-02 12:14:19 -0700304
Doug Zongker05d3dea2009-06-22 11:32:31 -0700305 device_specific = common.DeviceSpecificParams(
306 input_zip=input_zip,
307 output_zip=output_zip,
308 script=script,
309 input_tmp=OPTIONS.input_tmp)
310
Doug Zongker962069c2009-04-23 11:41:58 -0700311 if not OPTIONS.omit_prereq:
312 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700313 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700314
315 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700316 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700317
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700318 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700319
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700320 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700321 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700322
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700323 script.FormatPartition("system")
324 script.Mount("MTD", "system", "/system")
325 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700326
327 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700328 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700329
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)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700337
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700338 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700339 script.WriteRawImage("boot", "boot.img")
340
341 script.ShowProgress(0.1, 0)
342 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700343
Doug Zongker1c390a22009-05-14 19:06:36 -0700344 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700345 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700346
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700347 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700348
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):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700364 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700365
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
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700379def 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 """
Doug Zongkereef39442009-04-02 12:14:19 -0700384
385 ttemp = tf.WriteToTemp()
386 stemp = sf.WriteToTemp()
387
388 ext = os.path.splitext(tf.name)[1]
389
390 try:
391 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700392 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)
Doug Zongkereef39442009-04-02 12:14:19 -0700400 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700401 if err or p.returncode != 0:
402 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
403 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700404 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700405 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700406 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700407 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
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700425def 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
Doug Zongkereef39442009-04-02 12:14:19 -0700440def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700441 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,))
Doug Zongkereef39442009-04-02 12:14:19 -0700457
Doug Zongker05d3dea2009-06-22 11:32:31 -0700458 device_specific = common.DeviceSpecificParams(
459 source_zip=source_zip,
460 target_zip=target_zip,
461 output_zip=output_zip,
462 script=script)
463
Doug Zongkereef39442009-04-02 12:14:19 -0700464 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
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700485 diff_method = "bsdiff"
486 if tf.name.endswith(".gz"):
487 diff_method = "imgdiff"
488 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700489 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:
Doug Zongkereef39442009-04-02 12:14:19 -0700492 # 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:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700496 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700497 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
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700509 script.Mount("MTD", "system", "/system")
510 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700511
Doug Zongker5da317e2009-06-02 13:38:17 -0700512 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)
Doug Zongkereef39442009-04-02 12:14:19 -0700519
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700520 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)
Doug Zongkereef39442009-04-02 12:14:19 -0700527
Doug Zongker05d3dea2009-06-22 11:32:31 -0700528 # 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
Doug Zongkereef39442009-04-02 12:14:19 -0700532 if updating_boot:
533 progress_bar_total -= 0.1
Doug Zongkereef39442009-04-02 12:14:19 -0700534
535 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700536 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700537
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700538 script.Print("Verifying current system...")
539
Doug Zongkereef39442009-04-02 12:14:19 -0700540 pb_verify = progress_bar_total * 0.3 * \
541 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700542 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700543
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]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700547 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
548
549 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700550
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700551 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
Doug Zongker048e7ca2009-06-15 14:31:53 -0700556 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700557
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700558 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
559 (source_recovery.size, source_recovery.sha1,
560 target_recovery.size, target_recovery.sha1))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700561
Doug Zongker5da317e2009-06-02 13:38:17 -0700562 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
Doug Zongker048e7ca2009-06-15 14:31:53 -0700567 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700568
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700569 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
570 (source_boot.size, source_boot.sha1,
571 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700572
573 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700574 script.CacheFreeSpaceCheck(largest_source_size)
575 script.Print("Unpacking patches...")
576 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700577
Doug Zongker05d3dea2009-06-22 11:32:31 -0700578 device_specific.IncrementalOTA_VerifyEnd()
579
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700580 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700581
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700582 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700583 script.Print("Erasing user data...")
584 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700585
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700586 script.Print("Removing unneeded files...")
587 script.DeleteFiles(["/"+i[0] for i in verbatim_targets])
Doug Zongkereef39442009-04-02 12:14:19 -0700588
589 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700590 # 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.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700593 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")
Doug Zongkereef39442009-04-02 12:14:19 -0700600 print "boot image changed; including."
601 else:
602 print "boot image unchanged; skipping."
603
604 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700605 # Produce /system/recovery.img by applying a patch to the current
606 # contents of the recovery partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700607 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")
Doug Zongkereef39442009-04-02 12:14:19 -0700614 print "recovery image changed; including."
615 else:
616 print "recovery image unchanged; skipping."
617
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700618 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700619 pb_apply = progress_bar_total * 0.7 * \
620 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700621 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700622 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]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700625 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")
Doug Zongkereef39442009-04-02 12:14:19 -0700628
629 target_symlinks = CopySystemFiles(target_zip, None)
630
631 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700632 temp_script = script.MakeTemporary()
Doug Zongkereef39442009-04-02 12:14:19 -0700633 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)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700647 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700648
649 if verbatim_targets:
650 pb_verbatim = progress_bar_total * \
651 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700652 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700653 script.ShowProgress(pb_verbatim, 5)
654 script.Print("Unpacking new files...")
655 script.UnpackPackageDir("system", "/system")
656
Doug Zongker05d3dea2009-06-22 11:32:31 -0700657 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700658
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))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700669 script.DeleteFiles([i[1] for i in to_create])
670 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700671
672 # Now that the symlinks are created, we can set all the
673 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700674 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700675
Doug Zongker05d3dea2009-06-22 11:32:31 -0700676 # Write the radio image, if necessary.
677 script.ShowProgress(0.3, 10)
678 device_specific.IncrementalOTA_InstallEnd()
679
Doug Zongker1c390a22009-05-14 19:06:36 -0700680 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700681 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700682
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700683 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700684
685
686def main(argv):
687
688 def option_handler(o, a):
689 if o in ("-b", "--board_config"):
690 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700691 elif o in ("-k", "--package_key"):
692 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700693 elif o in ("-i", "--incremental_from"):
694 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700695 elif o in ("-w", "--wipe_user_data"):
696 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700697 elif o in ("-n", "--no_prereq"):
698 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700699 elif o in ("-e", "--extra_script"):
700 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700701 elif o in ("-m", "--script_mode"):
702 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700703 else:
704 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700705 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700706
707 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700708 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700709 extra_long_opts=["board_config=",
710 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700711 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700712 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700713 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700714 "extra_script=",
715 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700716 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
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700729 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
730 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
731
Doug Zongker1c390a22009-05-14 19:06:36 -0700732 if OPTIONS.extra_script is not None:
733 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
734
Doug Zongkereef39442009-04-02 12:14:19 -0700735 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)