blob: 299e60a88be0205c575caad454c540891a846ffe [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
Doug Zongker761e6422009-09-25 10:45:39 -070065import threading
Doug Zongkereef39442009-04-02 12:14:19 -070066import time
67import zipfile
68
69import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070070import amend_generator
71import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070072import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070073
74OPTIONS = common.OPTIONS
75OPTIONS.package_key = "build/target/product/security/testkey"
76OPTIONS.incremental_source = None
77OPTIONS.require_verbatim = set()
78OPTIONS.prohibit_verbatim = set(("system/build.prop",))
79OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070080OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070081OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070082OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070083OPTIONS.script_mode = 'auto'
Doug Zongker761e6422009-09-25 10:45:39 -070084OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070085
86def MostPopularKey(d, default):
87 """Given a dict, return the key corresponding to the largest
88 value. Returns 'default' if the dict is empty."""
89 x = [(v, k) for (k, v) in d.iteritems()]
90 if not x: return default
91 x.sort()
92 return x[-1][1]
93
94
95def IsSymlink(info):
96 """Return true if the zipfile.ZipInfo object passed in represents a
97 symlink."""
98 return (info.external_attr >> 16) == 0120777
99
100
101
102class Item:
103 """Items represent the metadata (user, group, mode) of files and
104 directories in the system image."""
105 ITEMS = {}
106 def __init__(self, name, dir=False):
107 self.name = name
108 self.uid = None
109 self.gid = None
110 self.mode = None
111 self.dir = dir
112
113 if name:
114 self.parent = Item.Get(os.path.dirname(name), dir=True)
115 self.parent.children.append(self)
116 else:
117 self.parent = None
118 if dir:
119 self.children = []
120
121 def Dump(self, indent=0):
122 if self.uid is not None:
123 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
124 else:
125 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
126 if self.dir:
127 print "%s%s" % (" "*indent, self.descendants)
128 print "%s%s" % (" "*indent, self.best_subtree)
129 for i in self.children:
130 i.Dump(indent=indent+1)
131
132 @classmethod
133 def Get(cls, name, dir=False):
134 if name not in cls.ITEMS:
135 cls.ITEMS[name] = Item(name, dir=dir)
136 return cls.ITEMS[name]
137
138 @classmethod
139 def GetMetadata(cls):
140 """Run the external 'fs_config' program to determine the desired
141 uid, gid, and mode for every Item object."""
142 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
143 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144 suffix = { False: "", True: "/" }
145 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
146 for i in cls.ITEMS.itervalues() if i.name])
147 output, error = p.communicate(input)
148 assert not error
149
150 for line in output.split("\n"):
151 if not line: continue
152 name, uid, gid, mode = line.split()
153 i = cls.ITEMS[name]
154 i.uid = int(uid)
155 i.gid = int(gid)
156 i.mode = int(mode, 8)
157 if i.dir:
158 i.children.sort(key=lambda i: i.name)
159
160 def CountChildMetadata(self):
161 """Count up the (uid, gid, mode) tuples for all children and
162 determine the best strategy for using set_perm_recursive and
163 set_perm to correctly chown/chmod all the files to their desired
164 values. Recursively calls itself for all descendants.
165
166 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
167 all descendants of this node. (dmode or fmode may be None.) Also
168 sets the best_subtree of each directory Item to the (uid, gid,
169 dmode, fmode) tuple that will match the most descendants of that
170 Item.
171 """
172
173 assert self.dir
174 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
175 for i in self.children:
176 if i.dir:
177 for k, v in i.CountChildMetadata().iteritems():
178 d[k] = d.get(k, 0) + v
179 else:
180 k = (i.uid, i.gid, None, i.mode)
181 d[k] = d.get(k, 0) + 1
182
183 # Find the (uid, gid, dmode, fmode) tuple that matches the most
184 # descendants.
185
186 # First, find the (uid, gid) pair that matches the most
187 # descendants.
188 ug = {}
189 for (uid, gid, _, _), count in d.iteritems():
190 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
191 ug = MostPopularKey(ug, (0, 0))
192
193 # Now find the dmode and fmode that match the most descendants
194 # with that (uid, gid), and choose those.
195 best_dmode = (0, 0755)
196 best_fmode = (0, 0644)
197 for k, count in d.iteritems():
198 if k[:2] != ug: continue
199 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
200 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
201 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
202
203 return d
204
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700205 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700206 """Append set_perm/set_perm_recursive commands to 'script' to
207 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700208 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700209
210 self.CountChildMetadata()
211
212 def recurse(item, current):
213 # current is the (uid, gid, dmode, fmode) tuple that the current
214 # item (and all its children) have already been set to. We only
215 # need to issue set_perm/set_perm_recursive commands if we're
216 # supposed to be something different.
217 if item.dir:
218 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700219 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700220 current = item.best_subtree
221
222 if item.uid != current[0] or item.gid != current[1] or \
223 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700224 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700225
226 for i in item.children:
227 recurse(i, current)
228 else:
229 if item.uid != current[0] or item.gid != current[1] or \
230 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700231 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700232
233 recurse(self, (-1, -1, -1, -1))
234
235
236def CopySystemFiles(input_zip, output_zip=None,
237 substitute=None):
238 """Copies files underneath system/ in the input zip to the output
239 zip. Populates the Item class with their metadata, and returns a
240 list of symlinks. output_zip may be None, in which case the copy is
241 skipped (but the other side effects still happen). substitute is an
242 optional dict of {output filename: contents} to be output instead of
243 certain input files.
244 """
245
246 symlinks = []
247
248 for info in input_zip.infolist():
249 if info.filename.startswith("SYSTEM/"):
250 basefilename = info.filename[7:]
251 if IsSymlink(info):
252 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700253 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700254 else:
255 info2 = copy.copy(info)
256 fn = info2.filename = "system/" + basefilename
257 if substitute and fn in substitute and substitute[fn] is None:
258 continue
259 if output_zip is not None:
260 if substitute and fn in substitute:
261 data = substitute[fn]
262 else:
263 data = input_zip.read(info.filename)
264 output_zip.writestr(info2, data)
265 if fn.endswith("/"):
266 Item.Get(fn[:-1], dir=True)
267 else:
268 Item.Get(fn, dir=False)
269
270 symlinks.sort()
271 return symlinks
272
273
Doug Zongkereef39442009-04-02 12:14:19 -0700274def SignOutput(temp_zip_name, output_zip_name):
275 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
276 pw = key_passwords[OPTIONS.package_key]
277
Doug Zongker951495f2009-08-14 12:44:19 -0700278 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
279 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700280
281
Doug Zongkereef39442009-04-02 12:14:19 -0700282def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700283 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700284 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700285
Doug Zongkereef39442009-04-02 12:14:19 -0700286
Doug Zongker73ef8252009-07-23 15:12:53 -0700287def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
288 """Generate a binary patch that creates the recovery image starting
289 with the boot image. (Most of the space in these images is just the
290 kernel, which is identical for the two, so the resulting patch
291 should be efficient.) Add it to the output zip, along with a shell
292 script that is run from init.rc on first boot to actually do the
293 patching and install the new recovery image.
294
295 recovery_img and boot_img should be File objects for the
296 corresponding images.
297
298 Returns an Item for the shell script, which must be made
299 executable.
300 """
301
Doug Zongker761e6422009-09-25 10:45:39 -0700302 d = Difference(recovery_img, boot_img)
303 _, _, patch = d.ComputePatch()
Doug Zongker73ef8252009-07-23 15:12:53 -0700304 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
305 Item.Get("system/recovery-from-boot.p", dir=False)
306
307 # Images with different content will have a different first page, so
308 # we check to see if this recovery has already been installed by
309 # testing just the first 2k.
310 HEADER_SIZE = 2048
311 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
312 sh = """#!/system/bin/sh
313if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
314 log -t recovery "Installing new recovery image"
315 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
316else
317 log -t recovery "Recovery image already installed"
318fi
319""" % { 'boot_size': boot_img.size,
320 'boot_sha1': boot_img.sha1,
321 'header_size': HEADER_SIZE,
322 'header_sha1': header_sha1,
323 'recovery_size': recovery_img.size,
324 'recovery_sha1': recovery_img.sha1 }
325 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
326 return Item.Get("system/etc/install-recovery.sh", dir=False)
327
328
Doug Zongkereef39442009-04-02 12:14:19 -0700329def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700330 if OPTIONS.script_mode == "auto":
331 script = both_generator.BothGenerator(2)
332 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700333 script = amend_generator.AmendGenerator()
334 else:
335 # TODO: how to determine this? We don't know what version it will
336 # be installed on top of. For now, we expect the API just won't
337 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700338 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700339
Doug Zongker05d3dea2009-06-22 11:32:31 -0700340 device_specific = common.DeviceSpecificParams(
341 input_zip=input_zip,
342 output_zip=output_zip,
343 script=script,
344 input_tmp=OPTIONS.input_tmp)
345
Doug Zongker962069c2009-04-23 11:41:58 -0700346 if not OPTIONS.omit_prereq:
347 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700348 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700349
350 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700351 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700352
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700353 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700354
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700355 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700356 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700357
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700358 script.FormatPartition("system")
359 script.Mount("MTD", "system", "/system")
360 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700361
362 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700363 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Doug Zongker73ef8252009-07-23 15:12:53 -0700365 boot_img = File("boot.img", common.BuildBootableImage(
366 os.path.join(OPTIONS.input_tmp, "BOOT")))
367 recovery_img = File("recovery.img", common.BuildBootableImage(
368 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
369 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700370
Doug Zongker73ef8252009-07-23 15:12:53 -0700371 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700372
Doug Zongker73ef8252009-07-23 15:12:53 -0700373 # GetMetadata uses the data in android_filesystem_config.h to assign
374 # the uid/gid/mode of all files. We want to override that for the
375 # recovery patching shell script to make it executable.
376 i.uid = 0
377 i.gid = 0
378 i.mode = 0544
379 Item.Get("system").SetPermissions(script)
380
381 common.CheckSize(boot_img.data, "boot.img")
382 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700383 script.ShowProgress(0.2, 0)
384
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700385 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700386 script.WriteRawImage("boot", "boot.img")
387
388 script.ShowProgress(0.1, 0)
389 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700390
Doug Zongker1c390a22009-05-14 19:06:36 -0700391 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700392 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700393
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700394 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700395
396
397class File(object):
398 def __init__(self, name, data):
399 self.name = name
400 self.data = data
401 self.size = len(data)
402 self.sha1 = sha.sha(data).hexdigest()
403
404 def WriteToTemp(self):
405 t = tempfile.NamedTemporaryFile()
406 t.write(self.data)
407 t.flush()
408 return t
409
410 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700411 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700412
413
414def LoadSystemFiles(z):
415 """Load all the files from SYSTEM/... in a given target-files
416 ZipFile, and return a dict of {filename: File object}."""
417 out = {}
418 for info in z.infolist():
419 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
420 fn = "system/" + info.filename[7:]
421 data = z.read(info.filename)
422 out[fn] = File(fn, data)
423 return out
424
425
Doug Zongker761e6422009-09-25 10:45:39 -0700426DIFF_PROGRAM_BY_EXT = {
427 ".gz" : "imgdiff",
428 ".zip" : ["imgdiff", "-z"],
429 ".jar" : ["imgdiff", "-z"],
430 ".apk" : ["imgdiff", "-z"],
431 ".img" : "imgdiff",
432 }
Doug Zongkereef39442009-04-02 12:14:19 -0700433
Doug Zongkereef39442009-04-02 12:14:19 -0700434
Doug Zongker761e6422009-09-25 10:45:39 -0700435class Difference(object):
436 def __init__(self, tf, sf):
437 self.tf = tf
438 self.sf = sf
439 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700440
Doug Zongker761e6422009-09-25 10:45:39 -0700441 def ComputePatch(self):
442 """Compute the patch (as a string of data) needed to turn sf into
443 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongker761e6422009-09-25 10:45:39 -0700445 tf = self.tf
446 sf = self.sf
447
448 ext = os.path.splitext(tf.name)[1]
449 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
450
451 ttemp = tf.WriteToTemp()
452 stemp = sf.WriteToTemp()
453
454 ext = os.path.splitext(tf.name)[1]
455
456 try:
457 ptemp = tempfile.NamedTemporaryFile()
458 if isinstance(diff_program, list):
459 cmd = copy.copy(diff_program)
460 else:
461 cmd = [diff_program]
462 cmd.append(stemp.name)
463 cmd.append(ttemp.name)
464 cmd.append(ptemp.name)
465 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
466 _, err = p.communicate()
467 if err or p.returncode != 0:
468 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
469 return None
470 diff = ptemp.read()
471 finally:
472 ptemp.close()
473 stemp.close()
474 ttemp.close()
475
476 self.patch = diff
477 return self.tf, self.sf, self.patch
478
479
480 def GetPatch(self):
481 """Return a tuple (target_file, source_file, patch_data).
482 patch_data may be None if ComputePatch hasn't been called, or if
483 computing the patch failed."""
484 return self.tf, self.sf, self.patch
485
486
487def ComputeDifferences(diffs):
488 """Call ComputePatch on all the Difference objects in 'diffs'."""
489 print len(diffs), "diffs to compute"
490
491 # Do the largest files first, to try and reduce the long-pole effect.
492 by_size = [(i.tf.size, i) for i in diffs]
493 by_size.sort(reverse=True)
494 by_size = [i[1] for i in by_size]
495
496 lock = threading.Lock()
497 diff_iter = iter(by_size) # accessed under lock
498
499 def worker():
500 try:
501 lock.acquire()
502 for d in diff_iter:
503 lock.release()
504 start = time.time()
505 d.ComputePatch()
506 dur = time.time() - start
507 lock.acquire()
508
509 tf, sf, patch = d.GetPatch()
510 if sf.name == tf.name:
511 name = tf.name
512 else:
513 name = "%s (%s)" % (tf.name, sf.name)
514 if patch is None:
515 print "patching failed! %s" % (name,)
516 else:
517 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
518 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
519 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700520 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700521 print e
522 raise
523
524 # start worker threads; wait for them all to finish.
525 threads = [threading.Thread(target=worker)
526 for i in range(OPTIONS.worker_threads)]
527 for th in threads:
528 th.start()
529 while threads:
530 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700531
532
533def GetBuildProp(property, z):
534 """Return the fingerprint of the build of a given target-files
535 ZipFile object."""
536 bp = z.read("SYSTEM/build.prop")
537 if not property:
538 return bp
539 m = re.search(re.escape(property) + r"=(.*)\n", bp)
540 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700541 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700542 return m.group(1).strip()
543
544
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700545def GetRecoveryAPIVersion(zip):
546 """Returns the version of the recovery API. Version 0 is the older
547 amend code (no separate binary)."""
548 try:
549 version = zip.read("META/recovery-api-version.txt")
550 return int(version)
551 except KeyError:
552 try:
553 # version one didn't have the recovery-api-version.txt file, but
554 # it did include an updater binary.
555 zip.getinfo("OTA/bin/updater")
556 return 1
557 except KeyError:
558 return 0
559
Doug Zongker15604b82009-09-01 17:53:34 -0700560
Doug Zongkereef39442009-04-02 12:14:19 -0700561def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700562 source_version = GetRecoveryAPIVersion(source_zip)
563
564 if OPTIONS.script_mode == 'amend':
565 script = amend_generator.AmendGenerator()
566 elif OPTIONS.script_mode == 'edify':
567 if source_version == 0:
568 print ("WARNING: generating edify script for a source that "
569 "can't install it.")
570 script = edify_generator.EdifyGenerator(source_version)
571 elif OPTIONS.script_mode == 'auto':
572 if source_version > 0:
573 script = edify_generator.EdifyGenerator(source_version)
574 else:
575 script = amend_generator.AmendGenerator()
576 else:
577 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700578
Doug Zongker05d3dea2009-06-22 11:32:31 -0700579 device_specific = common.DeviceSpecificParams(
580 source_zip=source_zip,
581 target_zip=target_zip,
582 output_zip=output_zip,
583 script=script)
584
Doug Zongkereef39442009-04-02 12:14:19 -0700585 print "Loading target..."
586 target_data = LoadSystemFiles(target_zip)
587 print "Loading source..."
588 source_data = LoadSystemFiles(source_zip)
589
590 verbatim_targets = []
591 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700592 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700593 largest_source_size = 0
594 for fn in sorted(target_data.keys()):
595 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700596 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700597 sf = source_data.get(fn, None)
598
599 if sf is None or fn in OPTIONS.require_verbatim:
600 # This file should be included verbatim
601 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700602 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700603 print "send", fn, "verbatim"
604 tf.AddToZip(output_zip)
605 verbatim_targets.append((fn, tf.size))
606 elif tf.sha1 != sf.sha1:
607 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700608 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700609 else:
610 # Target file identical to source.
611 pass
612
Doug Zongker761e6422009-09-25 10:45:39 -0700613 ComputeDifferences(diffs)
614
615 for diff in diffs:
616 tf, sf, d = diff.GetPatch()
617 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
618 # patch is almost as big as the file; don't bother patching
619 tf.AddToZip(output_zip)
620 verbatim_targets.append((tf.name, tf.size))
621 else:
622 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
623 patch_list.append((tf.name, tf, sf, tf.size))
624 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700625
626 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
627 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
628
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700629 script.Mount("MTD", "system", "/system")
630 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700631
Doug Zongker5da317e2009-06-02 13:38:17 -0700632 source_boot = File("/tmp/boot.img",
633 common.BuildBootableImage(
634 os.path.join(OPTIONS.source_tmp, "BOOT")))
635 target_boot = File("/tmp/boot.img",
636 common.BuildBootableImage(
637 os.path.join(OPTIONS.target_tmp, "BOOT")))
638 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700639
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700640 source_recovery = File("system/recovery.img",
641 common.BuildBootableImage(
642 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
643 target_recovery = File("system/recovery.img",
644 common.BuildBootableImage(
645 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
646 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700647
Doug Zongker881dd402009-09-20 14:03:55 -0700648 # Here's how we divide up the progress bar:
649 # 0.1 for verifying the start state (PatchCheck calls)
650 # 0.8 for applying patches (ApplyPatch calls)
651 # 0.1 for unpacking verbatim files, symlinking, and doing the
652 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700653
654 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700655 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700656
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700657 script.Print("Verifying current system...")
658
Doug Zongker881dd402009-09-20 14:03:55 -0700659 script.ShowProgress(0.1, 0)
660 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
661 if updating_boot:
662 total_verify_size += source_boot.size
663 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700664
Doug Zongker881dd402009-09-20 14:03:55 -0700665 for fn, tf, sf, size in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700666 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700667 so_far += sf.size
668 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700669
Doug Zongker5da317e2009-06-02 13:38:17 -0700670 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700671 d = Difference(target_boot, source_boot)
672 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700673 print "boot target: %d source: %d diff: %d" % (
674 target_boot.size, source_boot.size, len(d))
675
Doug Zongker048e7ca2009-06-15 14:31:53 -0700676 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700677
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700678 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
679 (source_boot.size, source_boot.sha1,
680 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700681 so_far += source_boot.size
682 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700683
684 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700685 script.CacheFreeSpaceCheck(largest_source_size)
686 script.Print("Unpacking patches...")
687 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700688
Doug Zongker05d3dea2009-06-22 11:32:31 -0700689 device_specific.IncrementalOTA_VerifyEnd()
690
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700691 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700692
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700693 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700694 script.Print("Erasing user data...")
695 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700696
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700697 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700698 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
699 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700700 if i not in target_data] +
701 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700702
Doug Zongker881dd402009-09-20 14:03:55 -0700703 script.ShowProgress(0.8, 0)
704 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
705 if updating_boot:
706 total_patch_size += target_boot.size
707 so_far = 0
708
709 script.Print("Patching system files...")
710 for fn, tf, sf, size in patch_list:
711 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
712 sf.sha1, "/tmp/patchtmp/"+fn+".p")
713 so_far += tf.size
714 script.SetProgress(so_far / total_patch_size)
715
Doug Zongkereef39442009-04-02 12:14:19 -0700716 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700717 # Produce the boot image by applying a patch to the current
718 # contents of the boot partition, and write it back to the
719 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700720 script.Print("Patching boot image...")
721 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
722 % (source_boot.size, source_boot.sha1,
723 target_boot.size, target_boot.sha1),
724 "-",
725 target_boot.size, target_boot.sha1,
726 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700727 so_far += target_boot.size
728 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700729 print "boot image changed; including."
730 else:
731 print "boot image unchanged; skipping."
732
733 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700734 # Is it better to generate recovery as a patch from the current
735 # boot image, or from the previous recovery image? For large
736 # updates with significant kernel changes, probably the former.
737 # For small updates where the kernel hasn't changed, almost
738 # certainly the latter. We pick the first option. Future
739 # complicated schemes may let us effectively use both.
740 #
741 # A wacky possibility: as long as there is room in the boot
742 # partition, include the binaries and image files from recovery in
743 # the boot image (though not in the ramdisk) so they can be used
744 # as fodder for constructing the recovery image.
745 recovery_sh_item = MakeRecoveryPatch(output_zip,
746 target_recovery, target_boot)
747 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700748 else:
749 print "recovery image unchanged; skipping."
750
Doug Zongker881dd402009-09-20 14:03:55 -0700751 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700752
753 target_symlinks = CopySystemFiles(target_zip, None)
754
755 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700756 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700757 Item.GetMetadata()
758 if updating_recovery:
759 recovery_sh_item.uid = 0
760 recovery_sh_item.gid = 0
761 recovery_sh_item.mode = 0544
762 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700763
764 # Note that this call will mess up the tree of Items, so make sure
765 # we're done with it.
766 source_symlinks = CopySystemFiles(source_zip, None)
767 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
768
769 # Delete all the symlinks in source that aren't in target. This
770 # needs to happen before verbatim files are unpacked, in case a
771 # symlink in the source is replaced by a real file in the target.
772 to_delete = []
773 for dest, link in source_symlinks:
774 if link not in target_symlinks_d:
775 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700776 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700777
778 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700779 script.Print("Unpacking new files...")
780 script.UnpackPackageDir("system", "/system")
781
Doug Zongker05d3dea2009-06-22 11:32:31 -0700782 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700783
784 # Create all the symlinks that don't already exist, or point to
785 # somewhere different than what we want. Delete each symlink before
786 # creating it, since the 'symlink' command won't overwrite.
787 to_create = []
788 for dest, link in target_symlinks:
789 if link in source_symlinks_d:
790 if dest != source_symlinks_d[link]:
791 to_create.append((dest, link))
792 else:
793 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700794 script.DeleteFiles([i[1] for i in to_create])
795 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700796
797 # Now that the symlinks are created, we can set all the
798 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700799 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700800
Doug Zongker881dd402009-09-20 14:03:55 -0700801 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700802 device_specific.IncrementalOTA_InstallEnd()
803
Doug Zongker1c390a22009-05-14 19:06:36 -0700804 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700805 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700806
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700807 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700808
809
810def main(argv):
811
812 def option_handler(o, a):
813 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700814 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700815 elif o in ("-k", "--package_key"):
816 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700817 elif o in ("-i", "--incremental_from"):
818 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700819 elif o in ("-w", "--wipe_user_data"):
820 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700821 elif o in ("-n", "--no_prereq"):
822 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700823 elif o in ("-e", "--extra_script"):
824 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700825 elif o in ("-m", "--script_mode"):
826 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700827 elif o in ("--worker_threads"):
828 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700829 else:
830 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700831 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700832
833 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700834 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700835 extra_long_opts=["board_config=",
836 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700837 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700838 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700839 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700840 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700841 "script_mode=",
842 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700843 extra_option_handler=option_handler)
844
845 if len(args) != 2:
846 common.Usage(__doc__)
847 sys.exit(1)
848
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700849 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
850 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
851
Doug Zongker1c390a22009-05-14 19:06:36 -0700852 if OPTIONS.extra_script is not None:
853 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
854
Doug Zongkereef39442009-04-02 12:14:19 -0700855 print "unzipping target target-files..."
856 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700857
858 common.LoadMaxSizes()
859 if not OPTIONS.max_image_size:
860 print
861 print " WARNING: Failed to load max image sizes; will not enforce"
862 print " image size limits."
863 print
864
Doug Zongkereef39442009-04-02 12:14:19 -0700865 OPTIONS.target_tmp = OPTIONS.input_tmp
866 input_zip = zipfile.ZipFile(args[0], "r")
867 if OPTIONS.package_key:
868 temp_zip_file = tempfile.NamedTemporaryFile()
869 output_zip = zipfile.ZipFile(temp_zip_file, "w",
870 compression=zipfile.ZIP_DEFLATED)
871 else:
872 output_zip = zipfile.ZipFile(args[1], "w",
873 compression=zipfile.ZIP_DEFLATED)
874
875 if OPTIONS.incremental_source is None:
876 WriteFullOTAPackage(input_zip, output_zip)
877 else:
878 print "unzipping source target-files..."
879 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
880 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
881 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
882
883 output_zip.close()
884 if OPTIONS.package_key:
885 SignOutput(temp_zip_file.name, args[1])
886 temp_zip_file.close()
887
888 common.Cleanup()
889
890 print "done."
891
892
893if __name__ == '__main__':
894 try:
895 main(sys.argv[1:])
896 except common.ExternalError, e:
897 print
898 print " ERROR: %s" % (e,)
899 print
900 sys.exit(1)