blob: 4b7ee03fea84f4cc07771517fc34030c41b6308e [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 Zongker9fc74c72009-06-23 16:27:38 -0700292 if m:
293 bootloaders = m.group(1).split("|")
294 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700295
296
297def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700298 if OPTIONS.script_mode == "auto":
299 script = both_generator.BothGenerator(2)
300 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700301 script = amend_generator.AmendGenerator()
302 else:
303 # TODO: how to determine this? We don't know what version it will
304 # be installed on top of. For now, we expect the API just won't
305 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700306 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700307
Doug Zongker962069c2009-04-23 11:41:58 -0700308 if not OPTIONS.omit_prereq:
309 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700310 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700311
312 AppendAssertions(script, input_zip)
313
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700314 script.ShowProgress(0.1, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700315
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700316 try:
317 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
318 script.WriteFirmwareImage("radio", "radio.img")
319 except KeyError:
320 print "warning: no radio image in input target_files; not flashing radio"
321
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 script.ShowProgress(0.2, 0)
343
344 script.WriteRawImage("boot", "boot.img")
345 script.ShowProgress(0.2, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700346
Doug Zongker1c390a22009-05-14 19:06:36 -0700347 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700348 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700349
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700350 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700351
352
353class File(object):
354 def __init__(self, name, data):
355 self.name = name
356 self.data = data
357 self.size = len(data)
358 self.sha1 = sha.sha(data).hexdigest()
359
360 def WriteToTemp(self):
361 t = tempfile.NamedTemporaryFile()
362 t.write(self.data)
363 t.flush()
364 return t
365
366 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700367 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700368
369
370def LoadSystemFiles(z):
371 """Load all the files from SYSTEM/... in a given target-files
372 ZipFile, and return a dict of {filename: File object}."""
373 out = {}
374 for info in z.infolist():
375 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
376 fn = "system/" + info.filename[7:]
377 data = z.read(info.filename)
378 out[fn] = File(fn, data)
379 return out
380
381
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700382def Difference(tf, sf, diff_program):
383 """Return the patch (as a string of data) needed to turn sf into tf.
384 diff_program is the name of an external program (or list, if
385 additional arguments are desired) to run to generate the diff.
386 """
Doug Zongkereef39442009-04-02 12:14:19 -0700387
388 ttemp = tf.WriteToTemp()
389 stemp = sf.WriteToTemp()
390
391 ext = os.path.splitext(tf.name)[1]
392
393 try:
394 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700395 if isinstance(diff_program, list):
396 cmd = copy.copy(diff_program)
397 else:
398 cmd = [diff_program]
399 cmd.append(stemp.name)
400 cmd.append(ttemp.name)
401 cmd.append(ptemp.name)
402 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700403 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700404 if err or p.returncode != 0:
405 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
406 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700407 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700408 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700409 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700410 stemp.close()
411 ttemp.close()
412
413 return diff
414
415
416def GetBuildProp(property, z):
417 """Return the fingerprint of the build of a given target-files
418 ZipFile object."""
419 bp = z.read("SYSTEM/build.prop")
420 if not property:
421 return bp
422 m = re.search(re.escape(property) + r"=(.*)\n", bp)
423 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700424 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700425 return m.group(1).strip()
426
427
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700428def GetRecoveryAPIVersion(zip):
429 """Returns the version of the recovery API. Version 0 is the older
430 amend code (no separate binary)."""
431 try:
432 version = zip.read("META/recovery-api-version.txt")
433 return int(version)
434 except KeyError:
435 try:
436 # version one didn't have the recovery-api-version.txt file, but
437 # it did include an updater binary.
438 zip.getinfo("OTA/bin/updater")
439 return 1
440 except KeyError:
441 return 0
442
Doug Zongkereef39442009-04-02 12:14:19 -0700443def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700444 source_version = GetRecoveryAPIVersion(source_zip)
445
446 if OPTIONS.script_mode == 'amend':
447 script = amend_generator.AmendGenerator()
448 elif OPTIONS.script_mode == 'edify':
449 if source_version == 0:
450 print ("WARNING: generating edify script for a source that "
451 "can't install it.")
452 script = edify_generator.EdifyGenerator(source_version)
453 elif OPTIONS.script_mode == 'auto':
454 if source_version > 0:
455 script = edify_generator.EdifyGenerator(source_version)
456 else:
457 script = amend_generator.AmendGenerator()
458 else:
459 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700460
461 print "Loading target..."
462 target_data = LoadSystemFiles(target_zip)
463 print "Loading source..."
464 source_data = LoadSystemFiles(source_zip)
465
466 verbatim_targets = []
467 patch_list = []
468 largest_source_size = 0
469 for fn in sorted(target_data.keys()):
470 tf = target_data[fn]
471 sf = source_data.get(fn, None)
472
473 if sf is None or fn in OPTIONS.require_verbatim:
474 # This file should be included verbatim
475 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700476 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700477 print "send", fn, "verbatim"
478 tf.AddToZip(output_zip)
479 verbatim_targets.append((fn, tf.size))
480 elif tf.sha1 != sf.sha1:
481 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700482 diff_method = "bsdiff"
483 if tf.name.endswith(".gz"):
484 diff_method = "imgdiff"
485 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700486 if d is not None:
487 print fn, tf.size, len(d), (float(len(d)) / tf.size)
488 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700489 # patch is almost as big as the file; don't bother patching
490 tf.AddToZip(output_zip)
491 verbatim_targets.append((fn, tf.size))
492 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700493 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700494 patch_list.append((fn, tf, sf, tf.size))
495 largest_source_size = max(largest_source_size, sf.size)
496 else:
497 # Target file identical to source.
498 pass
499
500 total_verbatim_size = sum([i[1] for i in verbatim_targets])
501 total_patched_size = sum([i[3] for i in patch_list])
502
503 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
504 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
505
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700506 script.Mount("MTD", "system", "/system")
507 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700508
Doug Zongker5da317e2009-06-02 13:38:17 -0700509 source_boot = File("/tmp/boot.img",
510 common.BuildBootableImage(
511 os.path.join(OPTIONS.source_tmp, "BOOT")))
512 target_boot = File("/tmp/boot.img",
513 common.BuildBootableImage(
514 os.path.join(OPTIONS.target_tmp, "BOOT")))
515 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700516
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700517 source_recovery = File("system/recovery.img",
518 common.BuildBootableImage(
519 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
520 target_recovery = File("system/recovery.img",
521 common.BuildBootableImage(
522 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
523 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700524
525 source_radio = source_zip.read("RADIO/image")
526 target_radio = target_zip.read("RADIO/image")
527 updating_radio = (source_radio != target_radio)
528
529 # The last 0.1 is reserved for creating symlinks, fixing
530 # permissions, and writing the boot image (if necessary).
531 progress_bar_total = 1.0
532 if updating_boot:
533 progress_bar_total -= 0.1
534 if updating_radio:
535 progress_bar_total -= 0.3
536
537 AppendAssertions(script, target_zip)
538
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700539 script.Print("Verifying current system...")
540
Doug Zongkereef39442009-04-02 12:14:19 -0700541 pb_verify = progress_bar_total * 0.3 * \
542 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700543 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700544
545 for i, (fn, tf, sf, size) in enumerate(patch_list):
546 if i % 5 == 0:
547 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700548 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
549
550 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700551
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700552 if updating_recovery:
553 d = Difference(target_recovery, source_recovery, "imgdiff")
554 print "recovery target: %d source: %d diff: %d" % (
555 target_recovery.size, source_recovery.size, len(d))
556
Doug Zongker048e7ca2009-06-15 14:31:53 -0700557 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700558
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700559 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
560 (source_recovery.size, source_recovery.sha1,
561 target_recovery.size, target_recovery.sha1))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700562
Doug Zongker5da317e2009-06-02 13:38:17 -0700563 if updating_boot:
564 d = Difference(target_boot, source_boot, "imgdiff")
565 print "boot target: %d source: %d diff: %d" % (
566 target_boot.size, source_boot.size, len(d))
567
Doug Zongker048e7ca2009-06-15 14:31:53 -0700568 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700569
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700570 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
571 (source_boot.size, source_boot.sha1,
572 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700573
574 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700575 script.CacheFreeSpaceCheck(largest_source_size)
576 script.Print("Unpacking patches...")
577 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700578
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700579 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700581 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700582 script.Print("Erasing user data...")
583 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700584
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700585 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700586 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
587 ["/"+i for i in sorted(source_data)
588 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700589
590 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700591 # Produce the boot image by applying a patch to the current
592 # contents of the boot partition, and write it back to the
593 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700594 script.Print("Patching boot image...")
595 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
596 % (source_boot.size, source_boot.sha1,
597 target_boot.size, target_boot.sha1),
598 "-",
599 target_boot.size, target_boot.sha1,
600 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700601 print "boot image changed; including."
602 else:
603 print "boot image unchanged; skipping."
604
605 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700606 # Produce /system/recovery.img by applying a patch to the current
607 # contents of the recovery partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700608 script.Print("Patching recovery image...")
609 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s"
610 % (source_recovery.size, source_recovery.sha1,
611 target_recovery.size, target_recovery.sha1),
612 "/system/recovery.img",
613 target_recovery.size, target_recovery.sha1,
614 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700615 print "recovery image changed; including."
616 else:
617 print "recovery image unchanged; skipping."
618
619 if updating_radio:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700620 script.ShowProgress(0.3, 10)
621 script.Print("Writing radio image...")
622 script.WriteFirmwareImage("radio", "radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700623 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700624 print "radio image changed; including."
625 else:
626 print "radio image unchanged; skipping."
627
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700628 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700629 pb_apply = progress_bar_total * 0.7 * \
630 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700631 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700632 for i, (fn, tf, sf, size) in enumerate(patch_list):
633 if i % 5 == 0:
634 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700635 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
636 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
637 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700638
639 target_symlinks = CopySystemFiles(target_zip, None)
640
641 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700642 temp_script = script.MakeTemporary()
Doug Zongkereef39442009-04-02 12:14:19 -0700643 FixPermissions(temp_script)
644
645 # Note that this call will mess up the tree of Items, so make sure
646 # we're done with it.
647 source_symlinks = CopySystemFiles(source_zip, None)
648 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
649
650 # Delete all the symlinks in source that aren't in target. This
651 # needs to happen before verbatim files are unpacked, in case a
652 # symlink in the source is replaced by a real file in the target.
653 to_delete = []
654 for dest, link in source_symlinks:
655 if link not in target_symlinks_d:
656 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700657 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700658
659 if verbatim_targets:
660 pb_verbatim = progress_bar_total * \
661 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700662 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700663 script.ShowProgress(pb_verbatim, 5)
664 script.Print("Unpacking new files...")
665 script.UnpackPackageDir("system", "/system")
666
667 script.Print("Finishing up...")
Doug Zongkereef39442009-04-02 12:14:19 -0700668
669 # Create all the symlinks that don't already exist, or point to
670 # somewhere different than what we want. Delete each symlink before
671 # creating it, since the 'symlink' command won't overwrite.
672 to_create = []
673 for dest, link in target_symlinks:
674 if link in source_symlinks_d:
675 if dest != source_symlinks_d[link]:
676 to_create.append((dest, link))
677 else:
678 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 script.DeleteFiles([i[1] for i in to_create])
680 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700681
682 # Now that the symlinks are created, we can set all the
683 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700684 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700685
Doug Zongker1c390a22009-05-14 19:06:36 -0700686 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700687 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700688
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700689 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700690
691
692def main(argv):
693
694 def option_handler(o, a):
695 if o in ("-b", "--board_config"):
696 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700697 elif o in ("-k", "--package_key"):
698 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700699 elif o in ("-i", "--incremental_from"):
700 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700701 elif o in ("-w", "--wipe_user_data"):
702 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700703 elif o in ("-n", "--no_prereq"):
704 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700705 elif o in ("-e", "--extra_script"):
706 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700707 elif o in ("-m", "--script_mode"):
708 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700709 else:
710 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700711 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700712
713 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700714 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700715 extra_long_opts=["board_config=",
716 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700717 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700718 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700719 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700720 "extra_script=",
721 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700722 extra_option_handler=option_handler)
723
724 if len(args) != 2:
725 common.Usage(__doc__)
726 sys.exit(1)
727
728 if not OPTIONS.max_image_size:
729 print
730 print " WARNING: No board config specified; will not check image"
731 print " sizes against limits. Use -b to make sure the generated"
732 print " images don't exceed partition sizes."
733 print
734
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700735 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
736 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
737
Doug Zongker1c390a22009-05-14 19:06:36 -0700738 if OPTIONS.extra_script is not None:
739 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
740
Doug Zongkereef39442009-04-02 12:14:19 -0700741 print "unzipping target target-files..."
742 OPTIONS.input_tmp = common.UnzipTemp(args[0])
743 OPTIONS.target_tmp = OPTIONS.input_tmp
744 input_zip = zipfile.ZipFile(args[0], "r")
745 if OPTIONS.package_key:
746 temp_zip_file = tempfile.NamedTemporaryFile()
747 output_zip = zipfile.ZipFile(temp_zip_file, "w",
748 compression=zipfile.ZIP_DEFLATED)
749 else:
750 output_zip = zipfile.ZipFile(args[1], "w",
751 compression=zipfile.ZIP_DEFLATED)
752
753 if OPTIONS.incremental_source is None:
754 WriteFullOTAPackage(input_zip, output_zip)
755 else:
756 print "unzipping source target-files..."
757 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
758 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
759 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
760
761 output_zip.close()
762 if OPTIONS.package_key:
763 SignOutput(temp_zip_file.name, args[1])
764 temp_zip_file.close()
765
766 common.Cleanup()
767
768 print "done."
769
770
771if __name__ == '__main__':
772 try:
773 main(sys.argv[1:])
774 except common.ExternalError, e:
775 print
776 print " ERROR: %s" % (e,)
777 print
778 sys.exit(1)