blob: e71969686799302d03c6a0025b62bdf9906c9350 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Given a target-files zipfile, produces an OTA package that installs
19that build. An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage: ota_from_target_files [flags] input_target_files output_ota_package
23
24 -b (--board_config) <file>
Doug Zongkerfdd8e692009-08-03 17:27:48 -070025 Deprecated.
Doug Zongkereef39442009-04-02 12:14:19 -070026
27 -k (--package_key) <key>
28 Key to use to sign the package (default is
29 "build/target/product/security/testkey").
30
31 -i (--incremental_from) <file>
32 Generate an incremental OTA using the given target-files zip as
33 the starting build.
34
Doug Zongkerdbfaae52009-04-21 17:12:54 -070035 -w (--wipe_user_data)
36 Generate an OTA package that will wipe the user data partition
37 when installed.
38
Doug Zongker962069c2009-04-23 11:41:58 -070039 -n (--no_prereq)
40 Omit the timestamp prereq check normally included at the top of
41 the build scripts (used for developer OTA packages which
42 legitimately need to go back and forth).
43
Doug Zongker1c390a22009-05-14 19:06:36 -070044 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
46
Doug Zongkerc494d7c2009-06-18 08:43:44 -070047 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
50
Doug Zongkereef39442009-04-02 12:14:19 -070051"""
52
53import sys
54
55if sys.hexversion < 0x02040000:
56 print >> sys.stderr, "Python 2.4 or newer is required."
57 sys.exit(1)
58
59import copy
Doug Zongkerc18736b2009-09-30 09:20:32 -070060import errno
Doug Zongkereef39442009-04-02 12:14:19 -070061import os
62import re
63import sha
64import subprocess
65import tempfile
Doug Zongker761e6422009-09-25 10:45:39 -070066import threading
Doug Zongkereef39442009-04-02 12:14:19 -070067import time
68import zipfile
69
70import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070071import amend_generator
72import edify_generator
Doug Zongker03061472009-07-13 18:36:37 -070073import both_generator
Doug Zongkereef39442009-04-02 12:14:19 -070074
75OPTIONS = common.OPTIONS
76OPTIONS.package_key = "build/target/product/security/testkey"
77OPTIONS.incremental_source = None
78OPTIONS.require_verbatim = set()
79OPTIONS.prohibit_verbatim = set(("system/build.prop",))
80OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070081OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070082OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070083OPTIONS.extra_script = None
Doug Zongkerc494d7c2009-06-18 08:43:44 -070084OPTIONS.script_mode = 'auto'
Doug Zongker761e6422009-09-25 10:45:39 -070085OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070086
87def MostPopularKey(d, default):
88 """Given a dict, return the key corresponding to the largest
89 value. Returns 'default' if the dict is empty."""
90 x = [(v, k) for (k, v) in d.iteritems()]
91 if not x: return default
92 x.sort()
93 return x[-1][1]
94
95
96def IsSymlink(info):
97 """Return true if the zipfile.ZipInfo object passed in represents a
98 symlink."""
99 return (info.external_attr >> 16) == 0120777
100
101
102
103class Item:
104 """Items represent the metadata (user, group, mode) of files and
105 directories in the system image."""
106 ITEMS = {}
107 def __init__(self, name, dir=False):
108 self.name = name
109 self.uid = None
110 self.gid = None
111 self.mode = None
112 self.dir = dir
113
114 if name:
115 self.parent = Item.Get(os.path.dirname(name), dir=True)
116 self.parent.children.append(self)
117 else:
118 self.parent = None
119 if dir:
120 self.children = []
121
122 def Dump(self, indent=0):
123 if self.uid is not None:
124 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
125 else:
126 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
127 if self.dir:
128 print "%s%s" % (" "*indent, self.descendants)
129 print "%s%s" % (" "*indent, self.best_subtree)
130 for i in self.children:
131 i.Dump(indent=indent+1)
132
133 @classmethod
134 def Get(cls, name, dir=False):
135 if name not in cls.ITEMS:
136 cls.ITEMS[name] = Item(name, dir=dir)
137 return cls.ITEMS[name]
138
139 @classmethod
140 def GetMetadata(cls):
141 """Run the external 'fs_config' program to determine the desired
142 uid, gid, and mode for every Item object."""
143 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
144 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
145 suffix = { False: "", True: "/" }
146 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
147 for i in cls.ITEMS.itervalues() if i.name])
148 output, error = p.communicate(input)
149 assert not error
150
151 for line in output.split("\n"):
152 if not line: continue
153 name, uid, gid, mode = line.split()
154 i = cls.ITEMS[name]
155 i.uid = int(uid)
156 i.gid = int(gid)
157 i.mode = int(mode, 8)
158 if i.dir:
159 i.children.sort(key=lambda i: i.name)
160
161 def CountChildMetadata(self):
162 """Count up the (uid, gid, mode) tuples for all children and
163 determine the best strategy for using set_perm_recursive and
164 set_perm to correctly chown/chmod all the files to their desired
165 values. Recursively calls itself for all descendants.
166
167 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
168 all descendants of this node. (dmode or fmode may be None.) Also
169 sets the best_subtree of each directory Item to the (uid, gid,
170 dmode, fmode) tuple that will match the most descendants of that
171 Item.
172 """
173
174 assert self.dir
175 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
176 for i in self.children:
177 if i.dir:
178 for k, v in i.CountChildMetadata().iteritems():
179 d[k] = d.get(k, 0) + v
180 else:
181 k = (i.uid, i.gid, None, i.mode)
182 d[k] = d.get(k, 0) + 1
183
184 # Find the (uid, gid, dmode, fmode) tuple that matches the most
185 # descendants.
186
187 # First, find the (uid, gid) pair that matches the most
188 # descendants.
189 ug = {}
190 for (uid, gid, _, _), count in d.iteritems():
191 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
192 ug = MostPopularKey(ug, (0, 0))
193
194 # Now find the dmode and fmode that match the most descendants
195 # with that (uid, gid), and choose those.
196 best_dmode = (0, 0755)
197 best_fmode = (0, 0644)
198 for k, count in d.iteritems():
199 if k[:2] != ug: continue
200 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
201 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
202 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
203
204 return d
205
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700206 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700207 """Append set_perm/set_perm_recursive commands to 'script' to
208 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700209 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700210
211 self.CountChildMetadata()
212
213 def recurse(item, current):
214 # current is the (uid, gid, dmode, fmode) tuple that the current
215 # item (and all its children) have already been set to. We only
216 # need to issue set_perm/set_perm_recursive commands if we're
217 # supposed to be something different.
218 if item.dir:
219 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700220 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700221 current = item.best_subtree
222
223 if item.uid != current[0] or item.gid != current[1] or \
224 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700225 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700226
227 for i in item.children:
228 recurse(i, current)
229 else:
230 if item.uid != current[0] or item.gid != current[1] or \
231 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700232 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700233
234 recurse(self, (-1, -1, -1, -1))
235
236
237def CopySystemFiles(input_zip, output_zip=None,
238 substitute=None):
239 """Copies files underneath system/ in the input zip to the output
240 zip. Populates the Item class with their metadata, and returns a
241 list of symlinks. output_zip may be None, in which case the copy is
242 skipped (but the other side effects still happen). substitute is an
243 optional dict of {output filename: contents} to be output instead of
244 certain input files.
245 """
246
247 symlinks = []
248
249 for info in input_zip.infolist():
250 if info.filename.startswith("SYSTEM/"):
251 basefilename = info.filename[7:]
252 if IsSymlink(info):
253 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700254 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700255 else:
256 info2 = copy.copy(info)
257 fn = info2.filename = "system/" + basefilename
258 if substitute and fn in substitute and substitute[fn] is None:
259 continue
260 if output_zip is not None:
261 if substitute and fn in substitute:
262 data = substitute[fn]
263 else:
264 data = input_zip.read(info.filename)
265 output_zip.writestr(info2, data)
266 if fn.endswith("/"):
267 Item.Get(fn[:-1], dir=True)
268 else:
269 Item.Get(fn, dir=False)
270
271 symlinks.sort()
272 return symlinks
273
274
Doug Zongkereef39442009-04-02 12:14:19 -0700275def SignOutput(temp_zip_name, output_zip_name):
276 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
277 pw = key_passwords[OPTIONS.package_key]
278
Doug Zongker951495f2009-08-14 12:44:19 -0700279 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
280 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700281
282
Doug Zongkereef39442009-04-02 12:14:19 -0700283def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700284 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700285 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700286
Doug Zongkereef39442009-04-02 12:14:19 -0700287
Doug Zongker73ef8252009-07-23 15:12:53 -0700288def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
289 """Generate a binary patch that creates the recovery image starting
290 with the boot image. (Most of the space in these images is just the
291 kernel, which is identical for the two, so the resulting patch
292 should be efficient.) Add it to the output zip, along with a shell
293 script that is run from init.rc on first boot to actually do the
294 patching and install the new recovery image.
295
296 recovery_img and boot_img should be File objects for the
297 corresponding images.
298
299 Returns an Item for the shell script, which must be made
300 executable.
301 """
302
Doug Zongker761e6422009-09-25 10:45:39 -0700303 d = Difference(recovery_img, boot_img)
304 _, _, patch = d.ComputePatch()
Doug Zongker73ef8252009-07-23 15:12:53 -0700305 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
306 Item.Get("system/recovery-from-boot.p", dir=False)
307
308 # Images with different content will have a different first page, so
309 # we check to see if this recovery has already been installed by
310 # testing just the first 2k.
311 HEADER_SIZE = 2048
312 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
313 sh = """#!/system/bin/sh
314if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
315 log -t recovery "Installing new recovery image"
316 applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
317else
318 log -t recovery "Recovery image already installed"
319fi
320""" % { 'boot_size': boot_img.size,
321 'boot_sha1': boot_img.sha1,
322 'header_size': HEADER_SIZE,
323 'header_sha1': header_sha1,
324 'recovery_size': recovery_img.size,
325 'recovery_sha1': recovery_img.sha1 }
326 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
327 return Item.Get("system/etc/install-recovery.sh", dir=False)
328
329
Doug Zongkereef39442009-04-02 12:14:19 -0700330def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700331 if OPTIONS.script_mode == "auto":
332 script = both_generator.BothGenerator(2)
333 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700334 script = amend_generator.AmendGenerator()
335 else:
336 # TODO: how to determine this? We don't know what version it will
337 # be installed on top of. For now, we expect the API just won't
338 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700339 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Doug Zongker05d3dea2009-06-22 11:32:31 -0700341 device_specific = common.DeviceSpecificParams(
342 input_zip=input_zip,
343 output_zip=output_zip,
344 script=script,
345 input_tmp=OPTIONS.input_tmp)
346
Doug Zongker962069c2009-04-23 11:41:58 -0700347 if not OPTIONS.omit_prereq:
348 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700349 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700350
351 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700352 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700353
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700354 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700355
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700356 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700357 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700358
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700359 script.FormatPartition("system")
360 script.Mount("MTD", "system", "/system")
361 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700362
363 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700364 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700365
Doug Zongker73ef8252009-07-23 15:12:53 -0700366 boot_img = File("boot.img", common.BuildBootableImage(
367 os.path.join(OPTIONS.input_tmp, "BOOT")))
368 recovery_img = File("recovery.img", common.BuildBootableImage(
369 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
370 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Doug Zongker73ef8252009-07-23 15:12:53 -0700372 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700373
Doug Zongker73ef8252009-07-23 15:12:53 -0700374 # GetMetadata uses the data in android_filesystem_config.h to assign
375 # the uid/gid/mode of all files. We want to override that for the
376 # recovery patching shell script to make it executable.
377 i.uid = 0
378 i.gid = 0
379 i.mode = 0544
380 Item.Get("system").SetPermissions(script)
381
382 common.CheckSize(boot_img.data, "boot.img")
383 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700384 script.ShowProgress(0.2, 0)
385
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700386 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700387 script.WriteRawImage("boot", "boot.img")
388
389 script.ShowProgress(0.1, 0)
390 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700391
Doug Zongker1c390a22009-05-14 19:06:36 -0700392 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700393 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700394
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700395 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700396
397
398class File(object):
399 def __init__(self, name, data):
400 self.name = name
401 self.data = data
402 self.size = len(data)
403 self.sha1 = sha.sha(data).hexdigest()
404
405 def WriteToTemp(self):
406 t = tempfile.NamedTemporaryFile()
407 t.write(self.data)
408 t.flush()
409 return t
410
411 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700412 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700413
414
415def LoadSystemFiles(z):
416 """Load all the files from SYSTEM/... in a given target-files
417 ZipFile, and return a dict of {filename: File object}."""
418 out = {}
419 for info in z.infolist():
420 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
421 fn = "system/" + info.filename[7:]
422 data = z.read(info.filename)
423 out[fn] = File(fn, data)
424 return out
425
426
Doug Zongker761e6422009-09-25 10:45:39 -0700427DIFF_PROGRAM_BY_EXT = {
428 ".gz" : "imgdiff",
429 ".zip" : ["imgdiff", "-z"],
430 ".jar" : ["imgdiff", "-z"],
431 ".apk" : ["imgdiff", "-z"],
432 ".img" : "imgdiff",
433 }
Doug Zongkereef39442009-04-02 12:14:19 -0700434
Doug Zongkereef39442009-04-02 12:14:19 -0700435
Doug Zongker761e6422009-09-25 10:45:39 -0700436class Difference(object):
437 def __init__(self, tf, sf):
438 self.tf = tf
439 self.sf = sf
440 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700441
Doug Zongker761e6422009-09-25 10:45:39 -0700442 def ComputePatch(self):
443 """Compute the patch (as a string of data) needed to turn sf into
444 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700445
Doug Zongker761e6422009-09-25 10:45:39 -0700446 tf = self.tf
447 sf = self.sf
448
449 ext = os.path.splitext(tf.name)[1]
450 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
451
452 ttemp = tf.WriteToTemp()
453 stemp = sf.WriteToTemp()
454
455 ext = os.path.splitext(tf.name)[1]
456
457 try:
458 ptemp = tempfile.NamedTemporaryFile()
459 if isinstance(diff_program, list):
460 cmd = copy.copy(diff_program)
461 else:
462 cmd = [diff_program]
463 cmd.append(stemp.name)
464 cmd.append(ttemp.name)
465 cmd.append(ptemp.name)
466 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
467 _, err = p.communicate()
468 if err or p.returncode != 0:
469 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
470 return None
471 diff = ptemp.read()
472 finally:
473 ptemp.close()
474 stemp.close()
475 ttemp.close()
476
477 self.patch = diff
478 return self.tf, self.sf, self.patch
479
480
481 def GetPatch(self):
482 """Return a tuple (target_file, source_file, patch_data).
483 patch_data may be None if ComputePatch hasn't been called, or if
484 computing the patch failed."""
485 return self.tf, self.sf, self.patch
486
487
488def ComputeDifferences(diffs):
489 """Call ComputePatch on all the Difference objects in 'diffs'."""
490 print len(diffs), "diffs to compute"
491
492 # Do the largest files first, to try and reduce the long-pole effect.
493 by_size = [(i.tf.size, i) for i in diffs]
494 by_size.sort(reverse=True)
495 by_size = [i[1] for i in by_size]
496
497 lock = threading.Lock()
498 diff_iter = iter(by_size) # accessed under lock
499
500 def worker():
501 try:
502 lock.acquire()
503 for d in diff_iter:
504 lock.release()
505 start = time.time()
506 d.ComputePatch()
507 dur = time.time() - start
508 lock.acquire()
509
510 tf, sf, patch = d.GetPatch()
511 if sf.name == tf.name:
512 name = tf.name
513 else:
514 name = "%s (%s)" % (tf.name, sf.name)
515 if patch is None:
516 print "patching failed! %s" % (name,)
517 else:
518 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
519 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
520 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700521 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700522 print e
523 raise
524
525 # start worker threads; wait for them all to finish.
526 threads = [threading.Thread(target=worker)
527 for i in range(OPTIONS.worker_threads)]
528 for th in threads:
529 th.start()
530 while threads:
531 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700532
533
534def GetBuildProp(property, z):
535 """Return the fingerprint of the build of a given target-files
536 ZipFile object."""
537 bp = z.read("SYSTEM/build.prop")
538 if not property:
539 return bp
540 m = re.search(re.escape(property) + r"=(.*)\n", bp)
541 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700542 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700543 return m.group(1).strip()
544
545
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700546def GetRecoveryAPIVersion(zip):
547 """Returns the version of the recovery API. Version 0 is the older
548 amend code (no separate binary)."""
549 try:
550 version = zip.read("META/recovery-api-version.txt")
551 return int(version)
552 except KeyError:
553 try:
554 # version one didn't have the recovery-api-version.txt file, but
555 # it did include an updater binary.
556 zip.getinfo("OTA/bin/updater")
557 return 1
558 except KeyError:
559 return 0
560
Doug Zongker15604b82009-09-01 17:53:34 -0700561
Doug Zongkereef39442009-04-02 12:14:19 -0700562def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700563 source_version = GetRecoveryAPIVersion(source_zip)
564
565 if OPTIONS.script_mode == 'amend':
566 script = amend_generator.AmendGenerator()
567 elif OPTIONS.script_mode == 'edify':
568 if source_version == 0:
569 print ("WARNING: generating edify script for a source that "
570 "can't install it.")
571 script = edify_generator.EdifyGenerator(source_version)
572 elif OPTIONS.script_mode == 'auto':
573 if source_version > 0:
574 script = edify_generator.EdifyGenerator(source_version)
575 else:
576 script = amend_generator.AmendGenerator()
577 else:
578 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700579
Doug Zongker05d3dea2009-06-22 11:32:31 -0700580 device_specific = common.DeviceSpecificParams(
581 source_zip=source_zip,
582 target_zip=target_zip,
583 output_zip=output_zip,
584 script=script)
585
Doug Zongkereef39442009-04-02 12:14:19 -0700586 print "Loading target..."
587 target_data = LoadSystemFiles(target_zip)
588 print "Loading source..."
589 source_data = LoadSystemFiles(source_zip)
590
591 verbatim_targets = []
592 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700593 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700594 largest_source_size = 0
595 for fn in sorted(target_data.keys()):
596 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700597 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700598 sf = source_data.get(fn, None)
599
600 if sf is None or fn in OPTIONS.require_verbatim:
601 # This file should be included verbatim
602 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700603 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700604 print "send", fn, "verbatim"
605 tf.AddToZip(output_zip)
606 verbatim_targets.append((fn, tf.size))
607 elif tf.sha1 != sf.sha1:
608 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700609 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700610 else:
611 # Target file identical to source.
612 pass
613
Doug Zongker761e6422009-09-25 10:45:39 -0700614 ComputeDifferences(diffs)
615
616 for diff in diffs:
617 tf, sf, d = diff.GetPatch()
618 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
619 # patch is almost as big as the file; don't bother patching
620 tf.AddToZip(output_zip)
621 verbatim_targets.append((tf.name, tf.size))
622 else:
623 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
624 patch_list.append((tf.name, tf, sf, tf.size))
625 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700626
627 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
628 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
629
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700630 script.Mount("MTD", "system", "/system")
631 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700632
Doug Zongker5da317e2009-06-02 13:38:17 -0700633 source_boot = File("/tmp/boot.img",
634 common.BuildBootableImage(
635 os.path.join(OPTIONS.source_tmp, "BOOT")))
636 target_boot = File("/tmp/boot.img",
637 common.BuildBootableImage(
638 os.path.join(OPTIONS.target_tmp, "BOOT")))
639 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700640
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700641 source_recovery = File("system/recovery.img",
642 common.BuildBootableImage(
643 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
644 target_recovery = File("system/recovery.img",
645 common.BuildBootableImage(
646 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
647 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700648
Doug Zongker881dd402009-09-20 14:03:55 -0700649 # Here's how we divide up the progress bar:
650 # 0.1 for verifying the start state (PatchCheck calls)
651 # 0.8 for applying patches (ApplyPatch calls)
652 # 0.1 for unpacking verbatim files, symlinking, and doing the
653 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700654
655 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700656 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700657
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700658 script.Print("Verifying current system...")
659
Doug Zongker881dd402009-09-20 14:03:55 -0700660 script.ShowProgress(0.1, 0)
661 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
662 if updating_boot:
663 total_verify_size += source_boot.size
664 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700665
Doug Zongker881dd402009-09-20 14:03:55 -0700666 for fn, tf, sf, size in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700667 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700668 so_far += sf.size
669 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700670
Doug Zongker5da317e2009-06-02 13:38:17 -0700671 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700672 d = Difference(target_boot, source_boot)
673 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700674 print "boot target: %d source: %d diff: %d" % (
675 target_boot.size, source_boot.size, len(d))
676
Doug Zongker048e7ca2009-06-15 14:31:53 -0700677 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700678
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
680 (source_boot.size, source_boot.sha1,
681 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700682 so_far += source_boot.size
683 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700684
685 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700686 script.CacheFreeSpaceCheck(largest_source_size)
687 script.Print("Unpacking patches...")
688 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700689
Doug Zongker05d3dea2009-06-22 11:32:31 -0700690 device_specific.IncrementalOTA_VerifyEnd()
691
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700692 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700693
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700694 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700695 script.Print("Erasing user data...")
696 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700697
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700698 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700699 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
700 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700701 if i not in target_data] +
702 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700703
Doug Zongker881dd402009-09-20 14:03:55 -0700704 script.ShowProgress(0.8, 0)
705 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
706 if updating_boot:
707 total_patch_size += target_boot.size
708 so_far = 0
709
710 script.Print("Patching system files...")
711 for fn, tf, sf, size in patch_list:
712 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
713 sf.sha1, "/tmp/patchtmp/"+fn+".p")
714 so_far += tf.size
715 script.SetProgress(so_far / total_patch_size)
716
Doug Zongkereef39442009-04-02 12:14:19 -0700717 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700718 # Produce the boot image by applying a patch to the current
719 # contents of the boot partition, and write it back to the
720 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700721 script.Print("Patching boot image...")
722 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
723 % (source_boot.size, source_boot.sha1,
724 target_boot.size, target_boot.sha1),
725 "-",
726 target_boot.size, target_boot.sha1,
727 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700728 so_far += target_boot.size
729 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700730 print "boot image changed; including."
731 else:
732 print "boot image unchanged; skipping."
733
734 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700735 # Is it better to generate recovery as a patch from the current
736 # boot image, or from the previous recovery image? For large
737 # updates with significant kernel changes, probably the former.
738 # For small updates where the kernel hasn't changed, almost
739 # certainly the latter. We pick the first option. Future
740 # complicated schemes may let us effectively use both.
741 #
742 # A wacky possibility: as long as there is room in the boot
743 # partition, include the binaries and image files from recovery in
744 # the boot image (though not in the ramdisk) so they can be used
745 # as fodder for constructing the recovery image.
746 recovery_sh_item = MakeRecoveryPatch(output_zip,
747 target_recovery, target_boot)
748 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700749 else:
750 print "recovery image unchanged; skipping."
751
Doug Zongker881dd402009-09-20 14:03:55 -0700752 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700753
754 target_symlinks = CopySystemFiles(target_zip, None)
755
756 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700757 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700758 Item.GetMetadata()
759 if updating_recovery:
760 recovery_sh_item.uid = 0
761 recovery_sh_item.gid = 0
762 recovery_sh_item.mode = 0544
763 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700764
765 # Note that this call will mess up the tree of Items, so make sure
766 # we're done with it.
767 source_symlinks = CopySystemFiles(source_zip, None)
768 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
769
770 # Delete all the symlinks in source that aren't in target. This
771 # needs to happen before verbatim files are unpacked, in case a
772 # symlink in the source is replaced by a real file in the target.
773 to_delete = []
774 for dest, link in source_symlinks:
775 if link not in target_symlinks_d:
776 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700777 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700778
779 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700780 script.Print("Unpacking new files...")
781 script.UnpackPackageDir("system", "/system")
782
Doug Zongker05d3dea2009-06-22 11:32:31 -0700783 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700784
785 # Create all the symlinks that don't already exist, or point to
786 # somewhere different than what we want. Delete each symlink before
787 # creating it, since the 'symlink' command won't overwrite.
788 to_create = []
789 for dest, link in target_symlinks:
790 if link in source_symlinks_d:
791 if dest != source_symlinks_d[link]:
792 to_create.append((dest, link))
793 else:
794 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700795 script.DeleteFiles([i[1] for i in to_create])
796 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700797
798 # Now that the symlinks are created, we can set all the
799 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700800 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700801
Doug Zongker881dd402009-09-20 14:03:55 -0700802 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700803 device_specific.IncrementalOTA_InstallEnd()
804
Doug Zongker1c390a22009-05-14 19:06:36 -0700805 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700806 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700807
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700808 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700809
810
811def main(argv):
812
813 def option_handler(o, a):
814 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700815 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700816 elif o in ("-k", "--package_key"):
817 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700818 elif o in ("-i", "--incremental_from"):
819 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700820 elif o in ("-w", "--wipe_user_data"):
821 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700822 elif o in ("-n", "--no_prereq"):
823 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700824 elif o in ("-e", "--extra_script"):
825 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700826 elif o in ("-m", "--script_mode"):
827 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700828 elif o in ("--worker_threads"):
829 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700830 else:
831 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700832 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700833
834 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700835 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700836 extra_long_opts=["board_config=",
837 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700838 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700839 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700840 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700841 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700842 "script_mode=",
843 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700844 extra_option_handler=option_handler)
845
846 if len(args) != 2:
847 common.Usage(__doc__)
848 sys.exit(1)
849
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700850 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
851 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
852
Doug Zongker1c390a22009-05-14 19:06:36 -0700853 if OPTIONS.extra_script is not None:
854 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
855
Doug Zongkereef39442009-04-02 12:14:19 -0700856 print "unzipping target target-files..."
857 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700858
Doug Zongkerc18736b2009-09-30 09:20:32 -0700859 if OPTIONS.device_specific is None:
860 # look for the device-specific tools extension location in the input
861 try:
862 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
863 ds = f.read().strip()
864 f.close()
865 if ds:
866 ds = os.path.normpath(ds)
867 print "using device-specific extensions in", ds
868 OPTIONS.device_specific = ds
869 except IOError, e:
870 if e.errno == errno.ENOENT:
871 # nothing specified in the file
872 pass
873 else:
874 raise
875
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700876 common.LoadMaxSizes()
877 if not OPTIONS.max_image_size:
878 print
879 print " WARNING: Failed to load max image sizes; will not enforce"
880 print " image size limits."
881 print
882
Doug Zongkereef39442009-04-02 12:14:19 -0700883 OPTIONS.target_tmp = OPTIONS.input_tmp
884 input_zip = zipfile.ZipFile(args[0], "r")
885 if OPTIONS.package_key:
886 temp_zip_file = tempfile.NamedTemporaryFile()
887 output_zip = zipfile.ZipFile(temp_zip_file, "w",
888 compression=zipfile.ZIP_DEFLATED)
889 else:
890 output_zip = zipfile.ZipFile(args[1], "w",
891 compression=zipfile.ZIP_DEFLATED)
892
893 if OPTIONS.incremental_source is None:
894 WriteFullOTAPackage(input_zip, output_zip)
895 else:
896 print "unzipping source target-files..."
897 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
898 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
899 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
900
901 output_zip.close()
902 if OPTIONS.package_key:
903 SignOutput(temp_zip_file.name, args[1])
904 temp_zip_file.close()
905
906 common.Cleanup()
907
908 print "done."
909
910
911if __name__ == '__main__':
912 try:
913 main(sys.argv[1:])
914 except common.ExternalError, e:
915 print
916 print " ERROR: %s" % (e,)
917 print
918 sys.exit(1)