blob: f129da1921439d059a540bf9a8c3d57cd036f591 [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 Zongkercfd7db62009-10-07 11:35:53 -0700305 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700306 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 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700326 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700327 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")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700361 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700362 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700363
364 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700365 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700366
Doug Zongker73ef8252009-07-23 15:12:53 -0700367 boot_img = File("boot.img", common.BuildBootableImage(
368 os.path.join(OPTIONS.input_tmp, "BOOT")))
369 recovery_img = File("recovery.img", common.BuildBootableImage(
370 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
371 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700372
Doug Zongker73ef8252009-07-23 15:12:53 -0700373 Item.GetMetadata()
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Doug Zongker73ef8252009-07-23 15:12:53 -0700375 # GetMetadata uses the data in android_filesystem_config.h to assign
376 # the uid/gid/mode of all files. We want to override that for the
377 # recovery patching shell script to make it executable.
378 i.uid = 0
379 i.gid = 0
380 i.mode = 0544
381 Item.Get("system").SetPermissions(script)
382
383 common.CheckSize(boot_img.data, "boot.img")
384 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700385 script.ShowProgress(0.2, 0)
386
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700388 script.WriteRawImage("boot", "boot.img")
389
390 script.ShowProgress(0.1, 0)
391 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700392
Doug Zongker1c390a22009-05-14 19:06:36 -0700393 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700394 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700395
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700396 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700397
398
399class File(object):
400 def __init__(self, name, data):
401 self.name = name
402 self.data = data
403 self.size = len(data)
404 self.sha1 = sha.sha(data).hexdigest()
405
406 def WriteToTemp(self):
407 t = tempfile.NamedTemporaryFile()
408 t.write(self.data)
409 t.flush()
410 return t
411
412 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700413 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700414
415
416def LoadSystemFiles(z):
417 """Load all the files from SYSTEM/... in a given target-files
418 ZipFile, and return a dict of {filename: File object}."""
419 out = {}
420 for info in z.infolist():
421 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
422 fn = "system/" + info.filename[7:]
423 data = z.read(info.filename)
424 out[fn] = File(fn, data)
425 return out
426
427
Doug Zongker761e6422009-09-25 10:45:39 -0700428DIFF_PROGRAM_BY_EXT = {
429 ".gz" : "imgdiff",
430 ".zip" : ["imgdiff", "-z"],
431 ".jar" : ["imgdiff", "-z"],
432 ".apk" : ["imgdiff", "-z"],
433 ".img" : "imgdiff",
434 }
Doug Zongkereef39442009-04-02 12:14:19 -0700435
Doug Zongkereef39442009-04-02 12:14:19 -0700436
Doug Zongker761e6422009-09-25 10:45:39 -0700437class Difference(object):
438 def __init__(self, tf, sf):
439 self.tf = tf
440 self.sf = sf
441 self.patch = None
Doug Zongkereef39442009-04-02 12:14:19 -0700442
Doug Zongker761e6422009-09-25 10:45:39 -0700443 def ComputePatch(self):
444 """Compute the patch (as a string of data) needed to turn sf into
445 tf. Returns the same tuple as GetPatch()."""
Doug Zongkereef39442009-04-02 12:14:19 -0700446
Doug Zongker761e6422009-09-25 10:45:39 -0700447 tf = self.tf
448 sf = self.sf
449
450 ext = os.path.splitext(tf.name)[1]
451 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
452
453 ttemp = tf.WriteToTemp()
454 stemp = sf.WriteToTemp()
455
456 ext = os.path.splitext(tf.name)[1]
457
458 try:
459 ptemp = tempfile.NamedTemporaryFile()
460 if isinstance(diff_program, list):
461 cmd = copy.copy(diff_program)
462 else:
463 cmd = [diff_program]
464 cmd.append(stemp.name)
465 cmd.append(ttemp.name)
466 cmd.append(ptemp.name)
467 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
468 _, err = p.communicate()
469 if err or p.returncode != 0:
470 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
471 return None
472 diff = ptemp.read()
473 finally:
474 ptemp.close()
475 stemp.close()
476 ttemp.close()
477
478 self.patch = diff
479 return self.tf, self.sf, self.patch
480
481
482 def GetPatch(self):
483 """Return a tuple (target_file, source_file, patch_data).
484 patch_data may be None if ComputePatch hasn't been called, or if
485 computing the patch failed."""
486 return self.tf, self.sf, self.patch
487
488
489def ComputeDifferences(diffs):
490 """Call ComputePatch on all the Difference objects in 'diffs'."""
491 print len(diffs), "diffs to compute"
492
493 # Do the largest files first, to try and reduce the long-pole effect.
494 by_size = [(i.tf.size, i) for i in diffs]
495 by_size.sort(reverse=True)
496 by_size = [i[1] for i in by_size]
497
498 lock = threading.Lock()
499 diff_iter = iter(by_size) # accessed under lock
500
501 def worker():
502 try:
503 lock.acquire()
504 for d in diff_iter:
505 lock.release()
506 start = time.time()
507 d.ComputePatch()
508 dur = time.time() - start
509 lock.acquire()
510
511 tf, sf, patch = d.GetPatch()
512 if sf.name == tf.name:
513 name = tf.name
514 else:
515 name = "%s (%s)" % (tf.name, sf.name)
516 if patch is None:
517 print "patching failed! %s" % (name,)
518 else:
519 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
520 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
521 lock.release()
Doug Zongker481c4e62009-09-28 10:07:13 -0700522 except Exception, e:
Doug Zongker761e6422009-09-25 10:45:39 -0700523 print e
524 raise
525
526 # start worker threads; wait for them all to finish.
527 threads = [threading.Thread(target=worker)
528 for i in range(OPTIONS.worker_threads)]
529 for th in threads:
530 th.start()
531 while threads:
532 threads.pop().join()
Doug Zongkereef39442009-04-02 12:14:19 -0700533
534
535def GetBuildProp(property, z):
536 """Return the fingerprint of the build of a given target-files
537 ZipFile object."""
538 bp = z.read("SYSTEM/build.prop")
539 if not property:
540 return bp
541 m = re.search(re.escape(property) + r"=(.*)\n", bp)
542 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700543 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700544 return m.group(1).strip()
545
546
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700547def GetRecoveryAPIVersion(zip):
548 """Returns the version of the recovery API. Version 0 is the older
549 amend code (no separate binary)."""
550 try:
551 version = zip.read("META/recovery-api-version.txt")
552 return int(version)
553 except KeyError:
554 try:
555 # version one didn't have the recovery-api-version.txt file, but
556 # it did include an updater binary.
557 zip.getinfo("OTA/bin/updater")
558 return 1
559 except KeyError:
560 return 0
561
Doug Zongker15604b82009-09-01 17:53:34 -0700562
Doug Zongkereef39442009-04-02 12:14:19 -0700563def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700564 source_version = GetRecoveryAPIVersion(source_zip)
565
566 if OPTIONS.script_mode == 'amend':
567 script = amend_generator.AmendGenerator()
568 elif OPTIONS.script_mode == 'edify':
569 if source_version == 0:
570 print ("WARNING: generating edify script for a source that "
571 "can't install it.")
572 script = edify_generator.EdifyGenerator(source_version)
573 elif OPTIONS.script_mode == 'auto':
574 if source_version > 0:
575 script = edify_generator.EdifyGenerator(source_version)
576 else:
577 script = amend_generator.AmendGenerator()
578 else:
579 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Doug Zongker05d3dea2009-06-22 11:32:31 -0700581 device_specific = common.DeviceSpecificParams(
582 source_zip=source_zip,
583 target_zip=target_zip,
584 output_zip=output_zip,
585 script=script)
586
Doug Zongkereef39442009-04-02 12:14:19 -0700587 print "Loading target..."
588 target_data = LoadSystemFiles(target_zip)
589 print "Loading source..."
590 source_data = LoadSystemFiles(source_zip)
591
592 verbatim_targets = []
593 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700594 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700595 largest_source_size = 0
596 for fn in sorted(target_data.keys()):
597 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700598 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700599 sf = source_data.get(fn, None)
600
601 if sf is None or fn in OPTIONS.require_verbatim:
602 # This file should be included verbatim
603 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700604 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700605 print "send", fn, "verbatim"
606 tf.AddToZip(output_zip)
607 verbatim_targets.append((fn, tf.size))
608 elif tf.sha1 != sf.sha1:
609 # File is different; consider sending as a patch
Doug Zongker761e6422009-09-25 10:45:39 -0700610 diffs.append(Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700611 else:
612 # Target file identical to source.
613 pass
614
Doug Zongker761e6422009-09-25 10:45:39 -0700615 ComputeDifferences(diffs)
616
617 for diff in diffs:
618 tf, sf, d = diff.GetPatch()
619 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
620 # patch is almost as big as the file; don't bother patching
621 tf.AddToZip(output_zip)
622 verbatim_targets.append((tf.name, tf.size))
623 else:
624 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
625 patch_list.append((tf.name, tf, sf, tf.size))
626 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700627
628 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
629 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
630
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700631 script.Mount("MTD", "system", "/system")
632 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700633
Doug Zongker5da317e2009-06-02 13:38:17 -0700634 source_boot = File("/tmp/boot.img",
635 common.BuildBootableImage(
636 os.path.join(OPTIONS.source_tmp, "BOOT")))
637 target_boot = File("/tmp/boot.img",
638 common.BuildBootableImage(
639 os.path.join(OPTIONS.target_tmp, "BOOT")))
640 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700641
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700642 source_recovery = File("system/recovery.img",
643 common.BuildBootableImage(
644 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
645 target_recovery = File("system/recovery.img",
646 common.BuildBootableImage(
647 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
648 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700649
Doug Zongker881dd402009-09-20 14:03:55 -0700650 # Here's how we divide up the progress bar:
651 # 0.1 for verifying the start state (PatchCheck calls)
652 # 0.8 for applying patches (ApplyPatch calls)
653 # 0.1 for unpacking verbatim files, symlinking, and doing the
654 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700655
656 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700657 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700658
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700659 script.Print("Verifying current system...")
660
Doug Zongker881dd402009-09-20 14:03:55 -0700661 script.ShowProgress(0.1, 0)
662 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
663 if updating_boot:
664 total_verify_size += source_boot.size
665 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700666
Doug Zongker881dd402009-09-20 14:03:55 -0700667 for fn, tf, sf, size in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700668 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700669 so_far += sf.size
670 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700671
Doug Zongker5da317e2009-06-02 13:38:17 -0700672 if updating_boot:
Doug Zongker761e6422009-09-25 10:45:39 -0700673 d = Difference(target_boot, source_boot)
674 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700675 print "boot target: %d source: %d diff: %d" % (
676 target_boot.size, source_boot.size, len(d))
677
Doug Zongker048e7ca2009-06-15 14:31:53 -0700678 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700679
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700680 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
681 (source_boot.size, source_boot.sha1,
682 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700683 so_far += source_boot.size
684 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700685
686 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700687 script.CacheFreeSpaceCheck(largest_source_size)
688 script.Print("Unpacking patches...")
689 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700690
Doug Zongker05d3dea2009-06-22 11:32:31 -0700691 device_specific.IncrementalOTA_VerifyEnd()
692
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700693 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700694
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700695 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700696 script.Print("Erasing user data...")
697 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700698
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700699 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700700 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
701 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700702 if i not in target_data] +
703 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700704
Doug Zongker881dd402009-09-20 14:03:55 -0700705 script.ShowProgress(0.8, 0)
706 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
707 if updating_boot:
708 total_patch_size += target_boot.size
709 so_far = 0
710
711 script.Print("Patching system files...")
712 for fn, tf, sf, size in patch_list:
713 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
714 sf.sha1, "/tmp/patchtmp/"+fn+".p")
715 so_far += tf.size
716 script.SetProgress(so_far / total_patch_size)
717
Doug Zongkereef39442009-04-02 12:14:19 -0700718 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700719 # Produce the boot image by applying a patch to the current
720 # contents of the boot partition, and write it back to the
721 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700722 script.Print("Patching boot image...")
723 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
724 % (source_boot.size, source_boot.sha1,
725 target_boot.size, target_boot.sha1),
726 "-",
727 target_boot.size, target_boot.sha1,
728 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700729 so_far += target_boot.size
730 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700731 print "boot image changed; including."
732 else:
733 print "boot image unchanged; skipping."
734
735 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700736 # Is it better to generate recovery as a patch from the current
737 # boot image, or from the previous recovery image? For large
738 # updates with significant kernel changes, probably the former.
739 # For small updates where the kernel hasn't changed, almost
740 # certainly the latter. We pick the first option. Future
741 # complicated schemes may let us effectively use both.
742 #
743 # A wacky possibility: as long as there is room in the boot
744 # partition, include the binaries and image files from recovery in
745 # the boot image (though not in the ramdisk) so they can be used
746 # as fodder for constructing the recovery image.
747 recovery_sh_item = MakeRecoveryPatch(output_zip,
748 target_recovery, target_boot)
Doug Zongkercfd7db62009-10-07 11:35:53 -0700749 script.UnpackPackageDir("recovery", "/system")
Doug Zongker73ef8252009-07-23 15:12:53 -0700750 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700751 else:
752 print "recovery image unchanged; skipping."
753
Doug Zongker881dd402009-09-20 14:03:55 -0700754 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700755
756 target_symlinks = CopySystemFiles(target_zip, None)
757
758 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700759 temp_script = script.MakeTemporary()
Doug Zongker73ef8252009-07-23 15:12:53 -0700760 Item.GetMetadata()
761 if updating_recovery:
762 recovery_sh_item.uid = 0
763 recovery_sh_item.gid = 0
764 recovery_sh_item.mode = 0544
765 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700766
767 # Note that this call will mess up the tree of Items, so make sure
768 # we're done with it.
769 source_symlinks = CopySystemFiles(source_zip, None)
770 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
771
772 # Delete all the symlinks in source that aren't in target. This
773 # needs to happen before verbatim files are unpacked, in case a
774 # symlink in the source is replaced by a real file in the target.
775 to_delete = []
776 for dest, link in source_symlinks:
777 if link not in target_symlinks_d:
778 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700779 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700780
781 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700782 script.Print("Unpacking new files...")
783 script.UnpackPackageDir("system", "/system")
784
Doug Zongker05d3dea2009-06-22 11:32:31 -0700785 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700786
787 # Create all the symlinks that don't already exist, or point to
788 # somewhere different than what we want. Delete each symlink before
789 # creating it, since the 'symlink' command won't overwrite.
790 to_create = []
791 for dest, link in target_symlinks:
792 if link in source_symlinks_d:
793 if dest != source_symlinks_d[link]:
794 to_create.append((dest, link))
795 else:
796 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700797 script.DeleteFiles([i[1] for i in to_create])
798 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700799
800 # Now that the symlinks are created, we can set all the
801 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700802 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700803
Doug Zongker881dd402009-09-20 14:03:55 -0700804 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700805 device_specific.IncrementalOTA_InstallEnd()
806
Doug Zongker1c390a22009-05-14 19:06:36 -0700807 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700808 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700809
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700810 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700811
812
813def main(argv):
814
815 def option_handler(o, a):
816 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700817 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700818 elif o in ("-k", "--package_key"):
819 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700820 elif o in ("-i", "--incremental_from"):
821 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700822 elif o in ("-w", "--wipe_user_data"):
823 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700824 elif o in ("-n", "--no_prereq"):
825 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700826 elif o in ("-e", "--extra_script"):
827 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700828 elif o in ("-m", "--script_mode"):
829 OPTIONS.script_mode = a
Doug Zongker761e6422009-09-25 10:45:39 -0700830 elif o in ("--worker_threads"):
831 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700832 else:
833 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700834 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700835
836 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700837 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700838 extra_long_opts=["board_config=",
839 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700840 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700841 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700842 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700843 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700844 "script_mode=",
845 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700846 extra_option_handler=option_handler)
847
848 if len(args) != 2:
849 common.Usage(__doc__)
850 sys.exit(1)
851
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700852 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
853 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
854
Doug Zongker1c390a22009-05-14 19:06:36 -0700855 if OPTIONS.extra_script is not None:
856 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
857
Doug Zongkereef39442009-04-02 12:14:19 -0700858 print "unzipping target target-files..."
859 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700860
Doug Zongkerc18736b2009-09-30 09:20:32 -0700861 if OPTIONS.device_specific is None:
862 # look for the device-specific tools extension location in the input
863 try:
864 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
865 ds = f.read().strip()
866 f.close()
867 if ds:
868 ds = os.path.normpath(ds)
869 print "using device-specific extensions in", ds
870 OPTIONS.device_specific = ds
871 except IOError, e:
872 if e.errno == errno.ENOENT:
873 # nothing specified in the file
874 pass
875 else:
876 raise
877
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700878 common.LoadMaxSizes()
879 if not OPTIONS.max_image_size:
880 print
881 print " WARNING: Failed to load max image sizes; will not enforce"
882 print " image size limits."
883 print
884
Doug Zongkereef39442009-04-02 12:14:19 -0700885 OPTIONS.target_tmp = OPTIONS.input_tmp
886 input_zip = zipfile.ZipFile(args[0], "r")
887 if OPTIONS.package_key:
888 temp_zip_file = tempfile.NamedTemporaryFile()
889 output_zip = zipfile.ZipFile(temp_zip_file, "w",
890 compression=zipfile.ZIP_DEFLATED)
891 else:
892 output_zip = zipfile.ZipFile(args[1], "w",
893 compression=zipfile.ZIP_DEFLATED)
894
895 if OPTIONS.incremental_source is None:
896 WriteFullOTAPackage(input_zip, output_zip)
897 else:
898 print "unzipping source target-files..."
899 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
900 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
901 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
902
903 output_zip.close()
904 if OPTIONS.package_key:
905 SignOutput(temp_zip_file.name, args[1])
906 temp_zip_file.close()
907
908 common.Cleanup()
909
910 print "done."
911
912
913if __name__ == '__main__':
914 try:
915 main(sys.argv[1:])
916 except common.ExternalError, e:
917 print
918 print " ERROR: %s" % (e,)
919 print
920 sys.exit(1)