blob: 1a6ebbf73bf4d6a477eb0bd9821a233fcf586a10 [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)
291 if not m:
292 raise ExternalError("failed to find required bootloaders in "
293 "android-info.txt")
294 bootloaders = m.group(1).split("|")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700295 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700296
297
298def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700299 if OPTIONS.script_mode in ("amend", "auto"):
300 script = amend_generator.AmendGenerator()
301 else:
302 # TODO: how to determine this? We don't know what version it will
303 # be installed on top of. For now, we expect the API just won't
304 # change very often.
305 script = edify_generator.EdifyGenerator(1)
Doug Zongkereef39442009-04-02 12:14:19 -0700306
Doug Zongker962069c2009-04-23 11:41:58 -0700307 if not OPTIONS.omit_prereq:
308 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700309 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700310
311 AppendAssertions(script, input_zip)
312
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700313 script.ShowProgress(0.1, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700314
Doug Zongker048e7ca2009-06-15 14:31:53 -0700315 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700316 script.WriteFirmwareImage("radio", "radio.img")
317 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700318
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700319 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700320 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700321
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700322 script.FormatPartition("system")
323 script.Mount("MTD", "system", "/system")
324 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700325
326 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700327 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700328
329 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
330 "system/recovery.img", output_zip)
331 Item.Get("system/recovery.img", dir=False)
332
333 FixPermissions(script)
334
335 common.AddBoot(output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700336 script.ShowProgress(0.2, 0)
337
338 script.WriteRawImage("boot", "boot.img")
339 script.ShowProgress(0.2, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Doug Zongker1c390a22009-05-14 19:06:36 -0700341 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700342 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700343
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700344 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700345
346
347class File(object):
348 def __init__(self, name, data):
349 self.name = name
350 self.data = data
351 self.size = len(data)
352 self.sha1 = sha.sha(data).hexdigest()
353
354 def WriteToTemp(self):
355 t = tempfile.NamedTemporaryFile()
356 t.write(self.data)
357 t.flush()
358 return t
359
360 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700361 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700362
363
364def LoadSystemFiles(z):
365 """Load all the files from SYSTEM/... in a given target-files
366 ZipFile, and return a dict of {filename: File object}."""
367 out = {}
368 for info in z.infolist():
369 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
370 fn = "system/" + info.filename[7:]
371 data = z.read(info.filename)
372 out[fn] = File(fn, data)
373 return out
374
375
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700376def Difference(tf, sf, diff_program):
377 """Return the patch (as a string of data) needed to turn sf into tf.
378 diff_program is the name of an external program (or list, if
379 additional arguments are desired) to run to generate the diff.
380 """
Doug Zongkereef39442009-04-02 12:14:19 -0700381
382 ttemp = tf.WriteToTemp()
383 stemp = sf.WriteToTemp()
384
385 ext = os.path.splitext(tf.name)[1]
386
387 try:
388 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700389 if isinstance(diff_program, list):
390 cmd = copy.copy(diff_program)
391 else:
392 cmd = [diff_program]
393 cmd.append(stemp.name)
394 cmd.append(ttemp.name)
395 cmd.append(ptemp.name)
396 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700397 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700398 if err or p.returncode != 0:
399 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
400 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700401 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700402 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700403 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700404 stemp.close()
405 ttemp.close()
406
407 return diff
408
409
410def GetBuildProp(property, z):
411 """Return the fingerprint of the build of a given target-files
412 ZipFile object."""
413 bp = z.read("SYSTEM/build.prop")
414 if not property:
415 return bp
416 m = re.search(re.escape(property) + r"=(.*)\n", bp)
417 if not m:
418 raise ExternalException("couldn't find %s in build.prop" % (property,))
419 return m.group(1).strip()
420
421
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700422def GetRecoveryAPIVersion(zip):
423 """Returns the version of the recovery API. Version 0 is the older
424 amend code (no separate binary)."""
425 try:
426 version = zip.read("META/recovery-api-version.txt")
427 return int(version)
428 except KeyError:
429 try:
430 # version one didn't have the recovery-api-version.txt file, but
431 # it did include an updater binary.
432 zip.getinfo("OTA/bin/updater")
433 return 1
434 except KeyError:
435 return 0
436
Doug Zongkereef39442009-04-02 12:14:19 -0700437def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700438 source_version = GetRecoveryAPIVersion(source_zip)
439
440 if OPTIONS.script_mode == 'amend':
441 script = amend_generator.AmendGenerator()
442 elif OPTIONS.script_mode == 'edify':
443 if source_version == 0:
444 print ("WARNING: generating edify script for a source that "
445 "can't install it.")
446 script = edify_generator.EdifyGenerator(source_version)
447 elif OPTIONS.script_mode == 'auto':
448 if source_version > 0:
449 script = edify_generator.EdifyGenerator(source_version)
450 else:
451 script = amend_generator.AmendGenerator()
452 else:
453 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700454
455 print "Loading target..."
456 target_data = LoadSystemFiles(target_zip)
457 print "Loading source..."
458 source_data = LoadSystemFiles(source_zip)
459
460 verbatim_targets = []
461 patch_list = []
462 largest_source_size = 0
463 for fn in sorted(target_data.keys()):
464 tf = target_data[fn]
465 sf = source_data.get(fn, None)
466
467 if sf is None or fn in OPTIONS.require_verbatim:
468 # This file should be included verbatim
469 if fn in OPTIONS.prohibit_verbatim:
470 raise ExternalError("\"%s\" must be sent verbatim" % (fn,))
471 print "send", fn, "verbatim"
472 tf.AddToZip(output_zip)
473 verbatim_targets.append((fn, tf.size))
474 elif tf.sha1 != sf.sha1:
475 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700476 diff_method = "bsdiff"
477 if tf.name.endswith(".gz"):
478 diff_method = "imgdiff"
479 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700480 if d is not None:
481 print fn, tf.size, len(d), (float(len(d)) / tf.size)
482 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700483 # patch is almost as big as the file; don't bother patching
484 tf.AddToZip(output_zip)
485 verbatim_targets.append((fn, tf.size))
486 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700487 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700488 patch_list.append((fn, tf, sf, tf.size))
489 largest_source_size = max(largest_source_size, sf.size)
490 else:
491 # Target file identical to source.
492 pass
493
494 total_verbatim_size = sum([i[1] for i in verbatim_targets])
495 total_patched_size = sum([i[3] for i in patch_list])
496
497 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
498 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
499
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700500 script.Mount("MTD", "system", "/system")
501 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700502
Doug Zongker5da317e2009-06-02 13:38:17 -0700503 source_boot = File("/tmp/boot.img",
504 common.BuildBootableImage(
505 os.path.join(OPTIONS.source_tmp, "BOOT")))
506 target_boot = File("/tmp/boot.img",
507 common.BuildBootableImage(
508 os.path.join(OPTIONS.target_tmp, "BOOT")))
509 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700510
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700511 source_recovery = File("system/recovery.img",
512 common.BuildBootableImage(
513 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
514 target_recovery = File("system/recovery.img",
515 common.BuildBootableImage(
516 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
517 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700518
519 source_radio = source_zip.read("RADIO/image")
520 target_radio = target_zip.read("RADIO/image")
521 updating_radio = (source_radio != target_radio)
522
523 # The last 0.1 is reserved for creating symlinks, fixing
524 # permissions, and writing the boot image (if necessary).
525 progress_bar_total = 1.0
526 if updating_boot:
527 progress_bar_total -= 0.1
528 if updating_radio:
529 progress_bar_total -= 0.3
530
531 AppendAssertions(script, target_zip)
532
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700533 script.Print("Verifying current system...")
534
Doug Zongkereef39442009-04-02 12:14:19 -0700535 pb_verify = progress_bar_total * 0.3 * \
536 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700537 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700538
539 for i, (fn, tf, sf, size) in enumerate(patch_list):
540 if i % 5 == 0:
541 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700542 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
543
544 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700545
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700546 if updating_recovery:
547 d = Difference(target_recovery, source_recovery, "imgdiff")
548 print "recovery target: %d source: %d diff: %d" % (
549 target_recovery.size, source_recovery.size, len(d))
550
Doug Zongker048e7ca2009-06-15 14:31:53 -0700551 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700552
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700553 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
554 (source_recovery.size, source_recovery.sha1,
555 target_recovery.size, target_recovery.sha1))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700556
Doug Zongker5da317e2009-06-02 13:38:17 -0700557 if updating_boot:
558 d = Difference(target_boot, source_boot, "imgdiff")
559 print "boot target: %d source: %d diff: %d" % (
560 target_boot.size, source_boot.size, len(d))
561
Doug Zongker048e7ca2009-06-15 14:31:53 -0700562 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700563
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700564 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
565 (source_boot.size, source_boot.sha1,
566 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700567
568 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700569 script.CacheFreeSpaceCheck(largest_source_size)
570 script.Print("Unpacking patches...")
571 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700572
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700573 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700574
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700575 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700576 script.Print("Erasing user data...")
577 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700578
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700579 script.Print("Removing unneeded files...")
580 script.DeleteFiles(["/"+i[0] for i in verbatim_targets])
Doug Zongkereef39442009-04-02 12:14:19 -0700581
582 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700583 # Produce the boot image by applying a patch to the current
584 # contents of the boot partition, and write it back to the
585 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700586 script.Print("Patching boot image...")
587 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
588 % (source_boot.size, source_boot.sha1,
589 target_boot.size, target_boot.sha1),
590 "-",
591 target_boot.size, target_boot.sha1,
592 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700593 print "boot image changed; including."
594 else:
595 print "boot image unchanged; skipping."
596
597 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700598 # Produce /system/recovery.img by applying a patch to the current
599 # contents of the recovery partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700600 script.Print("Patching recovery image...")
601 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s"
602 % (source_recovery.size, source_recovery.sha1,
603 target_recovery.size, target_recovery.sha1),
604 "/system/recovery.img",
605 target_recovery.size, target_recovery.sha1,
606 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700607 print "recovery image changed; including."
608 else:
609 print "recovery image unchanged; skipping."
610
611 if updating_radio:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700612 script.ShowProgress(0.3, 10)
613 script.Print("Writing radio image...")
614 script.WriteFirmwareImage("radio", "radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700615 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700616 print "radio image changed; including."
617 else:
618 print "radio image unchanged; skipping."
619
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700620 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700621 pb_apply = progress_bar_total * 0.7 * \
622 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700623 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700624 for i, (fn, tf, sf, size) in enumerate(patch_list):
625 if i % 5 == 0:
626 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700627 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
628 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
629 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700630
631 target_symlinks = CopySystemFiles(target_zip, None)
632
633 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700634 temp_script = script.MakeTemporary()
Doug Zongkereef39442009-04-02 12:14:19 -0700635 FixPermissions(temp_script)
636
637 # Note that this call will mess up the tree of Items, so make sure
638 # we're done with it.
639 source_symlinks = CopySystemFiles(source_zip, None)
640 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
641
642 # Delete all the symlinks in source that aren't in target. This
643 # needs to happen before verbatim files are unpacked, in case a
644 # symlink in the source is replaced by a real file in the target.
645 to_delete = []
646 for dest, link in source_symlinks:
647 if link not in target_symlinks_d:
648 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700649 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700650
651 if verbatim_targets:
652 pb_verbatim = progress_bar_total * \
653 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700654 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700655 script.ShowProgress(pb_verbatim, 5)
656 script.Print("Unpacking new files...")
657 script.UnpackPackageDir("system", "/system")
658
659 script.Print("Finishing up...")
Doug Zongkereef39442009-04-02 12:14:19 -0700660
661 # Create all the symlinks that don't already exist, or point to
662 # somewhere different than what we want. Delete each symlink before
663 # creating it, since the 'symlink' command won't overwrite.
664 to_create = []
665 for dest, link in target_symlinks:
666 if link in source_symlinks_d:
667 if dest != source_symlinks_d[link]:
668 to_create.append((dest, link))
669 else:
670 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700671 script.DeleteFiles([i[1] for i in to_create])
672 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700673
674 # Now that the symlinks are created, we can set all the
675 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700676 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700677
Doug Zongker1c390a22009-05-14 19:06:36 -0700678 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700680
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700681 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700682
683
684def main(argv):
685
686 def option_handler(o, a):
687 if o in ("-b", "--board_config"):
688 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700689 elif o in ("-k", "--package_key"):
690 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700691 elif o in ("-i", "--incremental_from"):
692 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700693 elif o in ("-w", "--wipe_user_data"):
694 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700695 elif o in ("-n", "--no_prereq"):
696 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700697 elif o in ("-e", "--extra_script"):
698 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700699 elif o in ("-m", "--script_mode"):
700 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700701 else:
702 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700703 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700704
705 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700706 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700707 extra_long_opts=["board_config=",
708 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700709 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700710 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700711 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700712 "extra_script=",
713 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700714 extra_option_handler=option_handler)
715
716 if len(args) != 2:
717 common.Usage(__doc__)
718 sys.exit(1)
719
720 if not OPTIONS.max_image_size:
721 print
722 print " WARNING: No board config specified; will not check image"
723 print " sizes against limits. Use -b to make sure the generated"
724 print " images don't exceed partition sizes."
725 print
726
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700727 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
728 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
729
Doug Zongker1c390a22009-05-14 19:06:36 -0700730 if OPTIONS.extra_script is not None:
731 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
732
Doug Zongkereef39442009-04-02 12:14:19 -0700733 print "unzipping target target-files..."
734 OPTIONS.input_tmp = common.UnzipTemp(args[0])
735 OPTIONS.target_tmp = OPTIONS.input_tmp
736 input_zip = zipfile.ZipFile(args[0], "r")
737 if OPTIONS.package_key:
738 temp_zip_file = tempfile.NamedTemporaryFile()
739 output_zip = zipfile.ZipFile(temp_zip_file, "w",
740 compression=zipfile.ZIP_DEFLATED)
741 else:
742 output_zip = zipfile.ZipFile(args[1], "w",
743 compression=zipfile.ZIP_DEFLATED)
744
745 if OPTIONS.incremental_source is None:
746 WriteFullOTAPackage(input_zip, output_zip)
747 else:
748 print "unzipping source target-files..."
749 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
750 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
751 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
752
753 output_zip.close()
754 if OPTIONS.package_key:
755 SignOutput(temp_zip_file.name, args[1])
756 temp_zip_file.close()
757
758 common.Cleanup()
759
760 print "done."
761
762
763if __name__ == '__main__':
764 try:
765 main(sys.argv[1:])
766 except common.ExternalError, e:
767 print
768 print " ERROR: %s" % (e,)
769 print
770 sys.exit(1)