blob: 70ab55f8e3f72debcb0dc4c56beba472ce112bda [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
Doug Zongkerc18736b2009-09-30 09:20:32 -070060import errno
Doug Zongkereef39442009-04-02 12:14:19 -070061import os
62import re
63import sha
64import subprocess
65import tempfile
Doug Zongker761e6422009-09-25 10:45:39 -070066import threading
Doug Zongkereef39442009-04-02 12:14:19 -070067import time
68import zipfile
69
70import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070071import amend_generator
72import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070073import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070074
75OPTIONS = common.OPTIONS
76OPTIONS.package_key = "build/target/product/security/testkey"
77OPTIONS.incremental_source = None
78OPTIONS.require_verbatim = set()
79OPTIONS.prohibit_verbatim = set(("system/build.prop",))
80OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070081OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070082OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070083OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070084OPTIONS.script_mode = 'auto'
Doug Zongker761e6422009-09-25 10:45:39 -070085OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070086
87def MostPopularKey(d, default):
88 """Given a dict, return the key corresponding to the largest
89 value. Returns 'default' if the dict is empty."""
90 x = [(v, k) for (k, v) in d.iteritems()]
91 if not x: return default
92 x.sort()
93 return x[-1][1]
94
95
96def IsSymlink(info):
97 """Return true if the zipfile.ZipInfo object passed in represents a
98 symlink."""
99 return (info.external_attr >> 16) == 0120777
100
101
102
103class Item:
104 """Items represent the metadata (user, group, mode) of files and
105 directories in the system image."""
106 ITEMS = {}
107 def __init__(self, name, dir=False):
108 self.name = name
109 self.uid = None
110 self.gid = None
111 self.mode = None
112 self.dir = dir
113
114 if name:
115 self.parent = Item.Get(os.path.dirname(name), dir=True)
116 self.parent.children.append(self)
117 else:
118 self.parent = None
119 if dir:
120 self.children = []
121
122 def Dump(self, indent=0):
123 if self.uid is not None:
124 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
125 else:
126 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
127 if self.dir:
128 print "%s%s" % (" "*indent, self.descendants)
129 print "%s%s" % (" "*indent, self.best_subtree)
130 for i in self.children:
131 i.Dump(indent=indent+1)
132
133 @classmethod
134 def Get(cls, name, dir=False):
135 if name not in cls.ITEMS:
136 cls.ITEMS[name] = Item(name, dir=dir)
137 return cls.ITEMS[name]
138
139 @classmethod
140 def GetMetadata(cls):
141 """Run the external 'fs_config' program to determine the desired
142 uid, gid, and mode for every Item object."""
143 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
144 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
145 suffix = { False: "", True: "/" }
146 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
147 for i in cls.ITEMS.itervalues() if i.name])
148 output, error = p.communicate(input)
149 assert not error
150
151 for line in output.split("\n"):
152 if not line: continue
153 name, uid, gid, mode = line.split()
154 i = cls.ITEMS[name]
155 i.uid = int(uid)
156 i.gid = int(gid)
157 i.mode = int(mode, 8)
158 if i.dir:
159 i.children.sort(key=lambda i: i.name)
160
161 def CountChildMetadata(self):
162 """Count up the (uid, gid, mode) tuples for all children and
163 determine the best strategy for using set_perm_recursive and
164 set_perm to correctly chown/chmod all the files to their desired
165 values. Recursively calls itself for all descendants.
166
167 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
168 all descendants of this node. (dmode or fmode may be None.) Also
169 sets the best_subtree of each directory Item to the (uid, gid,
170 dmode, fmode) tuple that will match the most descendants of that
171 Item.
172 """
173
174 assert self.dir
175 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
176 for i in self.children:
177 if i.dir:
178 for k, v in i.CountChildMetadata().iteritems():
179 d[k] = d.get(k, 0) + v
180 else:
181 k = (i.uid, i.gid, None, i.mode)
182 d[k] = d.get(k, 0) + 1
183
184 # Find the (uid, gid, dmode, fmode) tuple that matches the most
185 # descendants.
186
187 # First, find the (uid, gid) pair that matches the most
188 # descendants.
189 ug = {}
190 for (uid, gid, _, _), count in d.iteritems():
191 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
192 ug = MostPopularKey(ug, (0, 0))
193
194 # Now find the dmode and fmode that match the most descendants
195 # with that (uid, gid), and choose those.
196 best_dmode = (0, 0755)
197 best_fmode = (0, 0644)
198 for k, count in d.iteritems():
199 if k[:2] != ug: continue
200 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
201 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
202 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
203
204 return d
205
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700206 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700207 """Append set_perm/set_perm_recursive commands to 'script' to
208 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700209 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700210
211 self.CountChildMetadata()
212
213 def recurse(item, current):
214 # current is the (uid, gid, dmode, fmode) tuple that the current
215 # item (and all its children) have already been set to. We only
216 # need to issue set_perm/set_perm_recursive commands if we're
217 # supposed to be something different.
218 if item.dir:
219 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700220 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700221 current = item.best_subtree
222
223 if item.uid != current[0] or item.gid != current[1] or \
224 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700225 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700226
227 for i in item.children:
228 recurse(i, current)
229 else:
230 if item.uid != current[0] or item.gid != current[1] or \
231 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700232 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700233
234 recurse(self, (-1, -1, -1, -1))
235
236
237def CopySystemFiles(input_zip, output_zip=None,
238 substitute=None):
239 """Copies files underneath system/ in the input zip to the output
240 zip. Populates the Item class with their metadata, and returns a
241 list of symlinks. output_zip may be None, in which case the copy is
242 skipped (but the other side effects still happen). substitute is an
243 optional dict of {output filename: contents} to be output instead of
244 certain input files.
245 """
246
247 symlinks = []
248
249 for info in input_zip.infolist():
250 if info.filename.startswith("SYSTEM/"):
251 basefilename = info.filename[7:]
252 if IsSymlink(info):
253 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700254 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700255 else:
256 info2 = copy.copy(info)
257 fn = info2.filename = "system/" + basefilename
258 if substitute and fn in substitute and substitute[fn] is None:
259 continue
260 if output_zip is not None:
261 if substitute and fn in substitute:
262 data = substitute[fn]
263 else:
264 data = input_zip.read(info.filename)
265 output_zip.writestr(info2, data)
266 if fn.endswith("/"):
267 Item.Get(fn[:-1], dir=True)
268 else:
269 Item.Get(fn, dir=False)
270
271 symlinks.sort()
272 return symlinks
273
274
Doug Zongkereef39442009-04-02 12:14:19 -0700275def SignOutput(temp_zip_name, output_zip_name):
276 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
277 pw = key_passwords[OPTIONS.package_key]
278
Doug Zongker951495f2009-08-14 12:44:19 -0700279 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
280 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700281
282
Doug Zongkereef39442009-04-02 12:14:19 -0700283def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700284 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700285 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700286
Doug Zongkereef39442009-04-02 12:14:19 -0700287
Doug Zongker73ef8252009-07-23 15:12:53 -0700288def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
289 """Generate a binary patch that creates the recovery image starting
290 with the boot image. (Most of the space in these images is just the
291 kernel, which is identical for the two, so the resulting patch
292 should be efficient.) Add it to the output zip, along with a shell
293 script that is run from init.rc on first boot to actually do the
294 patching and install the new recovery image.
295
296 recovery_img and boot_img should be File objects for the
297 corresponding images.
298
299 Returns an Item for the shell script, which must be made
300 executable.
301 """
302
Doug Zongker761e6422009-09-25 10:45:39 -0700303 d = Difference(recovery_img, boot_img)
304 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700305 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700306 Item.Get("system/recovery-from-boot.p", dir=False)
307
308 # Images with different content will have a different first page, so
309 # we check to see if this recovery has already been installed by
310 # testing just the first 2k.
311 HEADER_SIZE = 2048
312 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
313 sh = """#!/system/bin/sh
314if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
315 log -t recovery "Installing new recovery image"
316 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
317else
318 log -t recovery "Recovery image already installed"
319fi
320""" % { 'boot_size': boot_img.size,
321 'boot_sha1': boot_img.sha1,
322 'header_size': HEADER_SIZE,
323 'header_sha1': header_sha1,
324 'recovery_size': recovery_img.size,
325 'recovery_sha1': recovery_img.sha1 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700326 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700327 return Item.Get("system/etc/install-recovery.sh", dir=False)
328
329
Doug Zongkereef39442009-04-02 12:14:19 -0700330def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700331 if OPTIONS.script_mode == "auto":
332 script = both_generator.BothGenerator(2)
333 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700334 script = amend_generator.AmendGenerator()
335 else:
336 # TODO: how to determine this? We don't know what version it will
337 # be installed on top of. For now, we expect the API just won't
338 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700339 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Doug Zongker05d3dea2009-06-22 11:32:31 -0700341 device_specific = common.DeviceSpecificParams(
342 input_zip=input_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800343 input_version=GetRecoveryAPIVersion(input_zip),
Doug Zongker05d3dea2009-06-22 11:32:31 -0700344 output_zip=output_zip,
345 script=script,
346 input_tmp=OPTIONS.input_tmp)
347
Doug Zongker962069c2009-04-23 11:41:58 -0700348 if not OPTIONS.omit_prereq:
349 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700350 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700351
352 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700353 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700354
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700355 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700356
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700357 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700358 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700359
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700360 script.FormatPartition("system")
361 script.Mount("MTD", "system", "/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700362 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700363 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
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700388 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700389 script.WriteRawImage("boot", "boot.img")
390
391 script.ShowProgress(0.1, 0)
392 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700393
Doug Zongker1c390a22009-05-14 19:06:36 -0700394 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700395 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700396
Doug Zongker14833602010-02-02 13:12:04 -0800397 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700398 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
400
401class File(object):
402 def __init__(self, name, data):
403 self.name = name
404 self.data = data
405 self.size = len(data)
406 self.sha1 = sha.sha(data).hexdigest()
407
408 def WriteToTemp(self):
409 t = tempfile.NamedTemporaryFile()
410 t.write(self.data)
411 t.flush()
412 return t
413
414 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700415 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700416
417
418def LoadSystemFiles(z):
419 """Load all the files from SYSTEM/... in a given target-files
420 ZipFile, and return a dict of {filename: File object}."""
421 out = {}
422 for info in z.infolist():
423 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
424 fn = "system/" + info.filename[7:]
425 data = z.read(info.filename)
426 out[fn] = File(fn, data)
427 return out
428
429
Doug Zongker761e6422009-09-25 10:45:39 -0700430DIFF_PROGRAM_BY_EXT = {
431 ".gz" : "imgdiff",
432 ".zip" : ["imgdiff", "-z"],
433 ".jar" : ["imgdiff", "-z"],
434 ".apk" : ["imgdiff", "-z"],
435 ".img" : "imgdiff",
436 }
Doug Zongkereef39442009-04-02 12:14:19 -0700437
Doug Zongkereef39442009-04-02 12:14:19 -0700438
Doug Zongker761e6422009-09-25 10:45:39 -0700439class Difference(object):
440 def __init__(self, tf, sf):
441 self.tf = tf
442 self.sf = sf
443 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongker761e6422009-09-25 10:45:39 -0700445 def ComputePatch(self):
446 """Compute the patch (as a string of data) needed to turn sf into
447 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700448
Doug Zongker761e6422009-09-25 10:45:39 -0700449 tf = self.tf
450 sf = self.sf
451
452 ext = os.path.splitext(tf.name)[1]
453 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
454
455 ttemp = tf.WriteToTemp()
456 stemp = sf.WriteToTemp()
457
458 ext = os.path.splitext(tf.name)[1]
459
460 try:
461 ptemp = tempfile.NamedTemporaryFile()
462 if isinstance(diff_program, list):
463 cmd = copy.copy(diff_program)
464 else:
465 cmd = [diff_program]
466 cmd.append(stemp.name)
467 cmd.append(ttemp.name)
468 cmd.append(ptemp.name)
469 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
470 _, err = p.communicate()
471 if err or p.returncode != 0:
472 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
473 return None
474 diff = ptemp.read()
475 finally:
476 ptemp.close()
477 stemp.close()
478 ttemp.close()
479
480 self.patch = diff
481 return self.tf, self.sf, self.patch
482
483
484 def GetPatch(self):
485 """Return a tuple (target_file, source_file, patch_data).
486 patch_data may be None if ComputePatch hasn't been called, or if
487 computing the patch failed."""
488 return self.tf, self.sf, self.patch
489
490
491def ComputeDifferences(diffs):
492 """Call ComputePatch on all the Difference objects in 'diffs'."""
493 print len(diffs), "diffs to compute"
494
495 # Do the largest files first, to try and reduce the long-pole effect.
496 by_size = [(i.tf.size, i) for i in diffs]
497 by_size.sort(reverse=True)
498 by_size = [i[1] for i in by_size]
499
500 lock = threading.Lock()
501 diff_iter = iter(by_size) # accessed under lock
502
503 def worker():
504 try:
505 lock.acquire()
506 for d in diff_iter:
507 lock.release()
508 start = time.time()
509 d.ComputePatch()
510 dur = time.time() - start
511 lock.acquire()
512
513 tf, sf, patch = d.GetPatch()
514 if sf.name == tf.name:
515 name = tf.name
516 else:
517 name = "%s (%s)" % (tf.name, sf.name)
518 if patch is None:
519 print "patching failed! %s" % (name,)
520 else:
521 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
522 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
523 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700524 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700525 print e
526 raise
527
528 # start worker threads; wait for them all to finish.
529 threads = [threading.Thread(target=worker)
530 for i in range(OPTIONS.worker_threads)]
531 for th in threads:
532 th.start()
533 while threads:
534 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700535
536
537def GetBuildProp(property, z):
538 """Return the fingerprint of the build of a given target-files
539 ZipFile object."""
540 bp = z.read("SYSTEM/build.prop")
541 if not property:
542 return bp
543 m = re.search(re.escape(property) + r"=(.*)\n", bp)
544 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700545 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700546 return m.group(1).strip()
547
548
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700549def GetRecoveryAPIVersion(zip):
550 """Returns the version of the recovery API. Version 0 is the older
551 amend code (no separate binary)."""
552 try:
553 version = zip.read("META/recovery-api-version.txt")
554 return int(version)
555 except KeyError:
556 try:
557 # version one didn't have the recovery-api-version.txt file, but
558 # it did include an updater binary.
559 zip.getinfo("OTA/bin/updater")
560 return 1
561 except KeyError:
562 return 0
563
Doug Zongker15604b82009-09-01 17:53:34 -0700564
Doug Zongkereef39442009-04-02 12:14:19 -0700565def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700566 source_version = GetRecoveryAPIVersion(source_zip)
Doug Zongker14833602010-02-02 13:12:04 -0800567 target_version = GetRecoveryAPIVersion(target_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700568
569 if OPTIONS.script_mode == 'amend':
570 script = amend_generator.AmendGenerator()
571 elif OPTIONS.script_mode == 'edify':
572 if source_version == 0:
573 print ("WARNING: generating edify script for a source that "
574 "can't install it.")
575 script = edify_generator.EdifyGenerator(source_version)
576 elif OPTIONS.script_mode == 'auto':
577 if source_version > 0:
578 script = edify_generator.EdifyGenerator(source_version)
579 else:
580 script = amend_generator.AmendGenerator()
581 else:
582 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700583
Doug Zongker05d3dea2009-06-22 11:32:31 -0700584 device_specific = common.DeviceSpecificParams(
585 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800586 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700587 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800588 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700589 output_zip=output_zip,
590 script=script)
591
Doug Zongkereef39442009-04-02 12:14:19 -0700592 print "Loading target..."
593 target_data = LoadSystemFiles(target_zip)
594 print "Loading source..."
595 source_data = LoadSystemFiles(source_zip)
596
597 verbatim_targets = []
598 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700599 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700600 largest_source_size = 0
601 for fn in sorted(target_data.keys()):
602 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700603 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700604 sf = source_data.get(fn, None)
605
606 if sf is None or fn in OPTIONS.require_verbatim:
607 # This file should be included verbatim
608 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700609 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700610 print "send", fn, "verbatim"
611 tf.AddToZip(output_zip)
612 verbatim_targets.append((fn, tf.size))
613 elif tf.sha1 != sf.sha1:
614 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700615 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700616 else:
617 # Target file identical to source.
618 pass
619
Doug Zongker761e6422009-09-25 10:45:39 -0700620 ComputeDifferences(diffs)
621
622 for diff in diffs:
623 tf, sf, d = diff.GetPatch()
624 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
625 # patch is almost as big as the file; don't bother patching
626 tf.AddToZip(output_zip)
627 verbatim_targets.append((tf.name, tf.size))
628 else:
629 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800630 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700631 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700632
633 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
634 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
635
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700636 script.Mount("MTD", "system", "/system")
637 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700638
Doug Zongker5da317e2009-06-02 13:38:17 -0700639 source_boot = File("/tmp/boot.img",
640 common.BuildBootableImage(
641 os.path.join(OPTIONS.source_tmp, "BOOT")))
642 target_boot = File("/tmp/boot.img",
643 common.BuildBootableImage(
644 os.path.join(OPTIONS.target_tmp, "BOOT")))
645 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700647 source_recovery = File("system/recovery.img",
648 common.BuildBootableImage(
649 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
650 target_recovery = File("system/recovery.img",
651 common.BuildBootableImage(
652 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
653 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700654
Doug Zongker881dd402009-09-20 14:03:55 -0700655 # Here's how we divide up the progress bar:
656 # 0.1 for verifying the start state (PatchCheck calls)
657 # 0.8 for applying patches (ApplyPatch calls)
658 # 0.1 for unpacking verbatim files, symlinking, and doing the
659 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700660
661 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700662 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700663
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700664 script.Print("Verifying current system...")
665
Doug Zongker881dd402009-09-20 14:03:55 -0700666 script.ShowProgress(0.1, 0)
667 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
668 if updating_boot:
669 total_verify_size += source_boot.size
670 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700671
Doug Zongker5a482092010-02-17 16:09:18 -0800672 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700673 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700674 so_far += sf.size
675 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700676
Doug Zongker5da317e2009-06-02 13:38:17 -0700677 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700678 d = Difference(target_boot, source_boot)
679 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700680 print "boot target: %d source: %d diff: %d" % (
681 target_boot.size, source_boot.size, len(d))
682
Doug Zongker048e7ca2009-06-15 14:31:53 -0700683 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700684
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700685 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
686 (source_boot.size, source_boot.sha1,
687 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700688 so_far += source_boot.size
689 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700690
691 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700692 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800693
Doug Zongker05d3dea2009-06-22 11:32:31 -0700694 device_specific.IncrementalOTA_VerifyEnd()
695
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700696 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700697
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700698 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700699 script.Print("Erasing user data...")
700 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700701
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700702 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700703 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
704 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700705 if i not in target_data] +
706 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700707
Doug Zongker881dd402009-09-20 14:03:55 -0700708 script.ShowProgress(0.8, 0)
709 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
710 if updating_boot:
711 total_patch_size += target_boot.size
712 so_far = 0
713
714 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800715 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800716 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700717 so_far += tf.size
718 script.SetProgress(so_far / total_patch_size)
719
Doug Zongkereef39442009-04-02 12:14:19 -0700720 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700721 # Produce the boot image by applying a patch to the current
722 # contents of the boot partition, and write it back to the
723 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700724 script.Print("Patching boot image...")
725 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
726 % (source_boot.size, source_boot.sha1,
727 target_boot.size, target_boot.sha1),
728 "-",
729 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800730 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700731 so_far += target_boot.size
732 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700733 print "boot image changed; including."
734 else:
735 print "boot image unchanged; skipping."
736
737 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700738 # Is it better to generate recovery as a patch from the current
739 # boot image, or from the previous recovery image? For large
740 # updates with significant kernel changes, probably the former.
741 # For small updates where the kernel hasn't changed, almost
742 # certainly the latter. We pick the first option. Future
743 # complicated schemes may let us effectively use both.
744 #
745 # A wacky possibility: as long as there is room in the boot
746 # partition, include the binaries and image files from recovery in
747 # the boot image (though not in the ramdisk) so they can be used
748 # as fodder for constructing the recovery image.
749 recovery_sh_item = MakeRecoveryPatch(output_zip,
750 target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800751 script.DeleteFiles(["/system/recovery-from-boot.p",
752 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700753 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700754 else:
755 print "recovery image unchanged; skipping."
756
Doug Zongker881dd402009-09-20 14:03:55 -0700757 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700758
759 target_symlinks = CopySystemFiles(target_zip, None)
760
761 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700762 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700763 Item.GetMetadata()
764 if updating_recovery:
765 recovery_sh_item.uid = 0
766 recovery_sh_item.gid = 0
767 recovery_sh_item.mode = 0544
768 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700769
770 # Note that this call will mess up the tree of Items, so make sure
771 # we're done with it.
772 source_symlinks = CopySystemFiles(source_zip, None)
773 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
774
775 # Delete all the symlinks in source that aren't in target. This
776 # needs to happen before verbatim files are unpacked, in case a
777 # symlink in the source is replaced by a real file in the target.
778 to_delete = []
779 for dest, link in source_symlinks:
780 if link not in target_symlinks_d:
781 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700782 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700783
784 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700785 script.Print("Unpacking new files...")
786 script.UnpackPackageDir("system", "/system")
787
Doug Zongker42265392010-02-12 10:21:00 -0800788 if updating_recovery:
789 script.Print("Unpacking new recovery...")
790 script.UnpackPackageDir("recovery", "/system")
791
Doug Zongker05d3dea2009-06-22 11:32:31 -0700792 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700793
794 # Create all the symlinks that don't already exist, or point to
795 # somewhere different than what we want. Delete each symlink before
796 # creating it, since the 'symlink' command won't overwrite.
797 to_create = []
798 for dest, link in target_symlinks:
799 if link in source_symlinks_d:
800 if dest != source_symlinks_d[link]:
801 to_create.append((dest, link))
802 else:
803 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700804 script.DeleteFiles([i[1] for i in to_create])
805 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700806
807 # Now that the symlinks are created, we can set all the
808 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700809 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700810
Doug Zongker881dd402009-09-20 14:03:55 -0700811 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700812 device_specific.IncrementalOTA_InstallEnd()
813
Doug Zongker1c390a22009-05-14 19:06:36 -0700814 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700815 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700816
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700817 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700818
819
820def main(argv):
821
822 def option_handler(o, a):
823 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700824 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700825 elif o in ("-k", "--package_key"):
826 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700827 elif o in ("-i", "--incremental_from"):
828 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700829 elif o in ("-w", "--wipe_user_data"):
830 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700831 elif o in ("-n", "--no_prereq"):
832 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700833 elif o in ("-e", "--extra_script"):
834 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700835 elif o in ("-m", "--script_mode"):
836 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700837 elif o in ("--worker_threads"):
838 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700839 else:
840 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700841 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700842
843 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700844 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700845 extra_long_opts=["board_config=",
846 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700847 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700848 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700849 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700850 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700851 "script_mode=",
852 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700853 extra_option_handler=option_handler)
854
855 if len(args) != 2:
856 common.Usage(__doc__)
857 sys.exit(1)
858
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700859 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
860 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
861
Doug Zongker1c390a22009-05-14 19:06:36 -0700862 if OPTIONS.extra_script is not None:
863 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
864
Doug Zongkereef39442009-04-02 12:14:19 -0700865 print "unzipping target target-files..."
866 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700867
Doug Zongkerc18736b2009-09-30 09:20:32 -0700868 if OPTIONS.device_specific is None:
869 # look for the device-specific tools extension location in the input
870 try:
871 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
872 ds = f.read().strip()
873 f.close()
874 if ds:
875 ds = os.path.normpath(ds)
876 print "using device-specific extensions in", ds
877 OPTIONS.device_specific = ds
878 except IOError, e:
879 if e.errno == errno.ENOENT:
880 # nothing specified in the file
881 pass
882 else:
883 raise
884
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700885 common.LoadMaxSizes()
886 if not OPTIONS.max_image_size:
887 print
888 print " WARNING: Failed to load max image sizes; will not enforce"
889 print " image size limits."
890 print
891
Doug Zongkereef39442009-04-02 12:14:19 -0700892 OPTIONS.target_tmp = OPTIONS.input_tmp
893 input_zip = zipfile.ZipFile(args[0], "r")
894 if OPTIONS.package_key:
895 temp_zip_file = tempfile.NamedTemporaryFile()
896 output_zip = zipfile.ZipFile(temp_zip_file, "w",
897 compression=zipfile.ZIP_DEFLATED)
898 else:
899 output_zip = zipfile.ZipFile(args[1], "w",
900 compression=zipfile.ZIP_DEFLATED)
901
902 if OPTIONS.incremental_source is None:
903 WriteFullOTAPackage(input_zip, output_zip)
904 else:
905 print "unzipping source target-files..."
906 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
907 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
908 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
909
910 output_zip.close()
911 if OPTIONS.package_key:
912 SignOutput(temp_zip_file.name, args[1])
913 temp_zip_file.close()
914
915 common.Cleanup()
916
917 print "done."
918
919
920if __name__ == '__main__':
921 try:
922 main(sys.argv[1:])
923 except common.ExternalError, e:
924 print
925 print " ERROR: %s" % (e,)
926 print
927 sys.exit(1)