blob: 0454d6ffc990d6480a551b855841e54cb75a6eed [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
Doug Zongker283e2a12010-03-15 17:52:32 -0700140 def GetMetadata(cls, input_zip):
141
142 try:
143 # See if the target_files contains a record of what the uid,
144 # gid, and mode is supposed to be.
145 output = input_zip.read("META/filesystem_config.txt")
146 except KeyError:
147 # Run the external 'fs_config' program to determine the desired
148 # uid, gid, and mode for every Item object. Note this uses the
149 # one in the client now, which might not be the same as the one
150 # used when this target_files was built.
151 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
152 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 suffix = { False: "", True: "/" }
154 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
155 for i in cls.ITEMS.itervalues() if i.name])
156 output2, error = p.communicate(input)
157 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700158
159 for line in output.split("\n"):
160 if not line: continue
161 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700162 i = cls.ITEMS.get(name, None)
163 if i is not None:
164 i.uid = int(uid)
165 i.gid = int(gid)
166 i.mode = int(mode, 8)
167 if i.dir:
168 i.children.sort(key=lambda i: i.name)
169
170 # set metadata for the files generated by this script.
171 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
172 if i: i.uid, i.gid, i.mode = 0, 0, 0644
173 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
174 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700175
176 def CountChildMetadata(self):
177 """Count up the (uid, gid, mode) tuples for all children and
178 determine the best strategy for using set_perm_recursive and
179 set_perm to correctly chown/chmod all the files to their desired
180 values. Recursively calls itself for all descendants.
181
182 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
183 all descendants of this node. (dmode or fmode may be None.) Also
184 sets the best_subtree of each directory Item to the (uid, gid,
185 dmode, fmode) tuple that will match the most descendants of that
186 Item.
187 """
188
189 assert self.dir
190 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
191 for i in self.children:
192 if i.dir:
193 for k, v in i.CountChildMetadata().iteritems():
194 d[k] = d.get(k, 0) + v
195 else:
196 k = (i.uid, i.gid, None, i.mode)
197 d[k] = d.get(k, 0) + 1
198
199 # Find the (uid, gid, dmode, fmode) tuple that matches the most
200 # descendants.
201
202 # First, find the (uid, gid) pair that matches the most
203 # descendants.
204 ug = {}
205 for (uid, gid, _, _), count in d.iteritems():
206 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
207 ug = MostPopularKey(ug, (0, 0))
208
209 # Now find the dmode and fmode that match the most descendants
210 # with that (uid, gid), and choose those.
211 best_dmode = (0, 0755)
212 best_fmode = (0, 0644)
213 for k, count in d.iteritems():
214 if k[:2] != ug: continue
215 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
216 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
217 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
218
219 return d
220
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700221 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700222 """Append set_perm/set_perm_recursive commands to 'script' to
223 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700224 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700225
226 self.CountChildMetadata()
227
228 def recurse(item, current):
229 # current is the (uid, gid, dmode, fmode) tuple that the current
230 # item (and all its children) have already been set to. We only
231 # need to issue set_perm/set_perm_recursive commands if we're
232 # supposed to be something different.
233 if item.dir:
234 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700235 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700236 current = item.best_subtree
237
238 if item.uid != current[0] or item.gid != current[1] or \
239 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700240 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700241
242 for i in item.children:
243 recurse(i, current)
244 else:
245 if item.uid != current[0] or item.gid != current[1] or \
246 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700247 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700248
249 recurse(self, (-1, -1, -1, -1))
250
251
252def CopySystemFiles(input_zip, output_zip=None,
253 substitute=None):
254 """Copies files underneath system/ in the input zip to the output
255 zip. Populates the Item class with their metadata, and returns a
256 list of symlinks. output_zip may be None, in which case the copy is
257 skipped (but the other side effects still happen). substitute is an
258 optional dict of {output filename: contents} to be output instead of
259 certain input files.
260 """
261
262 symlinks = []
263
264 for info in input_zip.infolist():
265 if info.filename.startswith("SYSTEM/"):
266 basefilename = info.filename[7:]
267 if IsSymlink(info):
268 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700269 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700270 else:
271 info2 = copy.copy(info)
272 fn = info2.filename = "system/" + basefilename
273 if substitute and fn in substitute and substitute[fn] is None:
274 continue
275 if output_zip is not None:
276 if substitute and fn in substitute:
277 data = substitute[fn]
278 else:
279 data = input_zip.read(info.filename)
280 output_zip.writestr(info2, data)
281 if fn.endswith("/"):
282 Item.Get(fn[:-1], dir=True)
283 else:
284 Item.Get(fn, dir=False)
285
286 symlinks.sort()
287 return symlinks
288
289
Doug Zongkereef39442009-04-02 12:14:19 -0700290def SignOutput(temp_zip_name, output_zip_name):
291 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
292 pw = key_passwords[OPTIONS.package_key]
293
Doug Zongker951495f2009-08-14 12:44:19 -0700294 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
295 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700296
297
Doug Zongkereef39442009-04-02 12:14:19 -0700298def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700299 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700300 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700301
Doug Zongkereef39442009-04-02 12:14:19 -0700302
Doug Zongker73ef8252009-07-23 15:12:53 -0700303def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
304 """Generate a binary patch that creates the recovery image starting
305 with the boot image. (Most of the space in these images is just the
306 kernel, which is identical for the two, so the resulting patch
307 should be efficient.) Add it to the output zip, along with a shell
308 script that is run from init.rc on first boot to actually do the
309 patching and install the new recovery image.
310
311 recovery_img and boot_img should be File objects for the
312 corresponding images.
313
314 Returns an Item for the shell script, which must be made
315 executable.
316 """
317
Doug Zongker761e6422009-09-25 10:45:39 -0700318 d = Difference(recovery_img, boot_img)
319 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700320 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700321 Item.Get("system/recovery-from-boot.p", dir=False)
322
323 # Images with different content will have a different first page, so
324 # we check to see if this recovery has already been installed by
325 # testing just the first 2k.
326 HEADER_SIZE = 2048
327 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
328 sh = """#!/system/bin/sh
329if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
330 log -t recovery "Installing new recovery image"
331 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
332else
333 log -t recovery "Recovery image already installed"
334fi
335""" % { 'boot_size': boot_img.size,
336 'boot_sha1': boot_img.sha1,
337 'header_size': HEADER_SIZE,
338 'header_sha1': header_sha1,
339 'recovery_size': recovery_img.size,
340 'recovery_sha1': recovery_img.sha1 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700341 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700342 return Item.Get("system/etc/install-recovery.sh", dir=False)
343
344
Doug Zongkereef39442009-04-02 12:14:19 -0700345def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700346 if OPTIONS.script_mode == "auto":
347 script = both_generator.BothGenerator(2)
348 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700349 script = amend_generator.AmendGenerator()
350 else:
351 # TODO: how to determine this? We don't know what version it will
352 # be installed on top of. For now, we expect the API just won't
353 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700354 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700355
Doug Zongker05d3dea2009-06-22 11:32:31 -0700356 device_specific = common.DeviceSpecificParams(
357 input_zip=input_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800358 input_version=GetRecoveryAPIVersion(input_zip),
Doug Zongker05d3dea2009-06-22 11:32:31 -0700359 output_zip=output_zip,
360 script=script,
361 input_tmp=OPTIONS.input_tmp)
362
Doug Zongker962069c2009-04-23 11:41:58 -0700363 if not OPTIONS.omit_prereq:
364 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700365 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700366
367 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700368 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700369
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700370 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700372 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700373 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700374
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700375 script.FormatPartition("system")
376 script.Mount("MTD", "system", "/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700377 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700378 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700379
380 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700381 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700382
Doug Zongker73ef8252009-07-23 15:12:53 -0700383 boot_img = File("boot.img", common.BuildBootableImage(
384 os.path.join(OPTIONS.input_tmp, "BOOT")))
385 recovery_img = File("recovery.img", common.BuildBootableImage(
386 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700387 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Doug Zongker283e2a12010-03-15 17:52:32 -0700389 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700390 Item.Get("system").SetPermissions(script)
391
392 common.CheckSize(boot_img.data, "boot.img")
393 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700394 script.ShowProgress(0.2, 0)
395
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700396 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700397 script.WriteRawImage("boot", "boot.img")
398
399 script.ShowProgress(0.1, 0)
400 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700401
Doug Zongker1c390a22009-05-14 19:06:36 -0700402 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700403 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700404
Doug Zongker14833602010-02-02 13:12:04 -0800405 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700406 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700407
408
409class File(object):
410 def __init__(self, name, data):
411 self.name = name
412 self.data = data
413 self.size = len(data)
414 self.sha1 = sha.sha(data).hexdigest()
415
416 def WriteToTemp(self):
417 t = tempfile.NamedTemporaryFile()
418 t.write(self.data)
419 t.flush()
420 return t
421
422 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700423 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700424
425
426def LoadSystemFiles(z):
427 """Load all the files from SYSTEM/... in a given target-files
428 ZipFile, and return a dict of {filename: File object}."""
429 out = {}
430 for info in z.infolist():
431 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
432 fn = "system/" + info.filename[7:]
433 data = z.read(info.filename)
434 out[fn] = File(fn, data)
435 return out
436
437
Doug Zongker761e6422009-09-25 10:45:39 -0700438DIFF_PROGRAM_BY_EXT = {
439 ".gz" : "imgdiff",
440 ".zip" : ["imgdiff", "-z"],
441 ".jar" : ["imgdiff", "-z"],
442 ".apk" : ["imgdiff", "-z"],
443 ".img" : "imgdiff",
444 }
Doug Zongkereef39442009-04-02 12:14:19 -0700445
Doug Zongkereef39442009-04-02 12:14:19 -0700446
Doug Zongker761e6422009-09-25 10:45:39 -0700447class Difference(object):
448 def __init__(self, tf, sf):
449 self.tf = tf
450 self.sf = sf
451 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700452
Doug Zongker761e6422009-09-25 10:45:39 -0700453 def ComputePatch(self):
454 """Compute the patch (as a string of data) needed to turn sf into
455 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700456
Doug Zongker761e6422009-09-25 10:45:39 -0700457 tf = self.tf
458 sf = self.sf
459
460 ext = os.path.splitext(tf.name)[1]
461 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
462
463 ttemp = tf.WriteToTemp()
464 stemp = sf.WriteToTemp()
465
466 ext = os.path.splitext(tf.name)[1]
467
468 try:
469 ptemp = tempfile.NamedTemporaryFile()
470 if isinstance(diff_program, list):
471 cmd = copy.copy(diff_program)
472 else:
473 cmd = [diff_program]
474 cmd.append(stemp.name)
475 cmd.append(ttemp.name)
476 cmd.append(ptemp.name)
477 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
478 _, err = p.communicate()
479 if err or p.returncode != 0:
480 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
481 return None
482 diff = ptemp.read()
483 finally:
484 ptemp.close()
485 stemp.close()
486 ttemp.close()
487
488 self.patch = diff
489 return self.tf, self.sf, self.patch
490
491
492 def GetPatch(self):
493 """Return a tuple (target_file, source_file, patch_data).
494 patch_data may be None if ComputePatch hasn't been called, or if
495 computing the patch failed."""
496 return self.tf, self.sf, self.patch
497
498
499def ComputeDifferences(diffs):
500 """Call ComputePatch on all the Difference objects in 'diffs'."""
501 print len(diffs), "diffs to compute"
502
503 # Do the largest files first, to try and reduce the long-pole effect.
504 by_size = [(i.tf.size, i) for i in diffs]
505 by_size.sort(reverse=True)
506 by_size = [i[1] for i in by_size]
507
508 lock = threading.Lock()
509 diff_iter = iter(by_size) # accessed under lock
510
511 def worker():
512 try:
513 lock.acquire()
514 for d in diff_iter:
515 lock.release()
516 start = time.time()
517 d.ComputePatch()
518 dur = time.time() - start
519 lock.acquire()
520
521 tf, sf, patch = d.GetPatch()
522 if sf.name == tf.name:
523 name = tf.name
524 else:
525 name = "%s (%s)" % (tf.name, sf.name)
526 if patch is None:
527 print "patching failed! %s" % (name,)
528 else:
529 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
530 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
531 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700532 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700533 print e
534 raise
535
536 # start worker threads; wait for them all to finish.
537 threads = [threading.Thread(target=worker)
538 for i in range(OPTIONS.worker_threads)]
539 for th in threads:
540 th.start()
541 while threads:
542 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700543
544
545def GetBuildProp(property, z):
546 """Return the fingerprint of the build of a given target-files
547 ZipFile object."""
548 bp = z.read("SYSTEM/build.prop")
549 if not property:
550 return bp
551 m = re.search(re.escape(property) + r"=(.*)\n", bp)
552 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700553 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700554 return m.group(1).strip()
555
556
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700557def GetRecoveryAPIVersion(zip):
558 """Returns the version of the recovery API. Version 0 is the older
559 amend code (no separate binary)."""
560 try:
561 version = zip.read("META/recovery-api-version.txt")
562 return int(version)
563 except KeyError:
564 try:
565 # version one didn't have the recovery-api-version.txt file, but
566 # it did include an updater binary.
567 zip.getinfo("OTA/bin/updater")
568 return 1
569 except KeyError:
570 return 0
571
Doug Zongker15604b82009-09-01 17:53:34 -0700572
Doug Zongkereef39442009-04-02 12:14:19 -0700573def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700574 source_version = GetRecoveryAPIVersion(source_zip)
Doug Zongker14833602010-02-02 13:12:04 -0800575 target_version = GetRecoveryAPIVersion(target_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700576
577 if OPTIONS.script_mode == 'amend':
578 script = amend_generator.AmendGenerator()
579 elif OPTIONS.script_mode == 'edify':
580 if source_version == 0:
581 print ("WARNING: generating edify script for a source that "
582 "can't install it.")
583 script = edify_generator.EdifyGenerator(source_version)
584 elif OPTIONS.script_mode == 'auto':
585 if source_version > 0:
586 script = edify_generator.EdifyGenerator(source_version)
587 else:
588 script = amend_generator.AmendGenerator()
589 else:
590 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700591
Doug Zongker05d3dea2009-06-22 11:32:31 -0700592 device_specific = common.DeviceSpecificParams(
593 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800594 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700595 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800596 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700597 output_zip=output_zip,
598 script=script)
599
Doug Zongkereef39442009-04-02 12:14:19 -0700600 print "Loading target..."
601 target_data = LoadSystemFiles(target_zip)
602 print "Loading source..."
603 source_data = LoadSystemFiles(source_zip)
604
605 verbatim_targets = []
606 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700607 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700608 largest_source_size = 0
609 for fn in sorted(target_data.keys()):
610 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700611 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700612 sf = source_data.get(fn, None)
613
614 if sf is None or fn in OPTIONS.require_verbatim:
615 # This file should be included verbatim
616 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700617 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700618 print "send", fn, "verbatim"
619 tf.AddToZip(output_zip)
620 verbatim_targets.append((fn, tf.size))
621 elif tf.sha1 != sf.sha1:
622 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700623 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700624 else:
625 # Target file identical to source.
626 pass
627
Doug Zongker761e6422009-09-25 10:45:39 -0700628 ComputeDifferences(diffs)
629
630 for diff in diffs:
631 tf, sf, d = diff.GetPatch()
632 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
633 # patch is almost as big as the file; don't bother patching
634 tf.AddToZip(output_zip)
635 verbatim_targets.append((tf.name, tf.size))
636 else:
637 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800638 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700639 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700640
641 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
642 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
643
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700644 script.Mount("MTD", "system", "/system")
645 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongker5da317e2009-06-02 13:38:17 -0700647 source_boot = File("/tmp/boot.img",
648 common.BuildBootableImage(
649 os.path.join(OPTIONS.source_tmp, "BOOT")))
650 target_boot = File("/tmp/boot.img",
651 common.BuildBootableImage(
652 os.path.join(OPTIONS.target_tmp, "BOOT")))
653 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700654
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700655 source_recovery = File("system/recovery.img",
656 common.BuildBootableImage(
657 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
658 target_recovery = File("system/recovery.img",
659 common.BuildBootableImage(
660 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
661 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700662
Doug Zongker881dd402009-09-20 14:03:55 -0700663 # Here's how we divide up the progress bar:
664 # 0.1 for verifying the start state (PatchCheck calls)
665 # 0.8 for applying patches (ApplyPatch calls)
666 # 0.1 for unpacking verbatim files, symlinking, and doing the
667 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700668
669 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700670 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700671
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700672 script.Print("Verifying current system...")
673
Doug Zongker881dd402009-09-20 14:03:55 -0700674 script.ShowProgress(0.1, 0)
675 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
676 if updating_boot:
677 total_verify_size += source_boot.size
678 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700679
Doug Zongker5a482092010-02-17 16:09:18 -0800680 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700681 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700682 so_far += sf.size
683 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700684
Doug Zongker5da317e2009-06-02 13:38:17 -0700685 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700686 d = Difference(target_boot, source_boot)
687 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700688 print "boot target: %d source: %d diff: %d" % (
689 target_boot.size, source_boot.size, len(d))
690
Doug Zongker048e7ca2009-06-15 14:31:53 -0700691 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700692
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700693 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
694 (source_boot.size, source_boot.sha1,
695 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700696 so_far += source_boot.size
697 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700698
699 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700700 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800701
Doug Zongker05d3dea2009-06-22 11:32:31 -0700702 device_specific.IncrementalOTA_VerifyEnd()
703
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700704 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700705
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700706 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700707 script.Print("Erasing user data...")
708 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700709
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700710 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700711 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
712 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700713 if i not in target_data] +
714 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700715
Doug Zongker881dd402009-09-20 14:03:55 -0700716 script.ShowProgress(0.8, 0)
717 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
718 if updating_boot:
719 total_patch_size += target_boot.size
720 so_far = 0
721
722 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800723 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800724 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700725 so_far += tf.size
726 script.SetProgress(so_far / total_patch_size)
727
Doug Zongkereef39442009-04-02 12:14:19 -0700728 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700729 # Produce the boot image by applying a patch to the current
730 # contents of the boot partition, and write it back to the
731 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700732 script.Print("Patching boot image...")
733 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
734 % (source_boot.size, source_boot.sha1,
735 target_boot.size, target_boot.sha1),
736 "-",
737 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800738 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700739 so_far += target_boot.size
740 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700741 print "boot image changed; including."
742 else:
743 print "boot image unchanged; skipping."
744
745 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700746 # Is it better to generate recovery as a patch from the current
747 # boot image, or from the previous recovery image? For large
748 # updates with significant kernel changes, probably the former.
749 # For small updates where the kernel hasn't changed, almost
750 # certainly the latter. We pick the first option. Future
751 # complicated schemes may let us effectively use both.
752 #
753 # A wacky possibility: as long as there is room in the boot
754 # partition, include the binaries and image files from recovery in
755 # the boot image (though not in the ramdisk) so they can be used
756 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700757 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800758 script.DeleteFiles(["/system/recovery-from-boot.p",
759 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700760 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700761 else:
762 print "recovery image unchanged; skipping."
763
Doug Zongker881dd402009-09-20 14:03:55 -0700764 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700765
766 target_symlinks = CopySystemFiles(target_zip, None)
767
768 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700769 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700770 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700771 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700772
773 # Note that this call will mess up the tree of Items, so make sure
774 # we're done with it.
775 source_symlinks = CopySystemFiles(source_zip, None)
776 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
777
778 # Delete all the symlinks in source that aren't in target. This
779 # needs to happen before verbatim files are unpacked, in case a
780 # symlink in the source is replaced by a real file in the target.
781 to_delete = []
782 for dest, link in source_symlinks:
783 if link not in target_symlinks_d:
784 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700785 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700786
787 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700788 script.Print("Unpacking new files...")
789 script.UnpackPackageDir("system", "/system")
790
Doug Zongker42265392010-02-12 10:21:00 -0800791 if updating_recovery:
792 script.Print("Unpacking new recovery...")
793 script.UnpackPackageDir("recovery", "/system")
794
Doug Zongker05d3dea2009-06-22 11:32:31 -0700795 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700796
797 # Create all the symlinks that don't already exist, or point to
798 # somewhere different than what we want. Delete each symlink before
799 # creating it, since the 'symlink' command won't overwrite.
800 to_create = []
801 for dest, link in target_symlinks:
802 if link in source_symlinks_d:
803 if dest != source_symlinks_d[link]:
804 to_create.append((dest, link))
805 else:
806 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700807 script.DeleteFiles([i[1] for i in to_create])
808 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700809
810 # Now that the symlinks are created, we can set all the
811 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700812 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700813
Doug Zongker881dd402009-09-20 14:03:55 -0700814 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700815 device_specific.IncrementalOTA_InstallEnd()
816
Doug Zongker1c390a22009-05-14 19:06:36 -0700817 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700818 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700819
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700820 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700821
822
823def main(argv):
824
825 def option_handler(o, a):
826 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700827 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700828 elif o in ("-k", "--package_key"):
829 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700830 elif o in ("-i", "--incremental_from"):
831 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700832 elif o in ("-w", "--wipe_user_data"):
833 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700834 elif o in ("-n", "--no_prereq"):
835 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700836 elif o in ("-e", "--extra_script"):
837 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700838 elif o in ("-m", "--script_mode"):
839 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700840 elif o in ("--worker_threads"):
841 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700842 else:
843 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700844 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700845
846 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700847 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700848 extra_long_opts=["board_config=",
849 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700850 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700851 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700852 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700853 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700854 "script_mode=",
855 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700856 extra_option_handler=option_handler)
857
858 if len(args) != 2:
859 common.Usage(__doc__)
860 sys.exit(1)
861
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700862 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
863 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
864
Doug Zongker1c390a22009-05-14 19:06:36 -0700865 if OPTIONS.extra_script is not None:
866 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
867
Doug Zongkereef39442009-04-02 12:14:19 -0700868 print "unzipping target target-files..."
869 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700870
Doug Zongkerc18736b2009-09-30 09:20:32 -0700871 if OPTIONS.device_specific is None:
872 # look for the device-specific tools extension location in the input
873 try:
874 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
875 ds = f.read().strip()
876 f.close()
877 if ds:
878 ds = os.path.normpath(ds)
879 print "using device-specific extensions in", ds
880 OPTIONS.device_specific = ds
881 except IOError, e:
882 if e.errno == errno.ENOENT:
883 # nothing specified in the file
884 pass
885 else:
886 raise
887
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700888 common.LoadMaxSizes()
889 if not OPTIONS.max_image_size:
890 print
891 print " WARNING: Failed to load max image sizes; will not enforce"
892 print " image size limits."
893 print
894
Doug Zongkereef39442009-04-02 12:14:19 -0700895 OPTIONS.target_tmp = OPTIONS.input_tmp
896 input_zip = zipfile.ZipFile(args[0], "r")
897 if OPTIONS.package_key:
898 temp_zip_file = tempfile.NamedTemporaryFile()
899 output_zip = zipfile.ZipFile(temp_zip_file, "w",
900 compression=zipfile.ZIP_DEFLATED)
901 else:
902 output_zip = zipfile.ZipFile(args[1], "w",
903 compression=zipfile.ZIP_DEFLATED)
904
905 if OPTIONS.incremental_source is None:
906 WriteFullOTAPackage(input_zip, output_zip)
907 else:
908 print "unzipping source target-files..."
909 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
910 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
911 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
912
913 output_zip.close()
914 if OPTIONS.package_key:
915 SignOutput(temp_zip_file.name, args[1])
916 temp_zip_file.close()
917
918 common.Cleanup()
919
920 print "done."
921
922
923if __name__ == '__main__':
924 try:
925 main(sys.argv[1:])
926 except common.ExternalError, e:
927 print
928 print " ERROR: %s" % (e,)
929 print
930 sys.exit(1)