blob: 1ac035a1f29d7b67bac0745b8059f56de6c258ce [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 Zongkereef39442009-04-02 12:14:19 -070048"""
49
50import sys
51
52if sys.hexversion < 0x02040000:
53 print >> sys.stderr, "Python 2.4 or newer is required."
54 sys.exit(1)
55
56import copy
57import os
58import re
59import sha
60import subprocess
61import tempfile
62import time
63import zipfile
64
65import common
66
67OPTIONS = common.OPTIONS
68OPTIONS.package_key = "build/target/product/security/testkey"
69OPTIONS.incremental_source = None
70OPTIONS.require_verbatim = set()
71OPTIONS.prohibit_verbatim = set(("system/build.prop",))
72OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070073OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070074OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070075OPTIONS.extra_script = None
Doug Zongkereef39442009-04-02 12:14:19 -070076
77def MostPopularKey(d, default):
78 """Given a dict, return the key corresponding to the largest
79 value. Returns 'default' if the dict is empty."""
80 x = [(v, k) for (k, v) in d.iteritems()]
81 if not x: return default
82 x.sort()
83 return x[-1][1]
84
85
86def IsSymlink(info):
87 """Return true if the zipfile.ZipInfo object passed in represents a
88 symlink."""
89 return (info.external_attr >> 16) == 0120777
90
91
92
93class Item:
94 """Items represent the metadata (user, group, mode) of files and
95 directories in the system image."""
96 ITEMS = {}
97 def __init__(self, name, dir=False):
98 self.name = name
99 self.uid = None
100 self.gid = None
101 self.mode = None
102 self.dir = dir
103
104 if name:
105 self.parent = Item.Get(os.path.dirname(name), dir=True)
106 self.parent.children.append(self)
107 else:
108 self.parent = None
109 if dir:
110 self.children = []
111
112 def Dump(self, indent=0):
113 if self.uid is not None:
114 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
115 else:
116 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
117 if self.dir:
118 print "%s%s" % (" "*indent, self.descendants)
119 print "%s%s" % (" "*indent, self.best_subtree)
120 for i in self.children:
121 i.Dump(indent=indent+1)
122
123 @classmethod
124 def Get(cls, name, dir=False):
125 if name not in cls.ITEMS:
126 cls.ITEMS[name] = Item(name, dir=dir)
127 return cls.ITEMS[name]
128
129 @classmethod
130 def GetMetadata(cls):
131 """Run the external 'fs_config' program to determine the desired
132 uid, gid, and mode for every Item object."""
133 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
134 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
135 suffix = { False: "", True: "/" }
136 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
137 for i in cls.ITEMS.itervalues() if i.name])
138 output, error = p.communicate(input)
139 assert not error
140
141 for line in output.split("\n"):
142 if not line: continue
143 name, uid, gid, mode = line.split()
144 i = cls.ITEMS[name]
145 i.uid = int(uid)
146 i.gid = int(gid)
147 i.mode = int(mode, 8)
148 if i.dir:
149 i.children.sort(key=lambda i: i.name)
150
151 def CountChildMetadata(self):
152 """Count up the (uid, gid, mode) tuples for all children and
153 determine the best strategy for using set_perm_recursive and
154 set_perm to correctly chown/chmod all the files to their desired
155 values. Recursively calls itself for all descendants.
156
157 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
158 all descendants of this node. (dmode or fmode may be None.) Also
159 sets the best_subtree of each directory Item to the (uid, gid,
160 dmode, fmode) tuple that will match the most descendants of that
161 Item.
162 """
163
164 assert self.dir
165 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
166 for i in self.children:
167 if i.dir:
168 for k, v in i.CountChildMetadata().iteritems():
169 d[k] = d.get(k, 0) + v
170 else:
171 k = (i.uid, i.gid, None, i.mode)
172 d[k] = d.get(k, 0) + 1
173
174 # Find the (uid, gid, dmode, fmode) tuple that matches the most
175 # descendants.
176
177 # First, find the (uid, gid) pair that matches the most
178 # descendants.
179 ug = {}
180 for (uid, gid, _, _), count in d.iteritems():
181 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
182 ug = MostPopularKey(ug, (0, 0))
183
184 # Now find the dmode and fmode that match the most descendants
185 # with that (uid, gid), and choose those.
186 best_dmode = (0, 0755)
187 best_fmode = (0, 0644)
188 for k, count in d.iteritems():
189 if k[:2] != ug: continue
190 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
191 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
192 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
193
194 return d
195
196 def SetPermissions(self, script, renamer=lambda x: x):
197 """Append set_perm/set_perm_recursive commands to 'script' to
198 set all permissions, users, and groups for the tree of files
199 rooted at 'self'. 'renamer' turns the filenames stored in the
200 tree of Items into the strings used in the script."""
201
202 self.CountChildMetadata()
203
204 def recurse(item, current):
205 # current is the (uid, gid, dmode, fmode) tuple that the current
206 # item (and all its children) have already been set to. We only
207 # need to issue set_perm/set_perm_recursive commands if we're
208 # supposed to be something different.
209 if item.dir:
210 if current != item.best_subtree:
211 script.append("set_perm_recursive %d %d 0%o 0%o %s" %
212 (item.best_subtree + (renamer(item.name),)))
213 current = item.best_subtree
214
215 if item.uid != current[0] or item.gid != current[1] or \
216 item.mode != current[2]:
217 script.append("set_perm %d %d 0%o %s" %
218 (item.uid, item.gid, item.mode, renamer(item.name)))
219
220 for i in item.children:
221 recurse(i, current)
222 else:
223 if item.uid != current[0] or item.gid != current[1] or \
224 item.mode != current[3]:
225 script.append("set_perm %d %d 0%o %s" %
226 (item.uid, item.gid, item.mode, renamer(item.name)))
227
228 recurse(self, (-1, -1, -1, -1))
229
230
231def CopySystemFiles(input_zip, output_zip=None,
232 substitute=None):
233 """Copies files underneath system/ in the input zip to the output
234 zip. Populates the Item class with their metadata, and returns a
235 list of symlinks. output_zip may be None, in which case the copy is
236 skipped (but the other side effects still happen). substitute is an
237 optional dict of {output filename: contents} to be output instead of
238 certain input files.
239 """
240
241 symlinks = []
242
243 for info in input_zip.infolist():
244 if info.filename.startswith("SYSTEM/"):
245 basefilename = info.filename[7:]
246 if IsSymlink(info):
247 symlinks.append((input_zip.read(info.filename),
248 "SYSTEM:" + basefilename))
249 else:
250 info2 = copy.copy(info)
251 fn = info2.filename = "system/" + basefilename
252 if substitute and fn in substitute and substitute[fn] is None:
253 continue
254 if output_zip is not None:
255 if substitute and fn in substitute:
256 data = substitute[fn]
257 else:
258 data = input_zip.read(info.filename)
259 output_zip.writestr(info2, data)
260 if fn.endswith("/"):
261 Item.Get(fn[:-1], dir=True)
262 else:
263 Item.Get(fn, dir=False)
264
265 symlinks.sort()
266 return symlinks
267
268
269def AddScript(script, output_zip):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700270 common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-script",
271 "\n".join(script) + "\n")
Doug Zongkereef39442009-04-02 12:14:19 -0700272
273
274def SignOutput(temp_zip_name, output_zip_name):
275 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
276 pw = key_passwords[OPTIONS.package_key]
277
278 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
279
280
281def SubstituteRoot(s):
282 if s == "system": return "SYSTEM:"
283 assert s.startswith("system/")
284 return "SYSTEM:" + s[7:]
285
286def FixPermissions(script):
287 Item.GetMetadata()
288 root = Item.Get("system")
289 root.SetPermissions(script, renamer=SubstituteRoot)
290
291def DeleteFiles(script, to_delete):
292 line = []
293 t = 0
294 for i in to_delete:
295 line.append(i)
296 t += len(i) + 1
297 if t > 80:
298 script.append("delete " + " ".join(line))
299 line = []
300 t = 0
301 if line:
302 script.append("delete " + " ".join(line))
303
304def AppendAssertions(script, input_zip):
305 script.append('assert compatible_with("0.2") == "true"')
306
307 device = GetBuildProp("ro.product.device", input_zip)
308 script.append('assert getprop("ro.product.device") == "%s" || '
309 'getprop("ro.build.product") == "%s"' % (device, device))
310
311 info = input_zip.read("OTA/android-info.txt")
312 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
313 if not m:
314 raise ExternalError("failed to find required bootloaders in "
315 "android-info.txt")
316 bootloaders = m.group(1).split("|")
317 script.append("assert " +
318 " || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
319 for b in bootloaders]))
320
321
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700322def IncludeBinary(name, input_zip, output_zip, input_path=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700323 try:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700324 if input_path is not None:
325 data = open(input_path).read()
326 else:
327 data = input_zip.read(os.path.join("OTA/bin", name))
Doug Zongker048e7ca2009-06-15 14:31:53 -0700328 common.ZipWriteStr(output_zip, name, data, perms=0755)
Doug Zongkereef39442009-04-02 12:14:19 -0700329 except IOError:
330 raise ExternalError('unable to include device binary "%s"' % (name,))
331
332
333def WriteFullOTAPackage(input_zip, output_zip):
334 script = []
335
Doug Zongker962069c2009-04-23 11:41:58 -0700336 if not OPTIONS.omit_prereq:
337 ts = GetBuildProp("ro.build.date.utc", input_zip)
338 script.append("run_program PACKAGE:check_prereq %s" % (ts,))
339 IncludeBinary("check_prereq", input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
341 AppendAssertions(script, input_zip)
342
343 script.append("format BOOT:")
344 script.append("show_progress 0.1 0")
345
Doug Zongker048e7ca2009-06-15 14:31:53 -0700346 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
Doug Zongkereef39442009-04-02 12:14:19 -0700347 script.append("write_radio_image PACKAGE:radio.img")
348 script.append("show_progress 0.5 0")
349
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700350 if OPTIONS.wipe_user_data:
351 script.append("format DATA:")
352
Doug Zongkereef39442009-04-02 12:14:19 -0700353 script.append("format SYSTEM:")
354 script.append("copy_dir PACKAGE:system SYSTEM:")
355
356 symlinks = CopySystemFiles(input_zip, output_zip)
357 script.extend(["symlink %s %s" % s for s in symlinks])
358
359 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
360 "system/recovery.img", output_zip)
361 Item.Get("system/recovery.img", dir=False)
362
363 FixPermissions(script)
364
365 common.AddBoot(output_zip)
366 script.append("show_progress 0.2 0")
367 script.append("write_raw_image PACKAGE:boot.img BOOT:")
368 script.append("show_progress 0.2 10")
369
Doug Zongker1c390a22009-05-14 19:06:36 -0700370 if OPTIONS.extra_script is not None:
371 script.append(OPTIONS.extra_script)
372
Doug Zongkereef39442009-04-02 12:14:19 -0700373 AddScript(script, output_zip)
374
375
376class File(object):
377 def __init__(self, name, data):
378 self.name = name
379 self.data = data
380 self.size = len(data)
381 self.sha1 = sha.sha(data).hexdigest()
382
383 def WriteToTemp(self):
384 t = tempfile.NamedTemporaryFile()
385 t.write(self.data)
386 t.flush()
387 return t
388
389 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700390 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700391
392
393def LoadSystemFiles(z):
394 """Load all the files from SYSTEM/... in a given target-files
395 ZipFile, and return a dict of {filename: File object}."""
396 out = {}
397 for info in z.infolist():
398 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
399 fn = "system/" + info.filename[7:]
400 data = z.read(info.filename)
401 out[fn] = File(fn, data)
402 return out
403
404
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700405def Difference(tf, sf, diff_program):
406 """Return the patch (as a string of data) needed to turn sf into tf.
407 diff_program is the name of an external program (or list, if
408 additional arguments are desired) to run to generate the diff.
409 """
Doug Zongkereef39442009-04-02 12:14:19 -0700410
411 ttemp = tf.WriteToTemp()
412 stemp = sf.WriteToTemp()
413
414 ext = os.path.splitext(tf.name)[1]
415
416 try:
417 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700418 if isinstance(diff_program, list):
419 cmd = copy.copy(diff_program)
420 else:
421 cmd = [diff_program]
422 cmd.append(stemp.name)
423 cmd.append(ttemp.name)
424 cmd.append(ptemp.name)
425 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700426 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700427 if err or p.returncode != 0:
428 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
429 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700430 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700431 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700432 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700433 stemp.close()
434 ttemp.close()
435
436 return diff
437
438
439def GetBuildProp(property, z):
440 """Return the fingerprint of the build of a given target-files
441 ZipFile object."""
442 bp = z.read("SYSTEM/build.prop")
443 if not property:
444 return bp
445 m = re.search(re.escape(property) + r"=(.*)\n", bp)
446 if not m:
447 raise ExternalException("couldn't find %s in build.prop" % (property,))
448 return m.group(1).strip()
449
450
451def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
452 script = []
453
454 print "Loading target..."
455 target_data = LoadSystemFiles(target_zip)
456 print "Loading source..."
457 source_data = LoadSystemFiles(source_zip)
458
459 verbatim_targets = []
460 patch_list = []
461 largest_source_size = 0
462 for fn in sorted(target_data.keys()):
463 tf = target_data[fn]
464 sf = source_data.get(fn, None)
465
466 if sf is None or fn in OPTIONS.require_verbatim:
467 # This file should be included verbatim
468 if fn in OPTIONS.prohibit_verbatim:
469 raise ExternalError("\"%s\" must be sent verbatim" % (fn,))
470 print "send", fn, "verbatim"
471 tf.AddToZip(output_zip)
472 verbatim_targets.append((fn, tf.size))
473 elif tf.sha1 != sf.sha1:
474 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700475 diff_method = "bsdiff"
476 if tf.name.endswith(".gz"):
477 diff_method = "imgdiff"
478 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700479 if d is not None:
480 print fn, tf.size, len(d), (float(len(d)) / tf.size)
481 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700482 # patch is almost as big as the file; don't bother patching
483 tf.AddToZip(output_zip)
484 verbatim_targets.append((fn, tf.size))
485 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700486 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700487 patch_list.append((fn, tf, sf, tf.size))
488 largest_source_size = max(largest_source_size, sf.size)
489 else:
490 # Target file identical to source.
491 pass
492
493 total_verbatim_size = sum([i[1] for i in verbatim_targets])
494 total_patched_size = sum([i[3] for i in patch_list])
495
496 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
497 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
498
499 script.append(('assert file_contains("SYSTEM:build.prop", '
500 '"ro.build.fingerprint=%s") == "true" || '
501 'file_contains("SYSTEM:build.prop", '
502 '"ro.build.fingerprint=%s") == "true"') %
503 (source_fp, target_fp))
504
Doug Zongker5da317e2009-06-02 13:38:17 -0700505 source_boot = File("/tmp/boot.img",
506 common.BuildBootableImage(
507 os.path.join(OPTIONS.source_tmp, "BOOT")))
508 target_boot = File("/tmp/boot.img",
509 common.BuildBootableImage(
510 os.path.join(OPTIONS.target_tmp, "BOOT")))
511 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700512
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700513 source_recovery = File("system/recovery.img",
514 common.BuildBootableImage(
515 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
516 target_recovery = File("system/recovery.img",
517 common.BuildBootableImage(
518 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
519 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700520
521 source_radio = source_zip.read("RADIO/image")
522 target_radio = target_zip.read("RADIO/image")
523 updating_radio = (source_radio != target_radio)
524
525 # The last 0.1 is reserved for creating symlinks, fixing
526 # permissions, and writing the boot image (if necessary).
527 progress_bar_total = 1.0
528 if updating_boot:
529 progress_bar_total -= 0.1
530 if updating_radio:
531 progress_bar_total -= 0.3
532
533 AppendAssertions(script, target_zip)
534
535 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]])
542 script.append("show_progress %f 1" %
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700543 (next_sizes * pb_verify / (total_patched_size+1),))
Doug Zongkereef39442009-04-02 12:14:19 -0700544 script.append("run_program PACKAGE:applypatch -c /%s %s %s" %
545 (fn, tf.sha1, sf.sha1))
546
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700547 if updating_recovery:
548 d = Difference(target_recovery, source_recovery, "imgdiff")
549 print "recovery target: %d source: %d diff: %d" % (
550 target_recovery.size, source_recovery.size, len(d))
551
Doug Zongker048e7ca2009-06-15 14:31:53 -0700552 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700553
554 script.append(("run_program PACKAGE:applypatch -c "
555 "MTD:recovery:%d:%s:%d:%s") %
556 (source_recovery.size, source_recovery.sha1,
557 target_recovery.size, target_recovery.sha1))
558
Doug Zongker5da317e2009-06-02 13:38:17 -0700559 if updating_boot:
560 d = Difference(target_boot, source_boot, "imgdiff")
561 print "boot target: %d source: %d diff: %d" % (
562 target_boot.size, source_boot.size, len(d))
563
Doug Zongker048e7ca2009-06-15 14:31:53 -0700564 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700565
566 script.append(("run_program PACKAGE:applypatch -c "
567 "MTD:boot:%d:%s:%d:%s") %
568 (source_boot.size, source_boot.sha1,
569 target_boot.size, target_boot.sha1))
570
571 if patch_list or updating_recovery or updating_boot:
572 script.append("run_program PACKAGE:applypatch -s %d" %
573 (largest_source_size,))
574 script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp")
575 IncludeBinary("applypatch", target_zip, output_zip)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700576
Doug Zongkereef39442009-04-02 12:14:19 -0700577 script.append("\n# ---- start making changes here\n")
578
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700579 if OPTIONS.wipe_user_data:
580 script.append("format DATA:")
581
Doug Zongkereef39442009-04-02 12:14:19 -0700582 DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets])
583
584 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700585 # Produce the boot image by applying a patch to the current
586 # contents of the boot partition, and write it back to the
587 # partition.
588 script.append(("run_program PACKAGE:applypatch "
589 "MTD:boot:%d:%s:%d:%s - "
590 "%s %d %s:/tmp/patchtmp/boot.img.p")
591 % (source_boot.size, source_boot.sha1,
592 target_boot.size, target_boot.sha1,
593 target_boot.sha1,
594 target_boot.size,
595 source_boot.sha1))
Doug Zongkereef39442009-04-02 12:14:19 -0700596 print "boot image changed; including."
597 else:
598 print "boot image unchanged; skipping."
599
600 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700601 # Produce /system/recovery.img by applying a patch to the current
602 # contents of the recovery partition.
603 script.append(("run_program PACKAGE:applypatch MTD:recovery:%d:%s:%d:%s "
604 "/system/recovery.img %s %d %s:/tmp/patchtmp/recovery.img.p")
605 % (source_recovery.size, source_recovery.sha1,
606 target_recovery.size, target_recovery.sha1,
607 target_recovery.sha1,
608 target_recovery.size,
609 source_recovery.sha1))
Doug Zongkereef39442009-04-02 12:14:19 -0700610 print "recovery image changed; including."
611 else:
612 print "recovery image unchanged; skipping."
613
614 if updating_radio:
615 script.append("show_progress 0.3 10")
616 script.append("write_radio_image PACKAGE:radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700617 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700618 print "radio image changed; including."
619 else:
620 print "radio image unchanged; skipping."
621
622 pb_apply = progress_bar_total * 0.7 * \
623 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700624 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700625 for i, (fn, tf, sf, size) in enumerate(patch_list):
626 if i % 5 == 0:
627 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
628 script.append("show_progress %f 1" %
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700629 (next_sizes * pb_apply / (total_patched_size+1),))
Doug Zongkereef39442009-04-02 12:14:19 -0700630 script.append(("run_program PACKAGE:applypatch "
Doug Zongkeref85ea62009-05-08 13:46:25 -0700631 "/%s - %s %d %s:/tmp/patchtmp/%s.p") %
Doug Zongkereef39442009-04-02 12:14:19 -0700632 (fn, tf.sha1, tf.size, sf.sha1, fn))
633
634 target_symlinks = CopySystemFiles(target_zip, None)
635
636 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
637 temp_script = []
638 FixPermissions(temp_script)
639
640 # Note that this call will mess up the tree of Items, so make sure
641 # we're done with it.
642 source_symlinks = CopySystemFiles(source_zip, None)
643 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
644
645 # Delete all the symlinks in source that aren't in target. This
646 # needs to happen before verbatim files are unpacked, in case a
647 # symlink in the source is replaced by a real file in the target.
648 to_delete = []
649 for dest, link in source_symlinks:
650 if link not in target_symlinks_d:
651 to_delete.append(link)
652 DeleteFiles(script, to_delete)
653
654 if verbatim_targets:
655 pb_verbatim = progress_bar_total * \
656 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700657 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700658 script.append("show_progress %f 5" % (pb_verbatim,))
659 script.append("copy_dir PACKAGE:system SYSTEM:")
660
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))
671 DeleteFiles(script, [i[1] for i in to_create])
672 script.extend(["symlink %s %s" % s for s in to_create])
673
674 # Now that the symlinks are created, we can set all the
675 # permissions.
676 script.extend(temp_script)
677
Doug Zongker1c390a22009-05-14 19:06:36 -0700678 if OPTIONS.extra_script is not None:
679 script.append(OPTIONS.extra_script)
680
Doug Zongkereef39442009-04-02 12:14:19 -0700681 AddScript(script, output_zip)
682
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 Zongkereef39442009-04-02 12:14:19 -0700699 else:
700 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700701 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700702
703 args = common.ParseOptions(argv, __doc__,
Doug Zongker1c390a22009-05-14 19:06:36 -0700704 extra_opts="b:k:i:d:wne:",
Doug Zongkereef39442009-04-02 12:14:19 -0700705 extra_long_opts=["board_config=",
706 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700707 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700708 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700709 "no_prereq",
710 "extra_script="],
Doug Zongkereef39442009-04-02 12:14:19 -0700711 extra_option_handler=option_handler)
712
713 if len(args) != 2:
714 common.Usage(__doc__)
715 sys.exit(1)
716
717 if not OPTIONS.max_image_size:
718 print
719 print " WARNING: No board config specified; will not check image"
720 print " sizes against limits. Use -b to make sure the generated"
721 print " images don't exceed partition sizes."
722 print
723
Doug Zongker1c390a22009-05-14 19:06:36 -0700724 if OPTIONS.extra_script is not None:
725 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
726
Doug Zongkereef39442009-04-02 12:14:19 -0700727 print "unzipping target target-files..."
728 OPTIONS.input_tmp = common.UnzipTemp(args[0])
729 OPTIONS.target_tmp = OPTIONS.input_tmp
730 input_zip = zipfile.ZipFile(args[0], "r")
731 if OPTIONS.package_key:
732 temp_zip_file = tempfile.NamedTemporaryFile()
733 output_zip = zipfile.ZipFile(temp_zip_file, "w",
734 compression=zipfile.ZIP_DEFLATED)
735 else:
736 output_zip = zipfile.ZipFile(args[1], "w",
737 compression=zipfile.ZIP_DEFLATED)
738
739 if OPTIONS.incremental_source is None:
740 WriteFullOTAPackage(input_zip, output_zip)
741 else:
742 print "unzipping source target-files..."
743 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
744 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
745 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
746
747 output_zip.close()
748 if OPTIONS.package_key:
749 SignOutput(temp_zip_file.name, args[1])
750 temp_zip_file.close()
751
752 common.Cleanup()
753
754 print "done."
755
756
757if __name__ == '__main__':
758 try:
759 main(sys.argv[1:])
760 except common.ExternalError, e:
761 print
762 print " ERROR: %s" % (e,)
763 print
764 sys.exit(1)