blob: dbb38f2fdf2b5bd833df526bab0436d38de3fdb6 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Given a target-files zipfile, produces an OTA package that installs
19that build. An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage: ota_from_target_files [flags] input_target_files output_ota_package
23
24 -b (--board_config) <file>
Doug Zongkerfdd8e692009-08-03 17:27:48 -070025 Deprecated.
Doug Zongkereef39442009-04-02 12:14:19 -070026
27 -k (--package_key) <key>
28 Key to use to sign the package (default is
29 "build/target/product/security/testkey").
30
31 -i (--incremental_from) <file>
32 Generate an incremental OTA using the given target-files zip as
33 the starting build.
34
Doug Zongkerdbfaae52009-04-21 17:12:54 -070035 -w (--wipe_user_data)
36 Generate an OTA package that will wipe the user data partition
37 when installed.
38
Doug Zongker962069c2009-04-23 11:41:58 -070039 -n (--no_prereq)
40 Omit the timestamp prereq check normally included at the top of
41 the build scripts (used for developer OTA packages which
42 legitimately need to go back and forth).
43
Doug Zongker1c390a22009-05-14 19:06:36 -070044 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
46
Doug Zongkerc494d7c2009-06-18 08:43:44 -070047 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
50
Doug Zongkereef39442009-04-02 12:14:19 -070051"""
52
53import sys
54
55if sys.hexversion < 0x02040000:
56 print >> sys.stderr, "Python 2.4 or newer is required."
57 sys.exit(1)
58
59import copy
60import os
61import re
62import sha
63import subprocess
64import tempfile
65import time
66import zipfile
67
68import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070069import amend_generator
70import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070071import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070072
73OPTIONS = common.OPTIONS
74OPTIONS.package_key = "build/target/product/security/testkey"
75OPTIONS.incremental_source = None
76OPTIONS.require_verbatim = set()
77OPTIONS.prohibit_verbatim = set(("system/build.prop",))
78OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070079OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070080OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070081OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070082OPTIONS.script_mode = 'auto'
Doug Zongkereef39442009-04-02 12:14:19 -070083
84def MostPopularKey(d, default):
85 """Given a dict, return the key corresponding to the largest
86 value. Returns 'default' if the dict is empty."""
87 x = [(v, k) for (k, v) in d.iteritems()]
88 if not x: return default
89 x.sort()
90 return x[-1][1]
91
92
93def IsSymlink(info):
94 """Return true if the zipfile.ZipInfo object passed in represents a
95 symlink."""
96 return (info.external_attr >> 16) == 0120777
97
98
99
100class Item:
101 """Items represent the metadata (user, group, mode) of files and
102 directories in the system image."""
103 ITEMS = {}
104 def __init__(self, name, dir=False):
105 self.name = name
106 self.uid = None
107 self.gid = None
108 self.mode = None
109 self.dir = dir
110
111 if name:
112 self.parent = Item.Get(os.path.dirname(name), dir=True)
113 self.parent.children.append(self)
114 else:
115 self.parent = None
116 if dir:
117 self.children = []
118
119 def Dump(self, indent=0):
120 if self.uid is not None:
121 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
122 else:
123 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
124 if self.dir:
125 print "%s%s" % (" "*indent, self.descendants)
126 print "%s%s" % (" "*indent, self.best_subtree)
127 for i in self.children:
128 i.Dump(indent=indent+1)
129
130 @classmethod
131 def Get(cls, name, dir=False):
132 if name not in cls.ITEMS:
133 cls.ITEMS[name] = Item(name, dir=dir)
134 return cls.ITEMS[name]
135
136 @classmethod
137 def GetMetadata(cls):
138 """Run the external 'fs_config' program to determine the desired
139 uid, gid, and mode for every Item object."""
140 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
141 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142 suffix = { False: "", True: "/" }
143 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
144 for i in cls.ITEMS.itervalues() if i.name])
145 output, error = p.communicate(input)
146 assert not error
147
148 for line in output.split("\n"):
149 if not line: continue
150 name, uid, gid, mode = line.split()
151 i = cls.ITEMS[name]
152 i.uid = int(uid)
153 i.gid = int(gid)
154 i.mode = int(mode, 8)
155 if i.dir:
156 i.children.sort(key=lambda i: i.name)
157
158 def CountChildMetadata(self):
159 """Count up the (uid, gid, mode) tuples for all children and
160 determine the best strategy for using set_perm_recursive and
161 set_perm to correctly chown/chmod all the files to their desired
162 values. Recursively calls itself for all descendants.
163
164 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
165 all descendants of this node. (dmode or fmode may be None.) Also
166 sets the best_subtree of each directory Item to the (uid, gid,
167 dmode, fmode) tuple that will match the most descendants of that
168 Item.
169 """
170
171 assert self.dir
172 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
173 for i in self.children:
174 if i.dir:
175 for k, v in i.CountChildMetadata().iteritems():
176 d[k] = d.get(k, 0) + v
177 else:
178 k = (i.uid, i.gid, None, i.mode)
179 d[k] = d.get(k, 0) + 1
180
181 # Find the (uid, gid, dmode, fmode) tuple that matches the most
182 # descendants.
183
184 # First, find the (uid, gid) pair that matches the most
185 # descendants.
186 ug = {}
187 for (uid, gid, _, _), count in d.iteritems():
188 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
189 ug = MostPopularKey(ug, (0, 0))
190
191 # Now find the dmode and fmode that match the most descendants
192 # with that (uid, gid), and choose those.
193 best_dmode = (0, 0755)
194 best_fmode = (0, 0644)
195 for k, count in d.iteritems():
196 if k[:2] != ug: continue
197 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
198 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
199 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
200
201 return d
202
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700203 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700204 """Append set_perm/set_perm_recursive commands to 'script' to
205 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700206 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700207
208 self.CountChildMetadata()
209
210 def recurse(item, current):
211 # current is the (uid, gid, dmode, fmode) tuple that the current
212 # item (and all its children) have already been set to. We only
213 # need to issue set_perm/set_perm_recursive commands if we're
214 # supposed to be something different.
215 if item.dir:
216 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700217 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700218 current = item.best_subtree
219
220 if item.uid != current[0] or item.gid != current[1] or \
221 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700222 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700223
224 for i in item.children:
225 recurse(i, current)
226 else:
227 if item.uid != current[0] or item.gid != current[1] or \
228 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700229 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700230
231 recurse(self, (-1, -1, -1, -1))
232
233
234def CopySystemFiles(input_zip, output_zip=None,
235 substitute=None):
236 """Copies files underneath system/ in the input zip to the output
237 zip. Populates the Item class with their metadata, and returns a
238 list of symlinks. output_zip may be None, in which case the copy is
239 skipped (but the other side effects still happen). substitute is an
240 optional dict of {output filename: contents} to be output instead of
241 certain input files.
242 """
243
244 symlinks = []
245
246 for info in input_zip.infolist():
247 if info.filename.startswith("SYSTEM/"):
248 basefilename = info.filename[7:]
249 if IsSymlink(info):
250 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700251 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700252 else:
253 info2 = copy.copy(info)
254 fn = info2.filename = "system/" + basefilename
255 if substitute and fn in substitute and substitute[fn] is None:
256 continue
257 if output_zip is not None:
258 if substitute and fn in substitute:
259 data = substitute[fn]
260 else:
261 data = input_zip.read(info.filename)
262 output_zip.writestr(info2, data)
263 if fn.endswith("/"):
264 Item.Get(fn[:-1], dir=True)
265 else:
266 Item.Get(fn, dir=False)
267
268 symlinks.sort()
269 return symlinks
270
271
Doug Zongkereef39442009-04-02 12:14:19 -0700272def SignOutput(temp_zip_name, output_zip_name):
273 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
274 pw = key_passwords[OPTIONS.package_key]
275
Doug Zongker951495f2009-08-14 12:44:19 -0700276 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
277 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700278
279
Doug Zongkereef39442009-04-02 12:14:19 -0700280def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700281 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700282 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700283
284 info = input_zip.read("OTA/android-info.txt")
285 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker171f1cd2009-06-15 22:36:37 -0700286 if m:
287 bootloaders = m.group(1).split("|")
Doug Zongker0493e242009-07-22 09:28:25 -0700288 if "*" not in bootloaders:
289 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700290
291
Doug Zongker73ef8252009-07-23 15:12:53 -0700292def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
293 """Generate a binary patch that creates the recovery image starting
294 with the boot image. (Most of the space in these images is just the
295 kernel, which is identical for the two, so the resulting patch
296 should be efficient.) Add it to the output zip, along with a shell
297 script that is run from init.rc on first boot to actually do the
298 patching and install the new recovery image.
299
300 recovery_img and boot_img should be File objects for the
301 corresponding images.
302
303 Returns an Item for the shell script, which must be made
304 executable.
305 """
306
307 patch = Difference(recovery_img, boot_img, "imgdiff")
308 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
309 Item.Get("system/recovery-from-boot.p", dir=False)
310
311 # Images with different content will have a different first page, so
312 # we check to see if this recovery has already been installed by
313 # testing just the first 2k.
314 HEADER_SIZE = 2048
315 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
316 sh = """#!/system/bin/sh
317if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
318 log -t recovery "Installing new recovery image"
319 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
320else
321 log -t recovery "Recovery image already installed"
322fi
323""" % { 'boot_size': boot_img.size,
324 'boot_sha1': boot_img.sha1,
325 'header_size': HEADER_SIZE,
326 'header_sha1': header_sha1,
327 'recovery_size': recovery_img.size,
328 'recovery_sha1': recovery_img.sha1 }
329 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
330 return Item.Get("system/etc/install-recovery.sh", dir=False)
331
332
Doug Zongkereef39442009-04-02 12:14:19 -0700333def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700334 if OPTIONS.script_mode == "auto":
335 script = both_generator.BothGenerator(2)
336 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700337 script = amend_generator.AmendGenerator()
338 else:
339 # 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.
Doug Zongker03061472009-07-13 18:36:37 -0700342 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700343
Doug Zongker05d3dea2009-06-22 11:32:31 -0700344 device_specific = common.DeviceSpecificParams(
345 input_zip=input_zip,
346 output_zip=output_zip,
347 script=script,
348 input_tmp=OPTIONS.input_tmp)
349
Doug Zongker962069c2009-04-23 11:41:58 -0700350 if not OPTIONS.omit_prereq:
351 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700352 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700353
354 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700355 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700356
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700357 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700358
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700359 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700360 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700361
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700362 script.FormatPartition("system")
363 script.Mount("MTD", "system", "/system")
364 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700365
366 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700367 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700368
Doug Zongker73ef8252009-07-23 15:12:53 -0700369 boot_img = File("boot.img", common.BuildBootableImage(
370 os.path.join(OPTIONS.input_tmp, "BOOT")))
371 recovery_img = File("recovery.img", common.BuildBootableImage(
372 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
373 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Doug Zongker73ef8252009-07-23 15:12:53 -0700375 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700376
Doug Zongker73ef8252009-07-23 15:12:53 -0700377 # GetMetadata uses the data in android_filesystem_config.h to assign
378 # the uid/gid/mode of all files. We want to override that for the
379 # recovery patching shell script to make it executable.
380 i.uid = 0
381 i.gid = 0
382 i.mode = 0544
383 Item.Get("system").SetPermissions(script)
384
385 common.CheckSize(boot_img.data, "boot.img")
386 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.ShowProgress(0.2, 0)
388
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700389 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700390 script.WriteRawImage("boot", "boot.img")
391
392 script.ShowProgress(0.1, 0)
393 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700394
Doug Zongker1c390a22009-05-14 19:06:36 -0700395 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700396 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700397
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 Zongkerf6a8bad2009-05-29 11:41:21 -0700430def Difference(tf, sf, diff_program):
431 """Return the patch (as a string of data) needed to turn sf into tf.
432 diff_program is the name of an external program (or list, if
433 additional arguments are desired) to run to generate the diff.
434 """
Doug Zongkereef39442009-04-02 12:14:19 -0700435
436 ttemp = tf.WriteToTemp()
437 stemp = sf.WriteToTemp()
438
439 ext = os.path.splitext(tf.name)[1]
440
441 try:
442 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700443 if isinstance(diff_program, list):
444 cmd = copy.copy(diff_program)
445 else:
446 cmd = [diff_program]
447 cmd.append(stemp.name)
448 cmd.append(ttemp.name)
449 cmd.append(ptemp.name)
450 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700451 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700452 if err or p.returncode != 0:
453 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
454 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700455 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700456 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700457 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700458 stemp.close()
459 ttemp.close()
460
461 return diff
462
463
464def GetBuildProp(property, z):
465 """Return the fingerprint of the build of a given target-files
466 ZipFile object."""
467 bp = z.read("SYSTEM/build.prop")
468 if not property:
469 return bp
470 m = re.search(re.escape(property) + r"=(.*)\n", bp)
471 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700472 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700473 return m.group(1).strip()
474
475
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700476def GetRecoveryAPIVersion(zip):
477 """Returns the version of the recovery API. Version 0 is the older
478 amend code (no separate binary)."""
479 try:
480 version = zip.read("META/recovery-api-version.txt")
481 return int(version)
482 except KeyError:
483 try:
484 # version one didn't have the recovery-api-version.txt file, but
485 # it did include an updater binary.
486 zip.getinfo("OTA/bin/updater")
487 return 1
488 except KeyError:
489 return 0
490
Doug Zongkereef39442009-04-02 12:14:19 -0700491def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700492 source_version = GetRecoveryAPIVersion(source_zip)
493
494 if OPTIONS.script_mode == 'amend':
495 script = amend_generator.AmendGenerator()
496 elif OPTIONS.script_mode == 'edify':
497 if source_version == 0:
498 print ("WARNING: generating edify script for a source that "
499 "can't install it.")
500 script = edify_generator.EdifyGenerator(source_version)
501 elif OPTIONS.script_mode == 'auto':
502 if source_version > 0:
503 script = edify_generator.EdifyGenerator(source_version)
504 else:
505 script = amend_generator.AmendGenerator()
506 else:
507 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700508
Doug Zongker05d3dea2009-06-22 11:32:31 -0700509 device_specific = common.DeviceSpecificParams(
510 source_zip=source_zip,
511 target_zip=target_zip,
512 output_zip=output_zip,
513 script=script)
514
Doug Zongkereef39442009-04-02 12:14:19 -0700515 print "Loading target..."
516 target_data = LoadSystemFiles(target_zip)
517 print "Loading source..."
518 source_data = LoadSystemFiles(source_zip)
519
520 verbatim_targets = []
521 patch_list = []
522 largest_source_size = 0
523 for fn in sorted(target_data.keys()):
524 tf = target_data[fn]
525 sf = source_data.get(fn, None)
526
527 if sf is None or fn in OPTIONS.require_verbatim:
528 # This file should be included verbatim
529 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700530 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700531 print "send", fn, "verbatim"
532 tf.AddToZip(output_zip)
533 verbatim_targets.append((fn, tf.size))
534 elif tf.sha1 != sf.sha1:
535 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700536 diff_method = "bsdiff"
537 if tf.name.endswith(".gz"):
538 diff_method = "imgdiff"
539 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700540 if d is not None:
541 print fn, tf.size, len(d), (float(len(d)) / tf.size)
542 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700543 # patch is almost as big as the file; don't bother patching
544 tf.AddToZip(output_zip)
545 verbatim_targets.append((fn, tf.size))
546 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700547 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700548 patch_list.append((fn, tf, sf, tf.size))
549 largest_source_size = max(largest_source_size, sf.size)
550 else:
551 # Target file identical to source.
552 pass
553
554 total_verbatim_size = sum([i[1] for i in verbatim_targets])
555 total_patched_size = sum([i[3] for i in patch_list])
556
557 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
558 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
559
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700560 script.Mount("MTD", "system", "/system")
561 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700562
Doug Zongker5da317e2009-06-02 13:38:17 -0700563 source_boot = File("/tmp/boot.img",
564 common.BuildBootableImage(
565 os.path.join(OPTIONS.source_tmp, "BOOT")))
566 target_boot = File("/tmp/boot.img",
567 common.BuildBootableImage(
568 os.path.join(OPTIONS.target_tmp, "BOOT")))
569 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700570
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700571 source_recovery = File("system/recovery.img",
572 common.BuildBootableImage(
573 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
574 target_recovery = File("system/recovery.img",
575 common.BuildBootableImage(
576 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
577 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700578
Doug Zongker05d3dea2009-06-22 11:32:31 -0700579 # We reserve the last 0.3 of the progress bar for the
580 # device-specific IncrementalOTA_InstallEnd() call at the end, which
581 # will typically install a radio image.
582 progress_bar_total = 0.7
Doug Zongkereef39442009-04-02 12:14:19 -0700583 if updating_boot:
584 progress_bar_total -= 0.1
Doug Zongkereef39442009-04-02 12:14:19 -0700585
586 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700587 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700588
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700589 script.Print("Verifying current system...")
590
Doug Zongkereef39442009-04-02 12:14:19 -0700591 pb_verify = progress_bar_total * 0.3 * \
592 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700593 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700594
595 for i, (fn, tf, sf, size) in enumerate(patch_list):
596 if i % 5 == 0:
597 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700598 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
599
600 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700601
Doug Zongker5da317e2009-06-02 13:38:17 -0700602 if updating_boot:
603 d = Difference(target_boot, source_boot, "imgdiff")
604 print "boot target: %d source: %d diff: %d" % (
605 target_boot.size, source_boot.size, len(d))
606
Doug Zongker048e7ca2009-06-15 14:31:53 -0700607 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700608
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700609 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
610 (source_boot.size, source_boot.sha1,
611 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700612
613 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700614 script.CacheFreeSpaceCheck(largest_source_size)
615 script.Print("Unpacking patches...")
616 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700617
Doug Zongker05d3dea2009-06-22 11:32:31 -0700618 device_specific.IncrementalOTA_VerifyEnd()
619
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700620 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700621
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700622 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700623 script.Print("Erasing user data...")
624 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700625
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700626 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700627 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
628 ["/"+i for i in sorted(source_data)
629 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700630
631 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700632 # Produce the boot image by applying a patch to the current
633 # contents of the boot partition, and write it back to the
634 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700635 script.Print("Patching boot image...")
636 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
637 % (source_boot.size, source_boot.sha1,
638 target_boot.size, target_boot.sha1),
639 "-",
640 target_boot.size, target_boot.sha1,
641 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700642 print "boot image changed; including."
643 else:
644 print "boot image unchanged; skipping."
645
646 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700647 # Is it better to generate recovery as a patch from the current
648 # boot image, or from the previous recovery image? For large
649 # updates with significant kernel changes, probably the former.
650 # For small updates where the kernel hasn't changed, almost
651 # certainly the latter. We pick the first option. Future
652 # complicated schemes may let us effectively use both.
653 #
654 # A wacky possibility: as long as there is room in the boot
655 # partition, include the binaries and image files from recovery in
656 # the boot image (though not in the ramdisk) so they can be used
657 # as fodder for constructing the recovery image.
658 recovery_sh_item = MakeRecoveryPatch(output_zip,
659 target_recovery, target_boot)
660 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700661 else:
662 print "recovery image unchanged; skipping."
663
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700664 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700665 pb_apply = progress_bar_total * 0.7 * \
666 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700667 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700668 for i, (fn, tf, sf, size) in enumerate(patch_list):
669 if i % 5 == 0:
670 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700671 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
672 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
673 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700674
675 target_symlinks = CopySystemFiles(target_zip, None)
676
677 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700678 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700679 Item.GetMetadata()
680 if updating_recovery:
681 recovery_sh_item.uid = 0
682 recovery_sh_item.gid = 0
683 recovery_sh_item.mode = 0544
684 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700685
686 # Note that this call will mess up the tree of Items, so make sure
687 # we're done with it.
688 source_symlinks = CopySystemFiles(source_zip, None)
689 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
690
691 # Delete all the symlinks in source that aren't in target. This
692 # needs to happen before verbatim files are unpacked, in case a
693 # symlink in the source is replaced by a real file in the target.
694 to_delete = []
695 for dest, link in source_symlinks:
696 if link not in target_symlinks_d:
697 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700698 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700699
700 if verbatim_targets:
701 pb_verbatim = progress_bar_total * \
702 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700703 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700704 script.ShowProgress(pb_verbatim, 5)
705 script.Print("Unpacking new files...")
706 script.UnpackPackageDir("system", "/system")
707
Doug Zongker05d3dea2009-06-22 11:32:31 -0700708 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700709
710 # Create all the symlinks that don't already exist, or point to
711 # somewhere different than what we want. Delete each symlink before
712 # creating it, since the 'symlink' command won't overwrite.
713 to_create = []
714 for dest, link in target_symlinks:
715 if link in source_symlinks_d:
716 if dest != source_symlinks_d[link]:
717 to_create.append((dest, link))
718 else:
719 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700720 script.DeleteFiles([i[1] for i in to_create])
721 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700722
723 # Now that the symlinks are created, we can set all the
724 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700725 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700726
Doug Zongker05d3dea2009-06-22 11:32:31 -0700727 # Write the radio image, if necessary.
728 script.ShowProgress(0.3, 10)
729 device_specific.IncrementalOTA_InstallEnd()
730
Doug Zongker1c390a22009-05-14 19:06:36 -0700731 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700732 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700733
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700734 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700735
736
737def main(argv):
738
739 def option_handler(o, a):
740 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700741 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700742 elif o in ("-k", "--package_key"):
743 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700744 elif o in ("-i", "--incremental_from"):
745 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700746 elif o in ("-w", "--wipe_user_data"):
747 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700748 elif o in ("-n", "--no_prereq"):
749 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700750 elif o in ("-e", "--extra_script"):
751 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700752 elif o in ("-m", "--script_mode"):
753 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700754 else:
755 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700756 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700757
758 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700759 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700760 extra_long_opts=["board_config=",
761 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700762 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700763 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700764 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700765 "extra_script=",
766 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700767 extra_option_handler=option_handler)
768
769 if len(args) != 2:
770 common.Usage(__doc__)
771 sys.exit(1)
772
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700773 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
774 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
775
Doug Zongker1c390a22009-05-14 19:06:36 -0700776 if OPTIONS.extra_script is not None:
777 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
778
Doug Zongkereef39442009-04-02 12:14:19 -0700779 print "unzipping target target-files..."
780 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700781
782 common.LoadMaxSizes()
783 if not OPTIONS.max_image_size:
784 print
785 print " WARNING: Failed to load max image sizes; will not enforce"
786 print " image size limits."
787 print
788
Doug Zongkereef39442009-04-02 12:14:19 -0700789 OPTIONS.target_tmp = OPTIONS.input_tmp
790 input_zip = zipfile.ZipFile(args[0], "r")
791 if OPTIONS.package_key:
792 temp_zip_file = tempfile.NamedTemporaryFile()
793 output_zip = zipfile.ZipFile(temp_zip_file, "w",
794 compression=zipfile.ZIP_DEFLATED)
795 else:
796 output_zip = zipfile.ZipFile(args[1], "w",
797 compression=zipfile.ZIP_DEFLATED)
798
799 if OPTIONS.incremental_source is None:
800 WriteFullOTAPackage(input_zip, output_zip)
801 else:
802 print "unzipping source target-files..."
803 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
804 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
805 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
806
807 output_zip.close()
808 if OPTIONS.package_key:
809 SignOutput(temp_zip_file.name, args[1])
810 temp_zip_file.close()
811
812 common.Cleanup()
813
814 print "done."
815
816
817if __name__ == '__main__':
818 try:
819 main(sys.argv[1:])
820 except common.ExternalError, e:
821 print
822 print " ERROR: %s" % (e,)
823 print
824 sys.exit(1)