blob: 307ca4f4c3e1d9410c8c3d865e37e1159a5695fa [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)
Doug Zongker171f1cd2009-06-15 22:36:37 -0700313 if m:
314 bootloaders = m.group(1).split("|")
315 script.append("assert " +
316 " || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
317 for b in bootloaders]))
Doug Zongkereef39442009-04-02 12:14:19 -0700318
319
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700320def IncludeBinary(name, input_zip, output_zip, input_path=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700321 try:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700322 if input_path is not None:
323 data = open(input_path).read()
324 else:
325 data = input_zip.read(os.path.join("OTA/bin", name))
Doug Zongker048e7ca2009-06-15 14:31:53 -0700326 common.ZipWriteStr(output_zip, name, data, perms=0755)
Doug Zongkereef39442009-04-02 12:14:19 -0700327 except IOError:
328 raise ExternalError('unable to include device binary "%s"' % (name,))
329
330
331def WriteFullOTAPackage(input_zip, output_zip):
332 script = []
333
Doug Zongker962069c2009-04-23 11:41:58 -0700334 if not OPTIONS.omit_prereq:
335 ts = GetBuildProp("ro.build.date.utc", input_zip)
336 script.append("run_program PACKAGE:check_prereq %s" % (ts,))
337 IncludeBinary("check_prereq", input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700338
339 AppendAssertions(script, input_zip)
340
341 script.append("format BOOT:")
342 script.append("show_progress 0.1 0")
343
Doug Zongker171f1cd2009-06-15 22:36:37 -0700344 try:
345 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
346 script.append("write_radio_image PACKAGE:radio.img")
347 except KeyError:
348 pass
349
Doug Zongkereef39442009-04-02 12:14:19 -0700350 script.append("show_progress 0.5 0")
351
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700352 if OPTIONS.wipe_user_data:
353 script.append("format DATA:")
354
Doug Zongkereef39442009-04-02 12:14:19 -0700355 script.append("format SYSTEM:")
356 script.append("copy_dir PACKAGE:system SYSTEM:")
357
358 symlinks = CopySystemFiles(input_zip, output_zip)
359 script.extend(["symlink %s %s" % s for s in symlinks])
360
361 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
362 "system/recovery.img", output_zip)
363 Item.Get("system/recovery.img", dir=False)
364
365 FixPermissions(script)
366
367 common.AddBoot(output_zip)
368 script.append("show_progress 0.2 0")
369 script.append("write_raw_image PACKAGE:boot.img BOOT:")
370 script.append("show_progress 0.2 10")
371
Doug Zongker1c390a22009-05-14 19:06:36 -0700372 if OPTIONS.extra_script is not None:
373 script.append(OPTIONS.extra_script)
374
Doug Zongkereef39442009-04-02 12:14:19 -0700375 AddScript(script, output_zip)
376
377
378class File(object):
379 def __init__(self, name, data):
380 self.name = name
381 self.data = data
382 self.size = len(data)
383 self.sha1 = sha.sha(data).hexdigest()
384
385 def WriteToTemp(self):
386 t = tempfile.NamedTemporaryFile()
387 t.write(self.data)
388 t.flush()
389 return t
390
391 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700392 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700393
394
395def LoadSystemFiles(z):
396 """Load all the files from SYSTEM/... in a given target-files
397 ZipFile, and return a dict of {filename: File object}."""
398 out = {}
399 for info in z.infolist():
400 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
401 fn = "system/" + info.filename[7:]
402 data = z.read(info.filename)
403 out[fn] = File(fn, data)
404 return out
405
406
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700407def Difference(tf, sf, diff_program):
408 """Return the patch (as a string of data) needed to turn sf into tf.
409 diff_program is the name of an external program (or list, if
410 additional arguments are desired) to run to generate the diff.
411 """
Doug Zongkereef39442009-04-02 12:14:19 -0700412
413 ttemp = tf.WriteToTemp()
414 stemp = sf.WriteToTemp()
415
416 ext = os.path.splitext(tf.name)[1]
417
418 try:
419 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700420 if isinstance(diff_program, list):
421 cmd = copy.copy(diff_program)
422 else:
423 cmd = [diff_program]
424 cmd.append(stemp.name)
425 cmd.append(ttemp.name)
426 cmd.append(ptemp.name)
427 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700428 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700429 if err or p.returncode != 0:
430 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
431 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700432 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700433 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700434 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700435 stemp.close()
436 ttemp.close()
437
438 return diff
439
440
441def GetBuildProp(property, z):
442 """Return the fingerprint of the build of a given target-files
443 ZipFile object."""
444 bp = z.read("SYSTEM/build.prop")
445 if not property:
446 return bp
447 m = re.search(re.escape(property) + r"=(.*)\n", bp)
448 if not m:
449 raise ExternalException("couldn't find %s in build.prop" % (property,))
450 return m.group(1).strip()
451
452
453def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
454 script = []
455
456 print "Loading target..."
457 target_data = LoadSystemFiles(target_zip)
458 print "Loading source..."
459 source_data = LoadSystemFiles(source_zip)
460
461 verbatim_targets = []
462 patch_list = []
463 largest_source_size = 0
464 for fn in sorted(target_data.keys()):
465 tf = target_data[fn]
466 sf = source_data.get(fn, None)
467
468 if sf is None or fn in OPTIONS.require_verbatim:
469 # This file should be included verbatim
470 if fn in OPTIONS.prohibit_verbatim:
471 raise ExternalError("\"%s\" must be sent verbatim" % (fn,))
472 print "send", fn, "verbatim"
473 tf.AddToZip(output_zip)
474 verbatim_targets.append((fn, tf.size))
475 elif tf.sha1 != sf.sha1:
476 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700477 diff_method = "bsdiff"
478 if tf.name.endswith(".gz"):
479 diff_method = "imgdiff"
480 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700481 if d is not None:
482 print fn, tf.size, len(d), (float(len(d)) / tf.size)
483 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700484 # patch is almost as big as the file; don't bother patching
485 tf.AddToZip(output_zip)
486 verbatim_targets.append((fn, tf.size))
487 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700488 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700489 patch_list.append((fn, tf, sf, tf.size))
490 largest_source_size = max(largest_source_size, sf.size)
491 else:
492 # Target file identical to source.
493 pass
494
495 total_verbatim_size = sum([i[1] for i in verbatim_targets])
496 total_patched_size = sum([i[3] for i in patch_list])
497
498 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
499 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
500
501 script.append(('assert file_contains("SYSTEM:build.prop", '
502 '"ro.build.fingerprint=%s") == "true" || '
503 'file_contains("SYSTEM:build.prop", '
504 '"ro.build.fingerprint=%s") == "true"') %
505 (source_fp, target_fp))
506
Doug Zongker5da317e2009-06-02 13:38:17 -0700507 source_boot = File("/tmp/boot.img",
508 common.BuildBootableImage(
509 os.path.join(OPTIONS.source_tmp, "BOOT")))
510 target_boot = File("/tmp/boot.img",
511 common.BuildBootableImage(
512 os.path.join(OPTIONS.target_tmp, "BOOT")))
513 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700514
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700515 source_recovery = File("system/recovery.img",
516 common.BuildBootableImage(
517 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
518 target_recovery = File("system/recovery.img",
519 common.BuildBootableImage(
520 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
521 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700522
523 source_radio = source_zip.read("RADIO/image")
524 target_radio = target_zip.read("RADIO/image")
525 updating_radio = (source_radio != target_radio)
526
527 # The last 0.1 is reserved for creating symlinks, fixing
528 # permissions, and writing the boot image (if necessary).
529 progress_bar_total = 1.0
530 if updating_boot:
531 progress_bar_total -= 0.1
532 if updating_radio:
533 progress_bar_total -= 0.3
534
535 AppendAssertions(script, target_zip)
536
537 pb_verify = progress_bar_total * 0.3 * \
538 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700539 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700540
541 for i, (fn, tf, sf, size) in enumerate(patch_list):
542 if i % 5 == 0:
543 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
544 script.append("show_progress %f 1" %
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700545 (next_sizes * pb_verify / (total_patched_size+1),))
Doug Zongkereef39442009-04-02 12:14:19 -0700546 script.append("run_program PACKAGE:applypatch -c /%s %s %s" %
547 (fn, tf.sha1, sf.sha1))
548
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700549 if updating_recovery:
550 d = Difference(target_recovery, source_recovery, "imgdiff")
551 print "recovery target: %d source: %d diff: %d" % (
552 target_recovery.size, source_recovery.size, len(d))
553
Doug Zongker048e7ca2009-06-15 14:31:53 -0700554 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700555
556 script.append(("run_program PACKAGE:applypatch -c "
557 "MTD:recovery:%d:%s:%d:%s") %
558 (source_recovery.size, source_recovery.sha1,
559 target_recovery.size, target_recovery.sha1))
560
Doug Zongker5da317e2009-06-02 13:38:17 -0700561 if updating_boot:
562 d = Difference(target_boot, source_boot, "imgdiff")
563 print "boot target: %d source: %d diff: %d" % (
564 target_boot.size, source_boot.size, len(d))
565
Doug Zongker048e7ca2009-06-15 14:31:53 -0700566 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700567
568 script.append(("run_program PACKAGE:applypatch -c "
569 "MTD:boot:%d:%s:%d:%s") %
570 (source_boot.size, source_boot.sha1,
571 target_boot.size, target_boot.sha1))
572
573 if patch_list or updating_recovery or updating_boot:
574 script.append("run_program PACKAGE:applypatch -s %d" %
575 (largest_source_size,))
576 script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp")
577 IncludeBinary("applypatch", target_zip, output_zip)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700578
Doug Zongkereef39442009-04-02 12:14:19 -0700579 script.append("\n# ---- start making changes here\n")
580
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700581 if OPTIONS.wipe_user_data:
582 script.append("format DATA:")
583
Doug Zongkereef39442009-04-02 12:14:19 -0700584 DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets])
585
586 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700587 # Produce the boot image by applying a patch to the current
588 # contents of the boot partition, and write it back to the
589 # partition.
590 script.append(("run_program PACKAGE:applypatch "
591 "MTD:boot:%d:%s:%d:%s - "
592 "%s %d %s:/tmp/patchtmp/boot.img.p")
593 % (source_boot.size, source_boot.sha1,
594 target_boot.size, target_boot.sha1,
595 target_boot.sha1,
596 target_boot.size,
597 source_boot.sha1))
Doug Zongkereef39442009-04-02 12:14:19 -0700598 print "boot image changed; including."
599 else:
600 print "boot image unchanged; skipping."
601
602 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700603 # Produce /system/recovery.img by applying a patch to the current
604 # contents of the recovery partition.
605 script.append(("run_program PACKAGE:applypatch MTD:recovery:%d:%s:%d:%s "
606 "/system/recovery.img %s %d %s:/tmp/patchtmp/recovery.img.p")
607 % (source_recovery.size, source_recovery.sha1,
608 target_recovery.size, target_recovery.sha1,
609 target_recovery.sha1,
610 target_recovery.size,
611 source_recovery.sha1))
Doug Zongkereef39442009-04-02 12:14:19 -0700612 print "recovery image changed; including."
613 else:
614 print "recovery image unchanged; skipping."
615
616 if updating_radio:
617 script.append("show_progress 0.3 10")
618 script.append("write_radio_image PACKAGE:radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700619 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700620 print "radio image changed; including."
621 else:
622 print "radio image unchanged; skipping."
623
624 pb_apply = progress_bar_total * 0.7 * \
625 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700626 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700627 for i, (fn, tf, sf, size) in enumerate(patch_list):
628 if i % 5 == 0:
629 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
630 script.append("show_progress %f 1" %
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700631 (next_sizes * pb_apply / (total_patched_size+1),))
Doug Zongkereef39442009-04-02 12:14:19 -0700632 script.append(("run_program PACKAGE:applypatch "
Doug Zongkeref85ea62009-05-08 13:46:25 -0700633 "/%s - %s %d %s:/tmp/patchtmp/%s.p") %
Doug Zongkereef39442009-04-02 12:14:19 -0700634 (fn, tf.sha1, tf.size, sf.sha1, fn))
635
636 target_symlinks = CopySystemFiles(target_zip, None)
637
638 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
639 temp_script = []
640 FixPermissions(temp_script)
641
642 # Note that this call will mess up the tree of Items, so make sure
643 # we're done with it.
644 source_symlinks = CopySystemFiles(source_zip, None)
645 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
646
647 # Delete all the symlinks in source that aren't in target. This
648 # needs to happen before verbatim files are unpacked, in case a
649 # symlink in the source is replaced by a real file in the target.
650 to_delete = []
651 for dest, link in source_symlinks:
652 if link not in target_symlinks_d:
653 to_delete.append(link)
654 DeleteFiles(script, to_delete)
655
656 if verbatim_targets:
657 pb_verbatim = progress_bar_total * \
658 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700659 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700660 script.append("show_progress %f 5" % (pb_verbatim,))
661 script.append("copy_dir PACKAGE:system SYSTEM:")
662
663 # Create all the symlinks that don't already exist, or point to
664 # somewhere different than what we want. Delete each symlink before
665 # creating it, since the 'symlink' command won't overwrite.
666 to_create = []
667 for dest, link in target_symlinks:
668 if link in source_symlinks_d:
669 if dest != source_symlinks_d[link]:
670 to_create.append((dest, link))
671 else:
672 to_create.append((dest, link))
673 DeleteFiles(script, [i[1] for i in to_create])
674 script.extend(["symlink %s %s" % s for s in to_create])
675
676 # Now that the symlinks are created, we can set all the
677 # permissions.
678 script.extend(temp_script)
679
Doug Zongker1c390a22009-05-14 19:06:36 -0700680 if OPTIONS.extra_script is not None:
681 script.append(OPTIONS.extra_script)
682
Doug Zongkereef39442009-04-02 12:14:19 -0700683 AddScript(script, output_zip)
684
685
686def main(argv):
687
688 def option_handler(o, a):
689 if o in ("-b", "--board_config"):
690 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700691 elif o in ("-k", "--package_key"):
692 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700693 elif o in ("-i", "--incremental_from"):
694 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700695 elif o in ("-w", "--wipe_user_data"):
696 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700697 elif o in ("-n", "--no_prereq"):
698 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700699 elif o in ("-e", "--extra_script"):
700 OPTIONS.extra_script = a
Doug 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 Zongker1c390a22009-05-14 19:06:36 -0700706 extra_opts="b:k:i:d:wne:",
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",
712 "extra_script="],
Doug Zongkereef39442009-04-02 12:14:19 -0700713 extra_option_handler=option_handler)
714
715 if len(args) != 2:
716 common.Usage(__doc__)
717 sys.exit(1)
718
719 if not OPTIONS.max_image_size:
720 print
721 print " WARNING: No board config specified; will not check image"
722 print " sizes against limits. Use -b to make sure the generated"
723 print " images don't exceed partition sizes."
724 print
725
Doug Zongker1c390a22009-05-14 19:06:36 -0700726 if OPTIONS.extra_script is not None:
727 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
728
Doug Zongkereef39442009-04-02 12:14:19 -0700729 print "unzipping target target-files..."
730 OPTIONS.input_tmp = common.UnzipTemp(args[0])
731 OPTIONS.target_tmp = OPTIONS.input_tmp
732 input_zip = zipfile.ZipFile(args[0], "r")
733 if OPTIONS.package_key:
734 temp_zip_file = tempfile.NamedTemporaryFile()
735 output_zip = zipfile.ZipFile(temp_zip_file, "w",
736 compression=zipfile.ZIP_DEFLATED)
737 else:
738 output_zip = zipfile.ZipFile(args[1], "w",
739 compression=zipfile.ZIP_DEFLATED)
740
741 if OPTIONS.incremental_source is None:
742 WriteFullOTAPackage(input_zip, output_zip)
743 else:
744 print "unzipping source target-files..."
745 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
746 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
747 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
748
749 output_zip.close()
750 if OPTIONS.package_key:
751 SignOutput(temp_zip_file.name, args[1])
752 temp_zip_file.close()
753
754 common.Cleanup()
755
756 print "done."
757
758
759if __name__ == '__main__':
760 try:
761 main(sys.argv[1:])
762 except common.ExternalError, e:
763 print
764 print " ERROR: %s" % (e,)
765 print
766 sys.exit(1)