blob: 4cda44a80e26923b01d6a735591158716a96dacb [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 AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700281 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700282 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700283
284 info = input_zip.read("OTA/android-info.txt")
285 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker9fc74c72009-06-23 16:27:38 -0700286 if m:
287 bootloaders = m.group(1).split("|")
288 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700289
290
Doug Zongker73ef8252009-07-23 15:12:53 -0700291def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
292 """Generate a binary patch that creates the recovery image starting
293 with the boot image. (Most of the space in these images is just the
294 kernel, which is identical for the two, so the resulting patch
295 should be efficient.) Add it to the output zip, along with a shell
296 script that is run from init.rc on first boot to actually do the
297 patching and install the new recovery image.
298
299 recovery_img and boot_img should be File objects for the
300 corresponding images.
301
302 Returns an Item for the shell script, which must be made
303 executable.
304 """
305
306 patch = Difference(recovery_img, boot_img, "imgdiff")
307 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
308 Item.Get("system/recovery-from-boot.p", dir=False)
309
310 # Images with different content will have a different first page, so
311 # we check to see if this recovery has already been installed by
312 # testing just the first 2k.
313 HEADER_SIZE = 2048
314 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
315 sh = """#!/system/bin/sh
316if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
317 log -t recovery "Installing new recovery image"
318 applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
319else
320 log -t recovery "Recovery image already installed"
321fi
322""" % { 'boot_size': boot_img.size,
323 'boot_sha1': boot_img.sha1,
324 'header_size': HEADER_SIZE,
325 'header_sha1': header_sha1,
326 'recovery_size': recovery_img.size,
327 'recovery_sha1': recovery_img.sha1 }
328 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
329 return Item.Get("system/etc/install-recovery.sh", dir=False)
330
331
Doug Zongkereef39442009-04-02 12:14:19 -0700332def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700333 if OPTIONS.script_mode == "auto":
334 script = both_generator.BothGenerator(2)
335 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700336 script = amend_generator.AmendGenerator()
337 else:
338 # TODO: how to determine this? We don't know what version it will
339 # be installed on top of. For now, we expect the API just won't
340 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700341 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700342
Doug Zongker962069c2009-04-23 11:41:58 -0700343 if not OPTIONS.omit_prereq:
344 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700345 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700346
347 AppendAssertions(script, input_zip)
348
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700349 script.ShowProgress(0.1, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700350
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700351 try:
352 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
353 script.WriteFirmwareImage("radio", "radio.img")
354 except KeyError:
355 print "warning: no radio image in input target_files; not flashing radio"
356
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700357 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700358
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700359 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700360 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700361
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700362 script.FormatPartition("system")
363 script.Mount("MTD", "system", "/system")
364 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700365
366 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700367 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700368
Doug Zongker73ef8252009-07-23 15:12:53 -0700369 boot_img = File("boot.img", common.BuildBootableImage(
370 os.path.join(OPTIONS.input_tmp, "BOOT")))
371 recovery_img = File("recovery.img", common.BuildBootableImage(
372 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
373 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Doug Zongker73ef8252009-07-23 15:12:53 -0700375 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700376
Doug Zongker73ef8252009-07-23 15:12:53 -0700377 # GetMetadata uses the data in android_filesystem_config.h to assign
378 # the uid/gid/mode of all files. We want to override that for the
379 # recovery patching shell script to make it executable.
380 i.uid = 0
381 i.gid = 0
382 i.mode = 0544
383 Item.Get("system").SetPermissions(script)
384
385 common.CheckSize(boot_img.data, "boot.img")
386 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.ShowProgress(0.2, 0)
388
389 script.WriteRawImage("boot", "boot.img")
390 script.ShowProgress(0.2, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700391
Doug Zongker1c390a22009-05-14 19:06:36 -0700392 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700393 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700394
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700395 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700396
397
398class File(object):
399 def __init__(self, name, data):
400 self.name = name
401 self.data = data
402 self.size = len(data)
403 self.sha1 = sha.sha(data).hexdigest()
404
405 def WriteToTemp(self):
406 t = tempfile.NamedTemporaryFile()
407 t.write(self.data)
408 t.flush()
409 return t
410
411 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700412 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700413
414
415def LoadSystemFiles(z):
416 """Load all the files from SYSTEM/... in a given target-files
417 ZipFile, and return a dict of {filename: File object}."""
418 out = {}
419 for info in z.infolist():
420 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
421 fn = "system/" + info.filename[7:]
422 data = z.read(info.filename)
423 out[fn] = File(fn, data)
424 return out
425
426
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700427def Difference(tf, sf, diff_program):
428 """Return the patch (as a string of data) needed to turn sf into tf.
429 diff_program is the name of an external program (or list, if
430 additional arguments are desired) to run to generate the diff.
431 """
Doug Zongkereef39442009-04-02 12:14:19 -0700432
433 ttemp = tf.WriteToTemp()
434 stemp = sf.WriteToTemp()
435
436 ext = os.path.splitext(tf.name)[1]
437
438 try:
439 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700440 if isinstance(diff_program, list):
441 cmd = copy.copy(diff_program)
442 else:
443 cmd = [diff_program]
444 cmd.append(stemp.name)
445 cmd.append(ttemp.name)
446 cmd.append(ptemp.name)
447 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700448 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700449 if err or p.returncode != 0:
450 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
451 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700452 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700453 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700454 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700455 stemp.close()
456 ttemp.close()
457
458 return diff
459
460
461def GetBuildProp(property, z):
462 """Return the fingerprint of the build of a given target-files
463 ZipFile object."""
464 bp = z.read("SYSTEM/build.prop")
465 if not property:
466 return bp
467 m = re.search(re.escape(property) + r"=(.*)\n", bp)
468 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700469 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700470 return m.group(1).strip()
471
472
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700473def GetRecoveryAPIVersion(zip):
474 """Returns the version of the recovery API. Version 0 is the older
475 amend code (no separate binary)."""
476 try:
477 version = zip.read("META/recovery-api-version.txt")
478 return int(version)
479 except KeyError:
480 try:
481 # version one didn't have the recovery-api-version.txt file, but
482 # it did include an updater binary.
483 zip.getinfo("OTA/bin/updater")
484 return 1
485 except KeyError:
486 return 0
487
Doug Zongkereef39442009-04-02 12:14:19 -0700488def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700489 source_version = GetRecoveryAPIVersion(source_zip)
490
491 if OPTIONS.script_mode == 'amend':
492 script = amend_generator.AmendGenerator()
493 elif OPTIONS.script_mode == 'edify':
494 if source_version == 0:
495 print ("WARNING: generating edify script for a source that "
496 "can't install it.")
497 script = edify_generator.EdifyGenerator(source_version)
498 elif OPTIONS.script_mode == 'auto':
499 if source_version > 0:
500 script = edify_generator.EdifyGenerator(source_version)
501 else:
502 script = amend_generator.AmendGenerator()
503 else:
504 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700505
506 print "Loading target..."
507 target_data = LoadSystemFiles(target_zip)
508 print "Loading source..."
509 source_data = LoadSystemFiles(source_zip)
510
511 verbatim_targets = []
512 patch_list = []
513 largest_source_size = 0
514 for fn in sorted(target_data.keys()):
515 tf = target_data[fn]
516 sf = source_data.get(fn, None)
517
518 if sf is None or fn in OPTIONS.require_verbatim:
519 # This file should be included verbatim
520 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700521 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700522 print "send", fn, "verbatim"
523 tf.AddToZip(output_zip)
524 verbatim_targets.append((fn, tf.size))
525 elif tf.sha1 != sf.sha1:
526 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700527 diff_method = "bsdiff"
528 if tf.name.endswith(".gz"):
529 diff_method = "imgdiff"
530 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700531 if d is not None:
532 print fn, tf.size, len(d), (float(len(d)) / tf.size)
533 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700534 # patch is almost as big as the file; don't bother patching
535 tf.AddToZip(output_zip)
536 verbatim_targets.append((fn, tf.size))
537 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700538 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700539 patch_list.append((fn, tf, sf, tf.size))
540 largest_source_size = max(largest_source_size, sf.size)
541 else:
542 # Target file identical to source.
543 pass
544
545 total_verbatim_size = sum([i[1] for i in verbatim_targets])
546 total_patched_size = sum([i[3] for i in patch_list])
547
548 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
549 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
550
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700551 script.Mount("MTD", "system", "/system")
552 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700553
Doug Zongker5da317e2009-06-02 13:38:17 -0700554 source_boot = File("/tmp/boot.img",
555 common.BuildBootableImage(
556 os.path.join(OPTIONS.source_tmp, "BOOT")))
557 target_boot = File("/tmp/boot.img",
558 common.BuildBootableImage(
559 os.path.join(OPTIONS.target_tmp, "BOOT")))
560 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700561
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700562 source_recovery = File("system/recovery.img",
563 common.BuildBootableImage(
564 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
565 target_recovery = File("system/recovery.img",
566 common.BuildBootableImage(
567 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
568 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700569
570 source_radio = source_zip.read("RADIO/image")
571 target_radio = target_zip.read("RADIO/image")
572 updating_radio = (source_radio != target_radio)
573
574 # The last 0.1 is reserved for creating symlinks, fixing
575 # permissions, and writing the boot image (if necessary).
576 progress_bar_total = 1.0
577 if updating_boot:
578 progress_bar_total -= 0.1
579 if updating_radio:
580 progress_bar_total -= 0.3
581
582 AppendAssertions(script, target_zip)
583
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700584 script.Print("Verifying current system...")
585
Doug Zongkereef39442009-04-02 12:14:19 -0700586 pb_verify = progress_bar_total * 0.3 * \
587 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700588 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700589
590 for i, (fn, tf, sf, size) in enumerate(patch_list):
591 if i % 5 == 0:
592 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700593 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
594
595 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700596
Doug Zongker5da317e2009-06-02 13:38:17 -0700597 if updating_boot:
598 d = Difference(target_boot, source_boot, "imgdiff")
599 print "boot target: %d source: %d diff: %d" % (
600 target_boot.size, source_boot.size, len(d))
601
Doug Zongker048e7ca2009-06-15 14:31:53 -0700602 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700603
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700604 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
605 (source_boot.size, source_boot.sha1,
606 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700607
608 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700609 script.CacheFreeSpaceCheck(largest_source_size)
610 script.Print("Unpacking patches...")
611 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700612
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700613 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700614
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700615 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700616 script.Print("Erasing user data...")
617 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700618
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700619 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700620 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
621 ["/"+i for i in sorted(source_data)
622 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700623
624 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700625 # Produce the boot image by applying a patch to the current
626 # contents of the boot partition, and write it back to the
627 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700628 script.Print("Patching boot image...")
629 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
630 % (source_boot.size, source_boot.sha1,
631 target_boot.size, target_boot.sha1),
632 "-",
633 target_boot.size, target_boot.sha1,
634 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700635 print "boot image changed; including."
636 else:
637 print "boot image unchanged; skipping."
638
639 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700640 # Is it better to generate recovery as a patch from the current
641 # boot image, or from the previous recovery image? For large
642 # updates with significant kernel changes, probably the former.
643 # For small updates where the kernel hasn't changed, almost
644 # certainly the latter. We pick the first option. Future
645 # complicated schemes may let us effectively use both.
646 #
647 # A wacky possibility: as long as there is room in the boot
648 # partition, include the binaries and image files from recovery in
649 # the boot image (though not in the ramdisk) so they can be used
650 # as fodder for constructing the recovery image.
651 recovery_sh_item = MakeRecoveryPatch(output_zip,
652 target_recovery, target_boot)
653 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700654 else:
655 print "recovery image unchanged; skipping."
656
657 if updating_radio:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700658 script.ShowProgress(0.3, 10)
659 script.Print("Writing radio image...")
660 script.WriteFirmwareImage("radio", "radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700661 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700662 print "radio image changed; including."
663 else:
664 print "radio image unchanged; skipping."
665
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700666 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700667 pb_apply = progress_bar_total * 0.7 * \
668 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700669 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700670 for i, (fn, tf, sf, size) in enumerate(patch_list):
671 if i % 5 == 0:
672 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700673 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
674 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
675 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700676
677 target_symlinks = CopySystemFiles(target_zip, None)
678
679 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700680 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700681 Item.GetMetadata()
682 if updating_recovery:
683 recovery_sh_item.uid = 0
684 recovery_sh_item.gid = 0
685 recovery_sh_item.mode = 0544
686 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700687
688 # Note that this call will mess up the tree of Items, so make sure
689 # we're done with it.
690 source_symlinks = CopySystemFiles(source_zip, None)
691 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
692
693 # Delete all the symlinks in source that aren't in target. This
694 # needs to happen before verbatim files are unpacked, in case a
695 # symlink in the source is replaced by a real file in the target.
696 to_delete = []
697 for dest, link in source_symlinks:
698 if link not in target_symlinks_d:
699 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700700 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700701
702 if verbatim_targets:
703 pb_verbatim = progress_bar_total * \
704 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700705 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700706 script.ShowProgress(pb_verbatim, 5)
707 script.Print("Unpacking new files...")
708 script.UnpackPackageDir("system", "/system")
709
710 script.Print("Finishing up...")
Doug Zongkereef39442009-04-02 12:14:19 -0700711
712 # Create all the symlinks that don't already exist, or point to
713 # somewhere different than what we want. Delete each symlink before
714 # creating it, since the 'symlink' command won't overwrite.
715 to_create = []
716 for dest, link in target_symlinks:
717 if link in source_symlinks_d:
718 if dest != source_symlinks_d[link]:
719 to_create.append((dest, link))
720 else:
721 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700722 script.DeleteFiles([i[1] for i in to_create])
723 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700724
725 # Now that the symlinks are created, we can set all the
726 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700727 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700728
Doug Zongker1c390a22009-05-14 19:06:36 -0700729 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700730 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700731
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700732 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700733
734
735def main(argv):
736
737 def option_handler(o, a):
738 if o in ("-b", "--board_config"):
739 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700740 elif o in ("-k", "--package_key"):
741 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700742 elif o in ("-i", "--incremental_from"):
743 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700744 elif o in ("-w", "--wipe_user_data"):
745 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700746 elif o in ("-n", "--no_prereq"):
747 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700748 elif o in ("-e", "--extra_script"):
749 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700750 elif o in ("-m", "--script_mode"):
751 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700752 else:
753 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700754 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700755
756 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700757 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700758 extra_long_opts=["board_config=",
759 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700760 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700761 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700762 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700763 "extra_script=",
764 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700765 extra_option_handler=option_handler)
766
767 if len(args) != 2:
768 common.Usage(__doc__)
769 sys.exit(1)
770
771 if not OPTIONS.max_image_size:
772 print
773 print " WARNING: No board config specified; will not check image"
774 print " sizes against limits. Use -b to make sure the generated"
775 print " images don't exceed partition sizes."
776 print
777
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700778 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
779 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
780
Doug Zongker1c390a22009-05-14 19:06:36 -0700781 if OPTIONS.extra_script is not None:
782 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
783
Doug Zongkereef39442009-04-02 12:14:19 -0700784 print "unzipping target target-files..."
785 OPTIONS.input_tmp = common.UnzipTemp(args[0])
786 OPTIONS.target_tmp = OPTIONS.input_tmp
787 input_zip = zipfile.ZipFile(args[0], "r")
788 if OPTIONS.package_key:
789 temp_zip_file = tempfile.NamedTemporaryFile()
790 output_zip = zipfile.ZipFile(temp_zip_file, "w",
791 compression=zipfile.ZIP_DEFLATED)
792 else:
793 output_zip = zipfile.ZipFile(args[1], "w",
794 compression=zipfile.ZIP_DEFLATED)
795
796 if OPTIONS.incremental_source is None:
797 WriteFullOTAPackage(input_zip, output_zip)
798 else:
799 print "unzipping source target-files..."
800 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
801 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
802 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
803
804 output_zip.close()
805 if OPTIONS.package_key:
806 SignOutput(temp_zip_file.name, args[1])
807 temp_zip_file.close()
808
809 common.Cleanup()
810
811 print "done."
812
813
814if __name__ == '__main__':
815 try:
816 main(sys.argv[1:])
817 except common.ExternalError, e:
818 print
819 print " ERROR: %s" % (e,)
820 print
821 sys.exit(1)