blob: 48645420b0c36dfc35ad5c4f3359860cfec6fd02 [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>
Doug Zongkerfdd8e692009-08-03 17:27:48 -070025 Deprecated.
Doug Zongkereef39442009-04-02 12:14:19 -070026
27 -k (--package_key) <key>
28 Key to use to sign the package (default is
29 "build/target/product/security/testkey").
30
31 -i (--incremental_from) <file>
32 Generate an incremental OTA using the given target-files zip as
33 the starting build.
34
Doug Zongkerdbfaae52009-04-21 17:12:54 -070035 -w (--wipe_user_data)
36 Generate an OTA package that will wipe the user data partition
37 when installed.
38
Doug Zongker962069c2009-04-23 11:41:58 -070039 -n (--no_prereq)
40 Omit the timestamp prereq check normally included at the top of
41 the build scripts (used for developer OTA packages which
42 legitimately need to go back and forth).
43
Doug Zongker1c390a22009-05-14 19:06:36 -070044 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
46
Doug Zongkerc494d7c2009-06-18 08:43:44 -070047 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
50
Doug Zongkereef39442009-04-02 12:14:19 -070051"""
52
53import sys
54
55if sys.hexversion < 0x02040000:
56 print >> sys.stderr, "Python 2.4 or newer is required."
57 sys.exit(1)
58
59import copy
60import os
61import re
62import sha
63import subprocess
64import tempfile
65import time
66import zipfile
67
68import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070069import amend_generator
70import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070071import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070072
73OPTIONS = common.OPTIONS
74OPTIONS.package_key = "build/target/product/security/testkey"
75OPTIONS.incremental_source = None
76OPTIONS.require_verbatim = set()
77OPTIONS.prohibit_verbatim = set(("system/build.prop",))
78OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070079OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070080OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070081OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070082OPTIONS.script_mode = 'auto'
Doug Zongkereef39442009-04-02 12:14:19 -070083
84def MostPopularKey(d, default):
85 """Given a dict, return the key corresponding to the largest
86 value. Returns 'default' if the dict is empty."""
87 x = [(v, k) for (k, v) in d.iteritems()]
88 if not x: return default
89 x.sort()
90 return x[-1][1]
91
92
93def IsSymlink(info):
94 """Return true if the zipfile.ZipInfo object passed in represents a
95 symlink."""
96 return (info.external_attr >> 16) == 0120777
97
98
99
100class Item:
101 """Items represent the metadata (user, group, mode) of files and
102 directories in the system image."""
103 ITEMS = {}
104 def __init__(self, name, dir=False):
105 self.name = name
106 self.uid = None
107 self.gid = None
108 self.mode = None
109 self.dir = dir
110
111 if name:
112 self.parent = Item.Get(os.path.dirname(name), dir=True)
113 self.parent.children.append(self)
114 else:
115 self.parent = None
116 if dir:
117 self.children = []
118
119 def Dump(self, indent=0):
120 if self.uid is not None:
121 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
122 else:
123 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
124 if self.dir:
125 print "%s%s" % (" "*indent, self.descendants)
126 print "%s%s" % (" "*indent, self.best_subtree)
127 for i in self.children:
128 i.Dump(indent=indent+1)
129
130 @classmethod
131 def Get(cls, name, dir=False):
132 if name not in cls.ITEMS:
133 cls.ITEMS[name] = Item(name, dir=dir)
134 return cls.ITEMS[name]
135
136 @classmethod
137 def GetMetadata(cls):
138 """Run the external 'fs_config' program to determine the desired
139 uid, gid, and mode for every Item object."""
140 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
141 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142 suffix = { False: "", True: "/" }
143 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
144 for i in cls.ITEMS.itervalues() if i.name])
145 output, error = p.communicate(input)
146 assert not error
147
148 for line in output.split("\n"):
149 if not line: continue
150 name, uid, gid, mode = line.split()
151 i = cls.ITEMS[name]
152 i.uid = int(uid)
153 i.gid = int(gid)
154 i.mode = int(mode, 8)
155 if i.dir:
156 i.children.sort(key=lambda i: i.name)
157
158 def CountChildMetadata(self):
159 """Count up the (uid, gid, mode) tuples for all children and
160 determine the best strategy for using set_perm_recursive and
161 set_perm to correctly chown/chmod all the files to their desired
162 values. Recursively calls itself for all descendants.
163
164 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
165 all descendants of this node. (dmode or fmode may be None.) Also
166 sets the best_subtree of each directory Item to the (uid, gid,
167 dmode, fmode) tuple that will match the most descendants of that
168 Item.
169 """
170
171 assert self.dir
172 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
173 for i in self.children:
174 if i.dir:
175 for k, v in i.CountChildMetadata().iteritems():
176 d[k] = d.get(k, 0) + v
177 else:
178 k = (i.uid, i.gid, None, i.mode)
179 d[k] = d.get(k, 0) + 1
180
181 # Find the (uid, gid, dmode, fmode) tuple that matches the most
182 # descendants.
183
184 # First, find the (uid, gid) pair that matches the most
185 # descendants.
186 ug = {}
187 for (uid, gid, _, _), count in d.iteritems():
188 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
189 ug = MostPopularKey(ug, (0, 0))
190
191 # Now find the dmode and fmode that match the most descendants
192 # with that (uid, gid), and choose those.
193 best_dmode = (0, 0755)
194 best_fmode = (0, 0644)
195 for k, count in d.iteritems():
196 if k[:2] != ug: continue
197 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
198 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
199 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
200
201 return d
202
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700203 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700204 """Append set_perm/set_perm_recursive commands to 'script' to
205 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700206 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700207
208 self.CountChildMetadata()
209
210 def recurse(item, current):
211 # current is the (uid, gid, dmode, fmode) tuple that the current
212 # item (and all its children) have already been set to. We only
213 # need to issue set_perm/set_perm_recursive commands if we're
214 # supposed to be something different.
215 if item.dir:
216 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700217 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700218 current = item.best_subtree
219
220 if item.uid != current[0] or item.gid != current[1] or \
221 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700222 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700223
224 for i in item.children:
225 recurse(i, current)
226 else:
227 if item.uid != current[0] or item.gid != current[1] or \
228 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700229 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700230
231 recurse(self, (-1, -1, -1, -1))
232
233
234def CopySystemFiles(input_zip, output_zip=None,
235 substitute=None):
236 """Copies files underneath system/ in the input zip to the output
237 zip. Populates the Item class with their metadata, and returns a
238 list of symlinks. output_zip may be None, in which case the copy is
239 skipped (but the other side effects still happen). substitute is an
240 optional dict of {output filename: contents} to be output instead of
241 certain input files.
242 """
243
244 symlinks = []
245
246 for info in input_zip.infolist():
247 if info.filename.startswith("SYSTEM/"):
248 basefilename = info.filename[7:]
249 if IsSymlink(info):
250 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700251 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700252 else:
253 info2 = copy.copy(info)
254 fn = info2.filename = "system/" + basefilename
255 if substitute and fn in substitute and substitute[fn] is None:
256 continue
257 if output_zip is not None:
258 if substitute and fn in substitute:
259 data = substitute[fn]
260 else:
261 data = input_zip.read(info.filename)
262 output_zip.writestr(info2, data)
263 if fn.endswith("/"):
264 Item.Get(fn[:-1], dir=True)
265 else:
266 Item.Get(fn, dir=False)
267
268 symlinks.sort()
269 return symlinks
270
271
Doug Zongkereef39442009-04-02 12:14:19 -0700272def SignOutput(temp_zip_name, output_zip_name):
273 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
274 pw = key_passwords[OPTIONS.package_key]
275
276 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
277
278
Doug Zongkereef39442009-04-02 12:14:19 -0700279def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700280 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700281 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700282
283 info = input_zip.read("OTA/android-info.txt")
284 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker9fc74c72009-06-23 16:27:38 -0700285 if m:
286 bootloaders = m.group(1).split("|")
287 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700288
289
Doug Zongker73ef8252009-07-23 15:12:53 -0700290def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
291 """Generate a binary patch that creates the recovery image starting
292 with the boot image. (Most of the space in these images is just the
293 kernel, which is identical for the two, so the resulting patch
294 should be efficient.) Add it to the output zip, along with a shell
295 script that is run from init.rc on first boot to actually do the
296 patching and install the new recovery image.
297
298 recovery_img and boot_img should be File objects for the
299 corresponding images.
300
301 Returns an Item for the shell script, which must be made
302 executable.
303 """
304
305 patch = Difference(recovery_img, boot_img, "imgdiff")
306 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
307 Item.Get("system/recovery-from-boot.p", dir=False)
308
309 # Images with different content will have a different first page, so
310 # we check to see if this recovery has already been installed by
311 # testing just the first 2k.
312 HEADER_SIZE = 2048
313 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
314 sh = """#!/system/bin/sh
315if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
316 log -t recovery "Installing new recovery image"
317 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
318else
319 log -t recovery "Recovery image already installed"
320fi
321""" % { 'boot_size': boot_img.size,
322 'boot_sha1': boot_img.sha1,
323 'header_size': HEADER_SIZE,
324 'header_sha1': header_sha1,
325 'recovery_size': recovery_img.size,
326 'recovery_sha1': recovery_img.sha1 }
327 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
328 return Item.Get("system/etc/install-recovery.sh", dir=False)
329
330
Doug Zongkereef39442009-04-02 12:14:19 -0700331def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700332 if OPTIONS.script_mode == "auto":
333 script = both_generator.BothGenerator(2)
334 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700335 script = amend_generator.AmendGenerator()
336 else:
337 # TODO: how to determine this? We don't know what version it will
338 # be installed on top of. For now, we expect the API just won't
339 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700340 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700341
Doug Zongker962069c2009-04-23 11:41:58 -0700342 if not OPTIONS.omit_prereq:
343 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700344 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700345
346 AppendAssertions(script, input_zip)
347
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700348 script.ShowProgress(0.1, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700349
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700350 try:
351 common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
352 script.WriteFirmwareImage("radio", "radio.img")
353 except KeyError:
354 print "warning: no radio image in input target_files; not flashing radio"
355
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700356 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700357
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700358 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700359 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700360
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700361 script.FormatPartition("system")
362 script.Mount("MTD", "system", "/system")
363 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700364
365 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700366 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700367
Doug Zongker73ef8252009-07-23 15:12:53 -0700368 boot_img = File("boot.img", common.BuildBootableImage(
369 os.path.join(OPTIONS.input_tmp, "BOOT")))
370 recovery_img = File("recovery.img", common.BuildBootableImage(
371 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
372 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700373
Doug Zongker73ef8252009-07-23 15:12:53 -0700374 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700375
Doug Zongker73ef8252009-07-23 15:12:53 -0700376 # GetMetadata uses the data in android_filesystem_config.h to assign
377 # the uid/gid/mode of all files. We want to override that for the
378 # recovery patching shell script to make it executable.
379 i.uid = 0
380 i.gid = 0
381 i.mode = 0544
382 Item.Get("system").SetPermissions(script)
383
384 common.CheckSize(boot_img.data, "boot.img")
385 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700386 script.ShowProgress(0.2, 0)
387
388 script.WriteRawImage("boot", "boot.img")
389 script.ShowProgress(0.2, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700390
Doug Zongker1c390a22009-05-14 19:06:36 -0700391 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700392 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700393
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700394 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700395
396
397class File(object):
398 def __init__(self, name, data):
399 self.name = name
400 self.data = data
401 self.size = len(data)
402 self.sha1 = sha.sha(data).hexdigest()
403
404 def WriteToTemp(self):
405 t = tempfile.NamedTemporaryFile()
406 t.write(self.data)
407 t.flush()
408 return t
409
410 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700411 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700412
413
414def LoadSystemFiles(z):
415 """Load all the files from SYSTEM/... in a given target-files
416 ZipFile, and return a dict of {filename: File object}."""
417 out = {}
418 for info in z.infolist():
419 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
420 fn = "system/" + info.filename[7:]
421 data = z.read(info.filename)
422 out[fn] = File(fn, data)
423 return out
424
425
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700426def Difference(tf, sf, diff_program):
427 """Return the patch (as a string of data) needed to turn sf into tf.
428 diff_program is the name of an external program (or list, if
429 additional arguments are desired) to run to generate the diff.
430 """
Doug Zongkereef39442009-04-02 12:14:19 -0700431
432 ttemp = tf.WriteToTemp()
433 stemp = sf.WriteToTemp()
434
435 ext = os.path.splitext(tf.name)[1]
436
437 try:
438 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700439 if isinstance(diff_program, list):
440 cmd = copy.copy(diff_program)
441 else:
442 cmd = [diff_program]
443 cmd.append(stemp.name)
444 cmd.append(ttemp.name)
445 cmd.append(ptemp.name)
446 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700447 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700448 if err or p.returncode != 0:
449 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
450 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700451 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700452 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700453 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700454 stemp.close()
455 ttemp.close()
456
457 return diff
458
459
460def GetBuildProp(property, z):
461 """Return the fingerprint of the build of a given target-files
462 ZipFile object."""
463 bp = z.read("SYSTEM/build.prop")
464 if not property:
465 return bp
466 m = re.search(re.escape(property) + r"=(.*)\n", bp)
467 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700468 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700469 return m.group(1).strip()
470
471
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700472def GetRecoveryAPIVersion(zip):
473 """Returns the version of the recovery API. Version 0 is the older
474 amend code (no separate binary)."""
475 try:
476 version = zip.read("META/recovery-api-version.txt")
477 return int(version)
478 except KeyError:
479 try:
480 # version one didn't have the recovery-api-version.txt file, but
481 # it did include an updater binary.
482 zip.getinfo("OTA/bin/updater")
483 return 1
484 except KeyError:
485 return 0
486
Doug Zongkereef39442009-04-02 12:14:19 -0700487def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700488 source_version = GetRecoveryAPIVersion(source_zip)
489
490 if OPTIONS.script_mode == 'amend':
491 script = amend_generator.AmendGenerator()
492 elif OPTIONS.script_mode == 'edify':
493 if source_version == 0:
494 print ("WARNING: generating edify script for a source that "
495 "can't install it.")
496 script = edify_generator.EdifyGenerator(source_version)
497 elif OPTIONS.script_mode == 'auto':
498 if source_version > 0:
499 script = edify_generator.EdifyGenerator(source_version)
500 else:
501 script = amend_generator.AmendGenerator()
502 else:
503 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700504
505 print "Loading target..."
506 target_data = LoadSystemFiles(target_zip)
507 print "Loading source..."
508 source_data = LoadSystemFiles(source_zip)
509
510 verbatim_targets = []
511 patch_list = []
512 largest_source_size = 0
513 for fn in sorted(target_data.keys()):
514 tf = target_data[fn]
515 sf = source_data.get(fn, None)
516
517 if sf is None or fn in OPTIONS.require_verbatim:
518 # This file should be included verbatim
519 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700520 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700521 print "send", fn, "verbatim"
522 tf.AddToZip(output_zip)
523 verbatim_targets.append((fn, tf.size))
524 elif tf.sha1 != sf.sha1:
525 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700526 diff_method = "bsdiff"
527 if tf.name.endswith(".gz"):
528 diff_method = "imgdiff"
529 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700530 if d is not None:
531 print fn, tf.size, len(d), (float(len(d)) / tf.size)
532 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700533 # patch is almost as big as the file; don't bother patching
534 tf.AddToZip(output_zip)
535 verbatim_targets.append((fn, tf.size))
536 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700537 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700538 patch_list.append((fn, tf, sf, tf.size))
539 largest_source_size = max(largest_source_size, sf.size)
540 else:
541 # Target file identical to source.
542 pass
543
544 total_verbatim_size = sum([i[1] for i in verbatim_targets])
545 total_patched_size = sum([i[3] for i in patch_list])
546
547 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
548 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
549
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700550 script.Mount("MTD", "system", "/system")
551 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700552
Doug Zongker5da317e2009-06-02 13:38:17 -0700553 source_boot = File("/tmp/boot.img",
554 common.BuildBootableImage(
555 os.path.join(OPTIONS.source_tmp, "BOOT")))
556 target_boot = File("/tmp/boot.img",
557 common.BuildBootableImage(
558 os.path.join(OPTIONS.target_tmp, "BOOT")))
559 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700560
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700561 source_recovery = File("system/recovery.img",
562 common.BuildBootableImage(
563 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
564 target_recovery = File("system/recovery.img",
565 common.BuildBootableImage(
566 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
567 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700568
569 source_radio = source_zip.read("RADIO/image")
570 target_radio = target_zip.read("RADIO/image")
571 updating_radio = (source_radio != target_radio)
572
573 # The last 0.1 is reserved for creating symlinks, fixing
574 # permissions, and writing the boot image (if necessary).
575 progress_bar_total = 1.0
576 if updating_boot:
577 progress_bar_total -= 0.1
578 if updating_radio:
579 progress_bar_total -= 0.3
580
581 AppendAssertions(script, target_zip)
582
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700583 script.Print("Verifying current system...")
584
Doug Zongkereef39442009-04-02 12:14:19 -0700585 pb_verify = progress_bar_total * 0.3 * \
586 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700587 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700588
589 for i, (fn, tf, sf, size) in enumerate(patch_list):
590 if i % 5 == 0:
591 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700592 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
593
594 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700595
Doug Zongker5da317e2009-06-02 13:38:17 -0700596 if updating_boot:
597 d = Difference(target_boot, source_boot, "imgdiff")
598 print "boot target: %d source: %d diff: %d" % (
599 target_boot.size, source_boot.size, len(d))
600
Doug Zongker048e7ca2009-06-15 14:31:53 -0700601 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700602
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700603 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
604 (source_boot.size, source_boot.sha1,
605 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700606
607 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700608 script.CacheFreeSpaceCheck(largest_source_size)
609 script.Print("Unpacking patches...")
610 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700611
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700612 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700613
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700614 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700615 script.Print("Erasing user data...")
616 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700617
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700618 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700619 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
620 ["/"+i for i in sorted(source_data)
621 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700622
623 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700624 # Produce the boot image by applying a patch to the current
625 # contents of the boot partition, and write it back to the
626 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700627 script.Print("Patching boot image...")
628 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
629 % (source_boot.size, source_boot.sha1,
630 target_boot.size, target_boot.sha1),
631 "-",
632 target_boot.size, target_boot.sha1,
633 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700634 print "boot image changed; including."
635 else:
636 print "boot image unchanged; skipping."
637
638 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700639 # Is it better to generate recovery as a patch from the current
640 # boot image, or from the previous recovery image? For large
641 # updates with significant kernel changes, probably the former.
642 # For small updates where the kernel hasn't changed, almost
643 # certainly the latter. We pick the first option. Future
644 # complicated schemes may let us effectively use both.
645 #
646 # A wacky possibility: as long as there is room in the boot
647 # partition, include the binaries and image files from recovery in
648 # the boot image (though not in the ramdisk) so they can be used
649 # as fodder for constructing the recovery image.
650 recovery_sh_item = MakeRecoveryPatch(output_zip,
651 target_recovery, target_boot)
652 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700653 else:
654 print "recovery image unchanged; skipping."
655
656 if updating_radio:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700657 script.ShowProgress(0.3, 10)
658 script.Print("Writing radio image...")
659 script.WriteFirmwareImage("radio", "radio.img")
Doug Zongker048e7ca2009-06-15 14:31:53 -0700660 common.ZipWriteStr(output_zip, "radio.img", target_radio)
Doug Zongkereef39442009-04-02 12:14:19 -0700661 print "radio image changed; including."
662 else:
663 print "radio image unchanged; skipping."
664
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700665 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700666 pb_apply = progress_bar_total * 0.7 * \
667 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700668 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700669 for i, (fn, tf, sf, size) in enumerate(patch_list):
670 if i % 5 == 0:
671 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700672 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
673 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
674 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700675
676 target_symlinks = CopySystemFiles(target_zip, None)
677
678 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700680 Item.GetMetadata()
681 if updating_recovery:
682 recovery_sh_item.uid = 0
683 recovery_sh_item.gid = 0
684 recovery_sh_item.mode = 0544
685 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700686
687 # Note that this call will mess up the tree of Items, so make sure
688 # we're done with it.
689 source_symlinks = CopySystemFiles(source_zip, None)
690 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
691
692 # Delete all the symlinks in source that aren't in target. This
693 # needs to happen before verbatim files are unpacked, in case a
694 # symlink in the source is replaced by a real file in the target.
695 to_delete = []
696 for dest, link in source_symlinks:
697 if link not in target_symlinks_d:
698 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700699 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700700
701 if verbatim_targets:
702 pb_verbatim = progress_bar_total * \
703 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700704 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700705 script.ShowProgress(pb_verbatim, 5)
706 script.Print("Unpacking new files...")
707 script.UnpackPackageDir("system", "/system")
708
709 script.Print("Finishing up...")
Doug Zongkereef39442009-04-02 12:14:19 -0700710
711 # Create all the symlinks that don't already exist, or point to
712 # somewhere different than what we want. Delete each symlink before
713 # creating it, since the 'symlink' command won't overwrite.
714 to_create = []
715 for dest, link in target_symlinks:
716 if link in source_symlinks_d:
717 if dest != source_symlinks_d[link]:
718 to_create.append((dest, link))
719 else:
720 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700721 script.DeleteFiles([i[1] for i in to_create])
722 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700723
724 # Now that the symlinks are created, we can set all the
725 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700726 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700727
Doug Zongker1c390a22009-05-14 19:06:36 -0700728 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700729 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700730
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700731 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700732
733
734def main(argv):
735
736 def option_handler(o, a):
737 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700738 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700739 elif o in ("-k", "--package_key"):
740 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700741 elif o in ("-i", "--incremental_from"):
742 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700743 elif o in ("-w", "--wipe_user_data"):
744 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700745 elif o in ("-n", "--no_prereq"):
746 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700747 elif o in ("-e", "--extra_script"):
748 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700749 elif o in ("-m", "--script_mode"):
750 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700751 else:
752 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700753 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700754
755 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700756 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700757 extra_long_opts=["board_config=",
758 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700759 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700760 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700761 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700762 "extra_script=",
763 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700764 extra_option_handler=option_handler)
765
766 if len(args) != 2:
767 common.Usage(__doc__)
768 sys.exit(1)
769
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700770 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
771 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
772
Doug Zongker1c390a22009-05-14 19:06:36 -0700773 if OPTIONS.extra_script is not None:
774 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
775
Doug Zongkereef39442009-04-02 12:14:19 -0700776 print "unzipping target target-files..."
777 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700778
779 common.LoadMaxSizes()
780 if not OPTIONS.max_image_size:
781 print
782 print " WARNING: Failed to load max image sizes; will not enforce"
783 print " image size limits."
784 print
785
Doug Zongkereef39442009-04-02 12:14:19 -0700786 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)