blob: b3f0b37b1dafe26b2474f098ef67c668db5a907c [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 Zongker03061472009-07-13 18:36:37 -070072import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070073
74OPTIONS = common.OPTIONS
75OPTIONS.package_key = "build/target/product/security/testkey"
76OPTIONS.incremental_source = None
77OPTIONS.require_verbatim = set()
78OPTIONS.prohibit_verbatim = set(("system/build.prop",))
79OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070080OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070081OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070082OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070083OPTIONS.script_mode = 'auto'
Doug Zongkereef39442009-04-02 12:14:19 -070084
85def MostPopularKey(d, default):
86 """Given a dict, return the key corresponding to the largest
87 value. Returns 'default' if the dict is empty."""
88 x = [(v, k) for (k, v) in d.iteritems()]
89 if not x: return default
90 x.sort()
91 return x[-1][1]
92
93
94def IsSymlink(info):
95 """Return true if the zipfile.ZipInfo object passed in represents a
96 symlink."""
97 return (info.external_attr >> 16) == 0120777
98
99
100
101class Item:
102 """Items represent the metadata (user, group, mode) of files and
103 directories in the system image."""
104 ITEMS = {}
105 def __init__(self, name, dir=False):
106 self.name = name
107 self.uid = None
108 self.gid = None
109 self.mode = None
110 self.dir = dir
111
112 if name:
113 self.parent = Item.Get(os.path.dirname(name), dir=True)
114 self.parent.children.append(self)
115 else:
116 self.parent = None
117 if dir:
118 self.children = []
119
120 def Dump(self, indent=0):
121 if self.uid is not None:
122 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
123 else:
124 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
125 if self.dir:
126 print "%s%s" % (" "*indent, self.descendants)
127 print "%s%s" % (" "*indent, self.best_subtree)
128 for i in self.children:
129 i.Dump(indent=indent+1)
130
131 @classmethod
132 def Get(cls, name, dir=False):
133 if name not in cls.ITEMS:
134 cls.ITEMS[name] = Item(name, dir=dir)
135 return cls.ITEMS[name]
136
137 @classmethod
138 def GetMetadata(cls):
139 """Run the external 'fs_config' program to determine the desired
140 uid, gid, and mode for every Item object."""
141 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
142 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
143 suffix = { False: "", True: "/" }
144 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
145 for i in cls.ITEMS.itervalues() if i.name])
146 output, error = p.communicate(input)
147 assert not error
148
149 for line in output.split("\n"):
150 if not line: continue
151 name, uid, gid, mode = line.split()
152 i = cls.ITEMS[name]
153 i.uid = int(uid)
154 i.gid = int(gid)
155 i.mode = int(mode, 8)
156 if i.dir:
157 i.children.sort(key=lambda i: i.name)
158
159 def CountChildMetadata(self):
160 """Count up the (uid, gid, mode) tuples for all children and
161 determine the best strategy for using set_perm_recursive and
162 set_perm to correctly chown/chmod all the files to their desired
163 values. Recursively calls itself for all descendants.
164
165 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
166 all descendants of this node. (dmode or fmode may be None.) Also
167 sets the best_subtree of each directory Item to the (uid, gid,
168 dmode, fmode) tuple that will match the most descendants of that
169 Item.
170 """
171
172 assert self.dir
173 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
174 for i in self.children:
175 if i.dir:
176 for k, v in i.CountChildMetadata().iteritems():
177 d[k] = d.get(k, 0) + v
178 else:
179 k = (i.uid, i.gid, None, i.mode)
180 d[k] = d.get(k, 0) + 1
181
182 # Find the (uid, gid, dmode, fmode) tuple that matches the most
183 # descendants.
184
185 # First, find the (uid, gid) pair that matches the most
186 # descendants.
187 ug = {}
188 for (uid, gid, _, _), count in d.iteritems():
189 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
190 ug = MostPopularKey(ug, (0, 0))
191
192 # Now find the dmode and fmode that match the most descendants
193 # with that (uid, gid), and choose those.
194 best_dmode = (0, 0755)
195 best_fmode = (0, 0644)
196 for k, count in d.iteritems():
197 if k[:2] != ug: continue
198 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
199 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
200 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
201
202 return d
203
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700204 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700205 """Append set_perm/set_perm_recursive commands to 'script' to
206 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700207 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700208
209 self.CountChildMetadata()
210
211 def recurse(item, current):
212 # current is the (uid, gid, dmode, fmode) tuple that the current
213 # item (and all its children) have already been set to. We only
214 # need to issue set_perm/set_perm_recursive commands if we're
215 # supposed to be something different.
216 if item.dir:
217 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700218 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700219 current = item.best_subtree
220
221 if item.uid != current[0] or item.gid != current[1] or \
222 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700223 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700224
225 for i in item.children:
226 recurse(i, current)
227 else:
228 if item.uid != current[0] or item.gid != current[1] or \
229 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700230 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700231
232 recurse(self, (-1, -1, -1, -1))
233
234
235def CopySystemFiles(input_zip, output_zip=None,
236 substitute=None):
237 """Copies files underneath system/ in the input zip to the output
238 zip. Populates the Item class with their metadata, and returns a
239 list of symlinks. output_zip may be None, in which case the copy is
240 skipped (but the other side effects still happen). substitute is an
241 optional dict of {output filename: contents} to be output instead of
242 certain input files.
243 """
244
245 symlinks = []
246
247 for info in input_zip.infolist():
248 if info.filename.startswith("SYSTEM/"):
249 basefilename = info.filename[7:]
250 if IsSymlink(info):
251 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700252 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700253 else:
254 info2 = copy.copy(info)
255 fn = info2.filename = "system/" + basefilename
256 if substitute and fn in substitute and substitute[fn] is None:
257 continue
258 if output_zip is not None:
259 if substitute and fn in substitute:
260 data = substitute[fn]
261 else:
262 data = input_zip.read(info.filename)
263 output_zip.writestr(info2, data)
264 if fn.endswith("/"):
265 Item.Get(fn[:-1], dir=True)
266 else:
267 Item.Get(fn, dir=False)
268
269 symlinks.sort()
270 return symlinks
271
272
Doug Zongkereef39442009-04-02 12:14:19 -0700273def 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
Doug Zongkereef39442009-04-02 12:14:19 -0700280def FixPermissions(script):
281 Item.GetMetadata()
282 root = Item.Get("system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700283 root.SetPermissions(script)
Doug Zongkereef39442009-04-02 12:14:19 -0700284
Doug Zongkereef39442009-04-02 12:14:19 -0700285
286def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700287 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700288 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700289
290 info = input_zip.read("OTA/android-info.txt")
291 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker171f1cd2009-06-15 22:36:37 -0700292 if m:
293 bootloaders = m.group(1).split("|")
Doug Zongker0493e242009-07-22 09:28:25 -0700294 if "*" not in bootloaders:
295 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700296
297
298def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700299 if OPTIONS.script_mode == "auto":
300 script = both_generator.BothGenerator(2)
301 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700302 script = amend_generator.AmendGenerator()
303 else:
304 # TODO: how to determine this? We don't know what version it will
305 # be installed on top of. For now, we expect the API just won't
306 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700307 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700308
Doug Zongker05d3dea2009-06-22 11:32:31 -0700309 device_specific = common.DeviceSpecificParams(
310 input_zip=input_zip,
311 output_zip=output_zip,
312 script=script,
313 input_tmp=OPTIONS.input_tmp)
314
Doug Zongker962069c2009-04-23 11:41:58 -0700315 if not OPTIONS.omit_prereq:
316 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700317 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700318
319 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700320 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700321
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700322 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700323
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700324 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700325 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700326
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700327 script.FormatPartition("system")
328 script.Mount("MTD", "system", "/system")
329 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700330
331 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700332 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700333
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700334 if common.BuildAndAddBootableImage(
335 os.path.join(OPTIONS.input_tmp, "RECOVERY"),
336 "system/recovery.img", output_zip):
337 Item.Get("system/recovery.img", dir=False)
Doug Zongkereef39442009-04-02 12:14:19 -0700338
339 FixPermissions(script)
340
341 common.AddBoot(output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700342
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700343 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700344 script.WriteRawImage("boot", "boot.img")
345
346 script.ShowProgress(0.1, 0)
347 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700348
Doug Zongker1c390a22009-05-14 19:06:36 -0700349 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700350 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700351
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700352 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700353
354
355class File(object):
356 def __init__(self, name, data):
357 self.name = name
358 self.data = data
359 self.size = len(data)
360 self.sha1 = sha.sha(data).hexdigest()
361
362 def WriteToTemp(self):
363 t = tempfile.NamedTemporaryFile()
364 t.write(self.data)
365 t.flush()
366 return t
367
368 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700369 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700370
371
372def LoadSystemFiles(z):
373 """Load all the files from SYSTEM/... in a given target-files
374 ZipFile, and return a dict of {filename: File object}."""
375 out = {}
376 for info in z.infolist():
377 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
378 fn = "system/" + info.filename[7:]
379 data = z.read(info.filename)
380 out[fn] = File(fn, data)
381 return out
382
383
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700384def Difference(tf, sf, diff_program):
385 """Return the patch (as a string of data) needed to turn sf into tf.
386 diff_program is the name of an external program (or list, if
387 additional arguments are desired) to run to generate the diff.
388 """
Doug Zongkereef39442009-04-02 12:14:19 -0700389
390 ttemp = tf.WriteToTemp()
391 stemp = sf.WriteToTemp()
392
393 ext = os.path.splitext(tf.name)[1]
394
395 try:
396 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700397 if isinstance(diff_program, list):
398 cmd = copy.copy(diff_program)
399 else:
400 cmd = [diff_program]
401 cmd.append(stemp.name)
402 cmd.append(ttemp.name)
403 cmd.append(ptemp.name)
404 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700405 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700406 if err or p.returncode != 0:
407 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
408 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700409 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700410 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700411 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700412 stemp.close()
413 ttemp.close()
414
415 return diff
416
417
418def GetBuildProp(property, z):
419 """Return the fingerprint of the build of a given target-files
420 ZipFile object."""
421 bp = z.read("SYSTEM/build.prop")
422 if not property:
423 return bp
424 m = re.search(re.escape(property) + r"=(.*)\n", bp)
425 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700426 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700427 return m.group(1).strip()
428
429
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700430def GetRecoveryAPIVersion(zip):
431 """Returns the version of the recovery API. Version 0 is the older
432 amend code (no separate binary)."""
433 try:
434 version = zip.read("META/recovery-api-version.txt")
435 return int(version)
436 except KeyError:
437 try:
438 # version one didn't have the recovery-api-version.txt file, but
439 # it did include an updater binary.
440 zip.getinfo("OTA/bin/updater")
441 return 1
442 except KeyError:
443 return 0
444
Doug Zongkereef39442009-04-02 12:14:19 -0700445def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700446 source_version = GetRecoveryAPIVersion(source_zip)
447
448 if OPTIONS.script_mode == 'amend':
449 script = amend_generator.AmendGenerator()
450 elif OPTIONS.script_mode == 'edify':
451 if source_version == 0:
452 print ("WARNING: generating edify script for a source that "
453 "can't install it.")
454 script = edify_generator.EdifyGenerator(source_version)
455 elif OPTIONS.script_mode == 'auto':
456 if source_version > 0:
457 script = edify_generator.EdifyGenerator(source_version)
458 else:
459 script = amend_generator.AmendGenerator()
460 else:
461 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700462
Doug Zongker05d3dea2009-06-22 11:32:31 -0700463 device_specific = common.DeviceSpecificParams(
464 source_zip=source_zip,
465 target_zip=target_zip,
466 output_zip=output_zip,
467 script=script)
468
Doug Zongkereef39442009-04-02 12:14:19 -0700469 print "Loading target..."
470 target_data = LoadSystemFiles(target_zip)
471 print "Loading source..."
472 source_data = LoadSystemFiles(source_zip)
473
474 verbatim_targets = []
475 patch_list = []
476 largest_source_size = 0
477 for fn in sorted(target_data.keys()):
478 tf = target_data[fn]
479 sf = source_data.get(fn, None)
480
481 if sf is None or fn in OPTIONS.require_verbatim:
482 # This file should be included verbatim
483 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700484 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700485 print "send", fn, "verbatim"
486 tf.AddToZip(output_zip)
487 verbatim_targets.append((fn, tf.size))
488 elif tf.sha1 != sf.sha1:
489 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700490 diff_method = "bsdiff"
491 if tf.name.endswith(".gz"):
492 diff_method = "imgdiff"
493 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700494 if d is not None:
495 print fn, tf.size, len(d), (float(len(d)) / tf.size)
496 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700497 # patch is almost as big as the file; don't bother patching
498 tf.AddToZip(output_zip)
499 verbatim_targets.append((fn, tf.size))
500 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700501 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700502 patch_list.append((fn, tf, sf, tf.size))
503 largest_source_size = max(largest_source_size, sf.size)
504 else:
505 # Target file identical to source.
506 pass
507
508 total_verbatim_size = sum([i[1] for i in verbatim_targets])
509 total_patched_size = sum([i[3] for i in patch_list])
510
511 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
512 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
513
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700514 script.Mount("MTD", "system", "/system")
515 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700516
Doug Zongker5da317e2009-06-02 13:38:17 -0700517 source_boot = File("/tmp/boot.img",
518 common.BuildBootableImage(
519 os.path.join(OPTIONS.source_tmp, "BOOT")))
520 target_boot = File("/tmp/boot.img",
521 common.BuildBootableImage(
522 os.path.join(OPTIONS.target_tmp, "BOOT")))
523 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700524
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700525 source_recovery = File("system/recovery.img",
526 common.BuildBootableImage(
527 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
528 target_recovery = File("system/recovery.img",
529 common.BuildBootableImage(
530 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
531 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700532
Doug Zongker05d3dea2009-06-22 11:32:31 -0700533 # We reserve the last 0.3 of the progress bar for the
534 # device-specific IncrementalOTA_InstallEnd() call at the end, which
535 # will typically install a radio image.
536 progress_bar_total = 0.7
Doug Zongkereef39442009-04-02 12:14:19 -0700537 if updating_boot:
538 progress_bar_total -= 0.1
Doug Zongkereef39442009-04-02 12:14:19 -0700539
540 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700541 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700542
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700543 script.Print("Verifying current system...")
544
Doug Zongkereef39442009-04-02 12:14:19 -0700545 pb_verify = progress_bar_total * 0.3 * \
546 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700547 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700548
549 for i, (fn, tf, sf, size) in enumerate(patch_list):
550 if i % 5 == 0:
551 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700552 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
553
554 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700555
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700556 if updating_recovery:
557 d = Difference(target_recovery, source_recovery, "imgdiff")
558 print "recovery target: %d source: %d diff: %d" % (
559 target_recovery.size, source_recovery.size, len(d))
560
Doug Zongker048e7ca2009-06-15 14:31:53 -0700561 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700562
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700563 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
564 (source_recovery.size, source_recovery.sha1,
565 target_recovery.size, target_recovery.sha1))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700566
Doug Zongker5da317e2009-06-02 13:38:17 -0700567 if updating_boot:
568 d = Difference(target_boot, source_boot, "imgdiff")
569 print "boot target: %d source: %d diff: %d" % (
570 target_boot.size, source_boot.size, len(d))
571
Doug Zongker048e7ca2009-06-15 14:31:53 -0700572 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700573
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700574 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
575 (source_boot.size, source_boot.sha1,
576 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700577
578 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700579 script.CacheFreeSpaceCheck(largest_source_size)
580 script.Print("Unpacking patches...")
581 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700582
Doug Zongker05d3dea2009-06-22 11:32:31 -0700583 device_specific.IncrementalOTA_VerifyEnd()
584
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700585 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700587 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700588 script.Print("Erasing user data...")
589 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700590
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700591 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700592 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
593 ["/"+i for i in sorted(source_data)
594 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700595
596 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700597 # Produce the boot image by applying a patch to the current
598 # contents of the boot partition, and write it back to the
599 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700600 script.Print("Patching boot image...")
601 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
602 % (source_boot.size, source_boot.sha1,
603 target_boot.size, target_boot.sha1),
604 "-",
605 target_boot.size, target_boot.sha1,
606 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700607 print "boot image changed; including."
608 else:
609 print "boot image unchanged; skipping."
610
611 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700612 # Produce /system/recovery.img by applying a patch to the current
613 # contents of the recovery partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700614 script.Print("Patching recovery image...")
615 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s"
616 % (source_recovery.size, source_recovery.sha1,
617 target_recovery.size, target_recovery.sha1),
618 "/system/recovery.img",
619 target_recovery.size, target_recovery.sha1,
620 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700621 print "recovery image changed; including."
622 else:
623 print "recovery image unchanged; skipping."
624
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700625 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700626 pb_apply = progress_bar_total * 0.7 * \
627 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700628 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700629 for i, (fn, tf, sf, size) in enumerate(patch_list):
630 if i % 5 == 0:
631 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700632 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
633 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
634 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700635
636 target_symlinks = CopySystemFiles(target_zip, None)
637
638 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700639 temp_script = script.MakeTemporary()
Doug Zongkereef39442009-04-02 12:14:19 -0700640 FixPermissions(temp_script)
641
642 # Note that this call will mess up the tree of Items, so make sure
643 # we're done with it.
644 source_symlinks = CopySystemFiles(source_zip, None)
645 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
646
647 # Delete all the symlinks in source that aren't in target. This
648 # needs to happen before verbatim files are unpacked, in case a
649 # symlink in the source is replaced by a real file in the target.
650 to_delete = []
651 for dest, link in source_symlinks:
652 if link not in target_symlinks_d:
653 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700654 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700655
656 if verbatim_targets:
657 pb_verbatim = progress_bar_total * \
658 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700659 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700660 script.ShowProgress(pb_verbatim, 5)
661 script.Print("Unpacking new files...")
662 script.UnpackPackageDir("system", "/system")
663
Doug Zongker05d3dea2009-06-22 11:32:31 -0700664 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700665
666 # Create all the symlinks that don't already exist, or point to
667 # somewhere different than what we want. Delete each symlink before
668 # creating it, since the 'symlink' command won't overwrite.
669 to_create = []
670 for dest, link in target_symlinks:
671 if link in source_symlinks_d:
672 if dest != source_symlinks_d[link]:
673 to_create.append((dest, link))
674 else:
675 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700676 script.DeleteFiles([i[1] for i in to_create])
677 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700678
679 # Now that the symlinks are created, we can set all the
680 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700681 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700682
Doug Zongker05d3dea2009-06-22 11:32:31 -0700683 # Write the radio image, if necessary.
684 script.ShowProgress(0.3, 10)
685 device_specific.IncrementalOTA_InstallEnd()
686
Doug Zongker1c390a22009-05-14 19:06:36 -0700687 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700688 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700689
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700690 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700691
692
693def main(argv):
694
695 def option_handler(o, a):
696 if o in ("-b", "--board_config"):
697 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700698 elif o in ("-k", "--package_key"):
699 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700700 elif o in ("-i", "--incremental_from"):
701 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700702 elif o in ("-w", "--wipe_user_data"):
703 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700704 elif o in ("-n", "--no_prereq"):
705 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700706 elif o in ("-e", "--extra_script"):
707 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700708 elif o in ("-m", "--script_mode"):
709 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700710 else:
711 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700712 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700713
714 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700715 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700716 extra_long_opts=["board_config=",
717 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700718 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700719 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700720 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700721 "extra_script=",
722 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700723 extra_option_handler=option_handler)
724
725 if len(args) != 2:
726 common.Usage(__doc__)
727 sys.exit(1)
728
729 if not OPTIONS.max_image_size:
730 print
731 print " WARNING: No board config specified; will not check image"
732 print " sizes against limits. Use -b to make sure the generated"
733 print " images don't exceed partition sizes."
734 print
735
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700736 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
737 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
738
Doug Zongker1c390a22009-05-14 19:06:36 -0700739 if OPTIONS.extra_script is not None:
740 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
741
Doug Zongkereef39442009-04-02 12:14:19 -0700742 print "unzipping target target-files..."
743 OPTIONS.input_tmp = common.UnzipTemp(args[0])
744 OPTIONS.target_tmp = OPTIONS.input_tmp
745 input_zip = zipfile.ZipFile(args[0], "r")
746 if OPTIONS.package_key:
747 temp_zip_file = tempfile.NamedTemporaryFile()
748 output_zip = zipfile.ZipFile(temp_zip_file, "w",
749 compression=zipfile.ZIP_DEFLATED)
750 else:
751 output_zip = zipfile.ZipFile(args[1], "w",
752 compression=zipfile.ZIP_DEFLATED)
753
754 if OPTIONS.incremental_source is None:
755 WriteFullOTAPackage(input_zip, output_zip)
756 else:
757 print "unzipping source target-files..."
758 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
759 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
760 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
761
762 output_zip.close()
763 if OPTIONS.package_key:
764 SignOutput(temp_zip_file.name, args[1])
765 temp_zip_file.close()
766
767 common.Cleanup()
768
769 print "done."
770
771
772if __name__ == '__main__':
773 try:
774 main(sys.argv[1:])
775 except common.ExternalError, e:
776 print
777 print " ERROR: %s" % (e,)
778 print
779 sys.exit(1)