blob: 29911bbdab32641c01ac3d62c2e9684b86c4dddc [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 Zongkereef39442009-04-02 12:14:19 -070047"""
48
49import sys
50
51if sys.hexversion < 0x02040000:
52 print >> sys.stderr, "Python 2.4 or newer is required."
53 sys.exit(1)
54
55import copy
Doug Zongkerc18736b2009-09-30 09:20:32 -070056import errno
Doug Zongkereef39442009-04-02 12:14:19 -070057import os
58import re
59import sha
60import subprocess
61import tempfile
Doug Zongker761e6422009-09-25 10:45:39 -070062import threading
Doug Zongkereef39442009-04-02 12:14:19 -070063import time
64import zipfile
65
66import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070067import edify_generator
Doug Zongkereef39442009-04-02 12:14:19 -070068
69OPTIONS = common.OPTIONS
70OPTIONS.package_key = "build/target/product/security/testkey"
71OPTIONS.incremental_source = None
72OPTIONS.require_verbatim = set()
73OPTIONS.prohibit_verbatim = set(("system/build.prop",))
74OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070075OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070076OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070077OPTIONS.extra_script = None
Doug Zongker761e6422009-09-25 10:45:39 -070078OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070079
80def MostPopularKey(d, default):
81 """Given a dict, return the key corresponding to the largest
82 value. Returns 'default' if the dict is empty."""
83 x = [(v, k) for (k, v) in d.iteritems()]
84 if not x: return default
85 x.sort()
86 return x[-1][1]
87
88
89def IsSymlink(info):
90 """Return true if the zipfile.ZipInfo object passed in represents a
91 symlink."""
92 return (info.external_attr >> 16) == 0120777
93
94
95
96class Item:
97 """Items represent the metadata (user, group, mode) of files and
98 directories in the system image."""
99 ITEMS = {}
100 def __init__(self, name, dir=False):
101 self.name = name
102 self.uid = None
103 self.gid = None
104 self.mode = None
105 self.dir = dir
106
107 if name:
108 self.parent = Item.Get(os.path.dirname(name), dir=True)
109 self.parent.children.append(self)
110 else:
111 self.parent = None
112 if dir:
113 self.children = []
114
115 def Dump(self, indent=0):
116 if self.uid is not None:
117 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
118 else:
119 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
120 if self.dir:
121 print "%s%s" % (" "*indent, self.descendants)
122 print "%s%s" % (" "*indent, self.best_subtree)
123 for i in self.children:
124 i.Dump(indent=indent+1)
125
126 @classmethod
127 def Get(cls, name, dir=False):
128 if name not in cls.ITEMS:
129 cls.ITEMS[name] = Item(name, dir=dir)
130 return cls.ITEMS[name]
131
132 @classmethod
Doug Zongker283e2a12010-03-15 17:52:32 -0700133 def GetMetadata(cls, input_zip):
134
135 try:
136 # See if the target_files contains a record of what the uid,
137 # gid, and mode is supposed to be.
138 output = input_zip.read("META/filesystem_config.txt")
139 except KeyError:
140 # Run the external 'fs_config' program to determine the desired
141 # uid, gid, and mode for every Item object. Note this uses the
142 # one in the client now, which might not be the same as the one
143 # used when this target_files was built.
144 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
145 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
146 suffix = { False: "", True: "/" }
147 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
148 for i in cls.ITEMS.itervalues() if i.name])
Doug Zongker3475d362010-03-17 16:39:30 -0700149 output, error = p.communicate(input)
Doug Zongker283e2a12010-03-15 17:52:32 -0700150 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700151
152 for line in output.split("\n"):
153 if not line: continue
154 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700155 i = cls.ITEMS.get(name, None)
156 if i is not None:
157 i.uid = int(uid)
158 i.gid = int(gid)
159 i.mode = int(mode, 8)
160 if i.dir:
161 i.children.sort(key=lambda i: i.name)
162
163 # set metadata for the files generated by this script.
164 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
165 if i: i.uid, i.gid, i.mode = 0, 0, 0644
166 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
167 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700168
169 def CountChildMetadata(self):
170 """Count up the (uid, gid, mode) tuples for all children and
171 determine the best strategy for using set_perm_recursive and
172 set_perm to correctly chown/chmod all the files to their desired
173 values. Recursively calls itself for all descendants.
174
175 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
176 all descendants of this node. (dmode or fmode may be None.) Also
177 sets the best_subtree of each directory Item to the (uid, gid,
178 dmode, fmode) tuple that will match the most descendants of that
179 Item.
180 """
181
182 assert self.dir
183 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
184 for i in self.children:
185 if i.dir:
186 for k, v in i.CountChildMetadata().iteritems():
187 d[k] = d.get(k, 0) + v
188 else:
189 k = (i.uid, i.gid, None, i.mode)
190 d[k] = d.get(k, 0) + 1
191
192 # Find the (uid, gid, dmode, fmode) tuple that matches the most
193 # descendants.
194
195 # First, find the (uid, gid) pair that matches the most
196 # descendants.
197 ug = {}
198 for (uid, gid, _, _), count in d.iteritems():
199 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
200 ug = MostPopularKey(ug, (0, 0))
201
202 # Now find the dmode and fmode that match the most descendants
203 # with that (uid, gid), and choose those.
204 best_dmode = (0, 0755)
205 best_fmode = (0, 0644)
206 for k, count in d.iteritems():
207 if k[:2] != ug: continue
208 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
209 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
210 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
211
212 return d
213
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700214 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700215 """Append set_perm/set_perm_recursive commands to 'script' to
216 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700217 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700218
219 self.CountChildMetadata()
220
221 def recurse(item, current):
222 # current is the (uid, gid, dmode, fmode) tuple that the current
223 # item (and all its children) have already been set to. We only
224 # need to issue set_perm/set_perm_recursive commands if we're
225 # supposed to be something different.
226 if item.dir:
227 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700228 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700229 current = item.best_subtree
230
231 if item.uid != current[0] or item.gid != current[1] or \
232 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700233 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700234
235 for i in item.children:
236 recurse(i, current)
237 else:
238 if item.uid != current[0] or item.gid != current[1] or \
239 item.mode != current[3]:
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 recurse(self, (-1, -1, -1, -1))
243
244
245def CopySystemFiles(input_zip, output_zip=None,
246 substitute=None):
247 """Copies files underneath system/ in the input zip to the output
248 zip. Populates the Item class with their metadata, and returns a
249 list of symlinks. output_zip may be None, in which case the copy is
250 skipped (but the other side effects still happen). substitute is an
251 optional dict of {output filename: contents} to be output instead of
252 certain input files.
253 """
254
255 symlinks = []
256
257 for info in input_zip.infolist():
258 if info.filename.startswith("SYSTEM/"):
259 basefilename = info.filename[7:]
260 if IsSymlink(info):
261 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700262 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700263 else:
264 info2 = copy.copy(info)
265 fn = info2.filename = "system/" + basefilename
266 if substitute and fn in substitute and substitute[fn] is None:
267 continue
268 if output_zip is not None:
269 if substitute and fn in substitute:
270 data = substitute[fn]
271 else:
272 data = input_zip.read(info.filename)
273 output_zip.writestr(info2, data)
274 if fn.endswith("/"):
275 Item.Get(fn[:-1], dir=True)
276 else:
277 Item.Get(fn, dir=False)
278
279 symlinks.sort()
280 return symlinks
281
282
Doug Zongkereef39442009-04-02 12:14:19 -0700283def SignOutput(temp_zip_name, output_zip_name):
284 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
285 pw = key_passwords[OPTIONS.package_key]
286
Doug Zongker951495f2009-08-14 12:44:19 -0700287 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
288 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700289
290
Doug Zongkereef39442009-04-02 12:14:19 -0700291def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700292 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700293 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700294
Doug Zongkereef39442009-04-02 12:14:19 -0700295
Doug Zongker73ef8252009-07-23 15:12:53 -0700296def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
297 """Generate a binary patch that creates the recovery image starting
298 with the boot image. (Most of the space in these images is just the
299 kernel, which is identical for the two, so the resulting patch
300 should be efficient.) Add it to the output zip, along with a shell
301 script that is run from init.rc on first boot to actually do the
302 patching and install the new recovery image.
303
304 recovery_img and boot_img should be File objects for the
305 corresponding images.
306
307 Returns an Item for the shell script, which must be made
308 executable.
309 """
310
Doug Zongker761e6422009-09-25 10:45:39 -0700311 d = Difference(recovery_img, boot_img)
312 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700313 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700314 Item.Get("system/recovery-from-boot.p", dir=False)
315
316 # Images with different content will have a different first page, so
317 # we check to see if this recovery has already been installed by
318 # testing just the first 2k.
319 HEADER_SIZE = 2048
320 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
321 sh = """#!/system/bin/sh
322if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
323 log -t recovery "Installing new recovery image"
324 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
325else
326 log -t recovery "Recovery image already installed"
327fi
328""" % { 'boot_size': boot_img.size,
329 'boot_sha1': boot_img.sha1,
330 'header_size': HEADER_SIZE,
331 'header_sha1': header_sha1,
332 'recovery_size': recovery_img.size,
333 'recovery_sha1': recovery_img.sha1 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700334 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700335 return Item.Get("system/etc/install-recovery.sh", dir=False)
336
337
Doug Zongkereef39442009-04-02 12:14:19 -0700338def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc637db12010-04-21 14:08:44 -0700339 # TODO: how to determine this? We don't know what version it will
340 # be installed on top of. For now, we expect the API just won't
341 # change very often.
342 script = edify_generator.EdifyGenerator(3)
Doug Zongkereef39442009-04-02 12:14:19 -0700343
Doug Zongker2ea21062010-04-28 16:05:21 -0700344 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
345 "pre-device": GetBuildProp("ro.product.device", input_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700346 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700347 }
348
Doug Zongker05d3dea2009-06-22 11:32:31 -0700349 device_specific = common.DeviceSpecificParams(
350 input_zip=input_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800351 input_version=GetRecoveryAPIVersion(input_zip),
Doug Zongker05d3dea2009-06-22 11:32:31 -0700352 output_zip=output_zip,
353 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700354 input_tmp=OPTIONS.input_tmp,
355 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700356
Doug Zongker962069c2009-04-23 11:41:58 -0700357 if not OPTIONS.omit_prereq:
358 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700359 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700360
361 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700362 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700363
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700364 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700365
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700366 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700367 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700368
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700369 script.FormatPartition("system")
370 script.Mount("MTD", "system", "/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700371 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700372 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700373
374 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700375 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700376
Doug Zongker73ef8252009-07-23 15:12:53 -0700377 boot_img = File("boot.img", common.BuildBootableImage(
378 os.path.join(OPTIONS.input_tmp, "BOOT")))
379 recovery_img = File("recovery.img", common.BuildBootableImage(
380 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700381 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700382
Doug Zongker283e2a12010-03-15 17:52:32 -0700383 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700384 Item.Get("system").SetPermissions(script)
385
386 common.CheckSize(boot_img.data, "boot.img")
387 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700388 script.ShowProgress(0.2, 0)
389
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700390 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700391 script.WriteRawImage("boot", "boot.img")
392
393 script.ShowProgress(0.1, 0)
394 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700395
Doug Zongker1c390a22009-05-14 19:06:36 -0700396 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700397 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700398
Doug Zongker14833602010-02-02 13:12:04 -0800399 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700400 script.AddToZip(input_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700401 WriteMetadata(metadata, output_zip)
402
403
404def WriteMetadata(metadata, output_zip):
405 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
406 "".join(["%s=%s\n" % kv
407 for kv in sorted(metadata.iteritems())]))
Doug Zongkereef39442009-04-02 12:14:19 -0700408
409
410class File(object):
411 def __init__(self, name, data):
412 self.name = name
413 self.data = data
414 self.size = len(data)
415 self.sha1 = sha.sha(data).hexdigest()
416
417 def WriteToTemp(self):
418 t = tempfile.NamedTemporaryFile()
419 t.write(self.data)
420 t.flush()
421 return t
422
423 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700424 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700425
426
427def LoadSystemFiles(z):
428 """Load all the files from SYSTEM/... in a given target-files
429 ZipFile, and return a dict of {filename: File object}."""
430 out = {}
431 for info in z.infolist():
432 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
433 fn = "system/" + info.filename[7:]
434 data = z.read(info.filename)
435 out[fn] = File(fn, data)
436 return out
437
438
Doug Zongker761e6422009-09-25 10:45:39 -0700439DIFF_PROGRAM_BY_EXT = {
440 ".gz" : "imgdiff",
441 ".zip" : ["imgdiff", "-z"],
442 ".jar" : ["imgdiff", "-z"],
443 ".apk" : ["imgdiff", "-z"],
444 ".img" : "imgdiff",
445 }
Doug Zongkereef39442009-04-02 12:14:19 -0700446
Doug Zongkereef39442009-04-02 12:14:19 -0700447
Doug Zongker761e6422009-09-25 10:45:39 -0700448class Difference(object):
449 def __init__(self, tf, sf):
450 self.tf = tf
451 self.sf = sf
452 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700453
Doug Zongker761e6422009-09-25 10:45:39 -0700454 def ComputePatch(self):
455 """Compute the patch (as a string of data) needed to turn sf into
456 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700457
Doug Zongker761e6422009-09-25 10:45:39 -0700458 tf = self.tf
459 sf = self.sf
460
461 ext = os.path.splitext(tf.name)[1]
462 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
463
464 ttemp = tf.WriteToTemp()
465 stemp = sf.WriteToTemp()
466
467 ext = os.path.splitext(tf.name)[1]
468
469 try:
470 ptemp = tempfile.NamedTemporaryFile()
471 if isinstance(diff_program, list):
472 cmd = copy.copy(diff_program)
473 else:
474 cmd = [diff_program]
475 cmd.append(stemp.name)
476 cmd.append(ttemp.name)
477 cmd.append(ptemp.name)
478 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
479 _, err = p.communicate()
480 if err or p.returncode != 0:
481 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
482 return None
483 diff = ptemp.read()
484 finally:
485 ptemp.close()
486 stemp.close()
487 ttemp.close()
488
489 self.patch = diff
490 return self.tf, self.sf, self.patch
491
492
493 def GetPatch(self):
494 """Return a tuple (target_file, source_file, patch_data).
495 patch_data may be None if ComputePatch hasn't been called, or if
496 computing the patch failed."""
497 return self.tf, self.sf, self.patch
498
499
500def ComputeDifferences(diffs):
501 """Call ComputePatch on all the Difference objects in 'diffs'."""
502 print len(diffs), "diffs to compute"
503
504 # Do the largest files first, to try and reduce the long-pole effect.
505 by_size = [(i.tf.size, i) for i in diffs]
506 by_size.sort(reverse=True)
507 by_size = [i[1] for i in by_size]
508
509 lock = threading.Lock()
510 diff_iter = iter(by_size) # accessed under lock
511
512 def worker():
513 try:
514 lock.acquire()
515 for d in diff_iter:
516 lock.release()
517 start = time.time()
518 d.ComputePatch()
519 dur = time.time() - start
520 lock.acquire()
521
522 tf, sf, patch = d.GetPatch()
523 if sf.name == tf.name:
524 name = tf.name
525 else:
526 name = "%s (%s)" % (tf.name, sf.name)
527 if patch is None:
528 print "patching failed! %s" % (name,)
529 else:
530 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
531 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
532 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700533 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700534 print e
535 raise
536
537 # start worker threads; wait for them all to finish.
538 threads = [threading.Thread(target=worker)
539 for i in range(OPTIONS.worker_threads)]
540 for th in threads:
541 th.start()
542 while threads:
543 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700544
545
546def GetBuildProp(property, z):
547 """Return the fingerprint of the build of a given target-files
548 ZipFile object."""
549 bp = z.read("SYSTEM/build.prop")
550 if not property:
551 return bp
552 m = re.search(re.escape(property) + r"=(.*)\n", bp)
553 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700554 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700555 return m.group(1).strip()
556
557
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700558def GetRecoveryAPIVersion(zip):
559 """Returns the version of the recovery API. Version 0 is the older
560 amend code (no separate binary)."""
561 try:
562 version = zip.read("META/recovery-api-version.txt")
563 return int(version)
564 except KeyError:
565 try:
566 # version one didn't have the recovery-api-version.txt file, but
567 # it did include an updater binary.
568 zip.getinfo("OTA/bin/updater")
569 return 1
570 except KeyError:
571 return 0
572
Doug Zongker15604b82009-09-01 17:53:34 -0700573
Doug Zongkereef39442009-04-02 12:14:19 -0700574def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700575 source_version = GetRecoveryAPIVersion(source_zip)
Doug Zongker14833602010-02-02 13:12:04 -0800576 target_version = GetRecoveryAPIVersion(target_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700577
Doug Zongkerc637db12010-04-21 14:08:44 -0700578 if source_version == 0:
579 print ("WARNING: generating edify script for a source that "
580 "can't install it.")
581 script = edify_generator.EdifyGenerator(source_version)
Doug Zongkereef39442009-04-02 12:14:19 -0700582
Doug Zongker2ea21062010-04-28 16:05:21 -0700583 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700584 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700585 }
586
Doug Zongker05d3dea2009-06-22 11:32:31 -0700587 device_specific = common.DeviceSpecificParams(
588 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800589 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700590 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800591 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700592 output_zip=output_zip,
Doug Zongker2ea21062010-04-28 16:05:21 -0700593 script=script,
594 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700595
Doug Zongkereef39442009-04-02 12:14:19 -0700596 print "Loading target..."
597 target_data = LoadSystemFiles(target_zip)
598 print "Loading source..."
599 source_data = LoadSystemFiles(source_zip)
600
601 verbatim_targets = []
602 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700603 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700604 largest_source_size = 0
605 for fn in sorted(target_data.keys()):
606 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700607 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700608 sf = source_data.get(fn, None)
609
610 if sf is None or fn in OPTIONS.require_verbatim:
611 # This file should be included verbatim
612 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700613 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700614 print "send", fn, "verbatim"
615 tf.AddToZip(output_zip)
616 verbatim_targets.append((fn, tf.size))
617 elif tf.sha1 != sf.sha1:
618 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700619 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700620 else:
621 # Target file identical to source.
622 pass
623
Doug Zongker761e6422009-09-25 10:45:39 -0700624 ComputeDifferences(diffs)
625
626 for diff in diffs:
627 tf, sf, d = diff.GetPatch()
628 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
629 # patch is almost as big as the file; don't bother patching
630 tf.AddToZip(output_zip)
631 verbatim_targets.append((tf.name, tf.size))
632 else:
633 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800634 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700635 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700636
637 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
638 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700639 metadata["pre-build"] = source_fp
640 metadata["post-build"] = target_fp
Doug Zongkereef39442009-04-02 12:14:19 -0700641
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700642 script.Mount("MTD", "system", "/system")
643 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700644
Doug Zongker5da317e2009-06-02 13:38:17 -0700645 source_boot = File("/tmp/boot.img",
646 common.BuildBootableImage(
647 os.path.join(OPTIONS.source_tmp, "BOOT")))
648 target_boot = File("/tmp/boot.img",
649 common.BuildBootableImage(
650 os.path.join(OPTIONS.target_tmp, "BOOT")))
651 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700652
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700653 source_recovery = File("system/recovery.img",
654 common.BuildBootableImage(
655 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
656 target_recovery = File("system/recovery.img",
657 common.BuildBootableImage(
658 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
659 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700660
Doug Zongker881dd402009-09-20 14:03:55 -0700661 # Here's how we divide up the progress bar:
662 # 0.1 for verifying the start state (PatchCheck calls)
663 # 0.8 for applying patches (ApplyPatch calls)
664 # 0.1 for unpacking verbatim files, symlinking, and doing the
665 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700666
667 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700668 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700669
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700670 script.Print("Verifying current system...")
671
Doug Zongker881dd402009-09-20 14:03:55 -0700672 script.ShowProgress(0.1, 0)
673 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
674 if updating_boot:
675 total_verify_size += source_boot.size
676 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700677
Doug Zongker5a482092010-02-17 16:09:18 -0800678 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700680 so_far += sf.size
681 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700682
Doug Zongker5da317e2009-06-02 13:38:17 -0700683 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700684 d = Difference(target_boot, source_boot)
685 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700686 print "boot target: %d source: %d diff: %d" % (
687 target_boot.size, source_boot.size, len(d))
688
Doug Zongker048e7ca2009-06-15 14:31:53 -0700689 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700690
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700691 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
692 (source_boot.size, source_boot.sha1,
693 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700694 so_far += source_boot.size
695 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700696
697 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700698 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800699
Doug Zongker05d3dea2009-06-22 11:32:31 -0700700 device_specific.IncrementalOTA_VerifyEnd()
701
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700702 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700703
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700704 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700705 script.Print("Erasing user data...")
706 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700707
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700708 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700709 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
710 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700711 if i not in target_data] +
712 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700713
Doug Zongker881dd402009-09-20 14:03:55 -0700714 script.ShowProgress(0.8, 0)
715 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
716 if updating_boot:
717 total_patch_size += target_boot.size
718 so_far = 0
719
720 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800721 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800722 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700723 so_far += tf.size
724 script.SetProgress(so_far / total_patch_size)
725
Doug Zongkereef39442009-04-02 12:14:19 -0700726 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700727 # Produce the boot image by applying a patch to the current
728 # contents of the boot partition, and write it back to the
729 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700730 script.Print("Patching boot image...")
731 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
732 % (source_boot.size, source_boot.sha1,
733 target_boot.size, target_boot.sha1),
734 "-",
735 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800736 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700737 so_far += target_boot.size
738 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700739 print "boot image changed; including."
740 else:
741 print "boot image unchanged; skipping."
742
743 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700744 # Is it better to generate recovery as a patch from the current
745 # boot image, or from the previous recovery image? For large
746 # updates with significant kernel changes, probably the former.
747 # For small updates where the kernel hasn't changed, almost
748 # certainly the latter. We pick the first option. Future
749 # complicated schemes may let us effectively use both.
750 #
751 # A wacky possibility: as long as there is room in the boot
752 # partition, include the binaries and image files from recovery in
753 # the boot image (though not in the ramdisk) so they can be used
754 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700755 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800756 script.DeleteFiles(["/system/recovery-from-boot.p",
757 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700758 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700759 else:
760 print "recovery image unchanged; skipping."
761
Doug Zongker881dd402009-09-20 14:03:55 -0700762 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700763
764 target_symlinks = CopySystemFiles(target_zip, None)
765
766 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700767 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700768 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700769 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700770
771 # Note that this call will mess up the tree of Items, so make sure
772 # we're done with it.
773 source_symlinks = CopySystemFiles(source_zip, None)
774 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
775
776 # Delete all the symlinks in source that aren't in target. This
777 # needs to happen before verbatim files are unpacked, in case a
778 # symlink in the source is replaced by a real file in the target.
779 to_delete = []
780 for dest, link in source_symlinks:
781 if link not in target_symlinks_d:
782 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700783 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700784
785 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700786 script.Print("Unpacking new files...")
787 script.UnpackPackageDir("system", "/system")
788
Doug Zongker42265392010-02-12 10:21:00 -0800789 if updating_recovery:
790 script.Print("Unpacking new recovery...")
791 script.UnpackPackageDir("recovery", "/system")
792
Doug Zongker05d3dea2009-06-22 11:32:31 -0700793 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700794
795 # Create all the symlinks that don't already exist, or point to
796 # somewhere different than what we want. Delete each symlink before
797 # creating it, since the 'symlink' command won't overwrite.
798 to_create = []
799 for dest, link in target_symlinks:
800 if link in source_symlinks_d:
801 if dest != source_symlinks_d[link]:
802 to_create.append((dest, link))
803 else:
804 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700805 script.DeleteFiles([i[1] for i in to_create])
806 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700807
808 # Now that the symlinks are created, we can set all the
809 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700810 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700811
Doug Zongker881dd402009-09-20 14:03:55 -0700812 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700813 device_specific.IncrementalOTA_InstallEnd()
814
Doug Zongker1c390a22009-05-14 19:06:36 -0700815 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700816 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700817
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700818 script.AddToZip(target_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700819 WriteMetadata(metadata, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700820
821
822def main(argv):
823
824 def option_handler(o, a):
825 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700826 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700827 elif o in ("-k", "--package_key"):
828 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700829 elif o in ("-i", "--incremental_from"):
830 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700831 elif o in ("-w", "--wipe_user_data"):
832 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700833 elif o in ("-n", "--no_prereq"):
834 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700835 elif o in ("-e", "--extra_script"):
836 OPTIONS.extra_script = 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 Zongkerc637db12010-04-21 14:08:44 -0700844 extra_opts="b:k:i:d:wne:",
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 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700852 extra_option_handler=option_handler)
853
854 if len(args) != 2:
855 common.Usage(__doc__)
856 sys.exit(1)
857
Doug Zongker1c390a22009-05-14 19:06:36 -0700858 if OPTIONS.extra_script is not None:
859 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
860
Doug Zongkereef39442009-04-02 12:14:19 -0700861 print "unzipping target target-files..."
862 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700863
Doug Zongkerc18736b2009-09-30 09:20:32 -0700864 if OPTIONS.device_specific is None:
865 # look for the device-specific tools extension location in the input
866 try:
867 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
868 ds = f.read().strip()
869 f.close()
870 if ds:
871 ds = os.path.normpath(ds)
872 print "using device-specific extensions in", ds
873 OPTIONS.device_specific = ds
874 except IOError, e:
875 if e.errno == errno.ENOENT:
876 # nothing specified in the file
877 pass
878 else:
879 raise
880
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700881 common.LoadMaxSizes()
882 if not OPTIONS.max_image_size:
883 print
884 print " WARNING: Failed to load max image sizes; will not enforce"
885 print " image size limits."
886 print
887
Doug Zongkereef39442009-04-02 12:14:19 -0700888 OPTIONS.target_tmp = OPTIONS.input_tmp
889 input_zip = zipfile.ZipFile(args[0], "r")
890 if OPTIONS.package_key:
891 temp_zip_file = tempfile.NamedTemporaryFile()
892 output_zip = zipfile.ZipFile(temp_zip_file, "w",
893 compression=zipfile.ZIP_DEFLATED)
894 else:
895 output_zip = zipfile.ZipFile(args[1], "w",
896 compression=zipfile.ZIP_DEFLATED)
897
898 if OPTIONS.incremental_source is None:
899 WriteFullOTAPackage(input_zip, output_zip)
900 else:
901 print "unzipping source target-files..."
902 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
903 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
904 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
905
906 output_zip.close()
907 if OPTIONS.package_key:
908 SignOutput(temp_zip_file.name, args[1])
909 temp_zip_file.close()
910
911 common.Cleanup()
912
913 print "done."
914
915
916if __name__ == '__main__':
917 try:
918 main(sys.argv[1:])
919 except common.ExternalError, e:
920 print
921 print " ERROR: %s" % (e,)
922 print
923 sys.exit(1)