blob: f2404b315390ba779c7b630176ed27dcd372b960 [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>
25 Specifies a BoardConfig.mk file containing image max sizes
26 against which the generated image files are checked.
27
28 -k (--package_key) <key>
29 Key to use to sign the package (default is
30 "build/target/product/security/testkey").
31
32 -i (--incremental_from) <file>
33 Generate an incremental OTA using the given target-files zip as
34 the starting build.
35
Doug Zongkerdbfaae52009-04-21 17:12:54 -070036 -w (--wipe_user_data)
37 Generate an OTA package that will wipe the user data partition
38 when installed.
39
Doug Zongker962069c2009-04-23 11:41:58 -070040 -n (--no_prereq)
41 Omit the timestamp prereq check normally included at the top of
42 the build scripts (used for developer OTA packages which
43 legitimately need to go back and forth).
44
Doug Zongker1c390a22009-05-14 19:06:36 -070045 -e (--extra_script) <file>
46 Insert the contents of file at the end of the update script.
47
Doug Zongkerc494d7c2009-06-18 08:43:44 -070048 -m (--script_mode) <mode>
49 Specify 'amend' or 'edify' scripts, or 'auto' to pick
50 automatically (this is the default).
51
Doug Zongkereef39442009-04-02 12:14:19 -070052"""
53
54import sys
55
56if sys.hexversion < 0x02040000:
57 print >> sys.stderr, "Python 2.4 or newer is required."
58 sys.exit(1)
59
60import copy
61import os
62import re
63import sha
64import subprocess
65import tempfile
66import 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 Zongkereef39442009-04-02 12:14:19 -070084
85def MostPopularKey(d, default):
86 """Given a dict, return the key corresponding to the largest
87 value. Returns 'default' if the dict is empty."""
88 x = [(v, k) for (k, v) in d.iteritems()]
89 if not x: return default
90 x.sort()
91 return x[-1][1]
92
93
94def IsSymlink(info):
95 """Return true if the zipfile.ZipInfo object passed in represents a
96 symlink."""
97 return (info.external_attr >> 16) == 0120777
98
99
100
101class Item:
102 """Items represent the metadata (user, group, mode) of files and
103 directories in the system image."""
104 ITEMS = {}
105 def __init__(self, name, dir=False):
106 self.name = name
107 self.uid = None
108 self.gid = None
109 self.mode = None
110 self.dir = dir
111
112 if name:
113 self.parent = Item.Get(os.path.dirname(name), dir=True)
114 self.parent.children.append(self)
115 else:
116 self.parent = None
117 if dir:
118 self.children = []
119
120 def Dump(self, indent=0):
121 if self.uid is not None:
122 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
123 else:
124 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
125 if self.dir:
126 print "%s%s" % (" "*indent, self.descendants)
127 print "%s%s" % (" "*indent, self.best_subtree)
128 for i in self.children:
129 i.Dump(indent=indent+1)
130
131 @classmethod
132 def Get(cls, name, dir=False):
133 if name not in cls.ITEMS:
134 cls.ITEMS[name] = Item(name, dir=dir)
135 return cls.ITEMS[name]
136
137 @classmethod
138 def GetMetadata(cls):
139 """Run the external 'fs_config' program to determine the desired
140 uid, gid, and mode for every Item object."""
141 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
142 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
143 suffix = { False: "", True: "/" }
144 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
145 for i in cls.ITEMS.itervalues() if i.name])
146 output, error = p.communicate(input)
147 assert not error
148
149 for line in output.split("\n"):
150 if not line: continue
151 name, uid, gid, mode = line.split()
152 i = cls.ITEMS[name]
153 i.uid = int(uid)
154 i.gid = int(gid)
155 i.mode = int(mode, 8)
156 if i.dir:
157 i.children.sort(key=lambda i: i.name)
158
159 def CountChildMetadata(self):
160 """Count up the (uid, gid, mode) tuples for all children and
161 determine the best strategy for using set_perm_recursive and
162 set_perm to correctly chown/chmod all the files to their desired
163 values. Recursively calls itself for all descendants.
164
165 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
166 all descendants of this node. (dmode or fmode may be None.) Also
167 sets the best_subtree of each directory Item to the (uid, gid,
168 dmode, fmode) tuple that will match the most descendants of that
169 Item.
170 """
171
172 assert self.dir
173 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
174 for i in self.children:
175 if i.dir:
176 for k, v in i.CountChildMetadata().iteritems():
177 d[k] = d.get(k, 0) + v
178 else:
179 k = (i.uid, i.gid, None, i.mode)
180 d[k] = d.get(k, 0) + 1
181
182 # Find the (uid, gid, dmode, fmode) tuple that matches the most
183 # descendants.
184
185 # First, find the (uid, gid) pair that matches the most
186 # descendants.
187 ug = {}
188 for (uid, gid, _, _), count in d.iteritems():
189 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
190 ug = MostPopularKey(ug, (0, 0))
191
192 # Now find the dmode and fmode that match the most descendants
193 # with that (uid, gid), and choose those.
194 best_dmode = (0, 0755)
195 best_fmode = (0, 0644)
196 for k, count in d.iteritems():
197 if k[:2] != ug: continue
198 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
199 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
200 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
201
202 return d
203
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700204 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700205 """Append set_perm/set_perm_recursive commands to 'script' to
206 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700207 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700208
209 self.CountChildMetadata()
210
211 def recurse(item, current):
212 # current is the (uid, gid, dmode, fmode) tuple that the current
213 # item (and all its children) have already been set to. We only
214 # need to issue set_perm/set_perm_recursive commands if we're
215 # supposed to be something different.
216 if item.dir:
217 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700218 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700219 current = item.best_subtree
220
221 if item.uid != current[0] or item.gid != current[1] or \
222 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700223 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700224
225 for i in item.children:
226 recurse(i, current)
227 else:
228 if item.uid != current[0] or item.gid != current[1] or \
229 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700230 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700231
232 recurse(self, (-1, -1, -1, -1))
233
234
235def CopySystemFiles(input_zip, output_zip=None,
236 substitute=None):
237 """Copies files underneath system/ in the input zip to the output
238 zip. Populates the Item class with their metadata, and returns a
239 list of symlinks. output_zip may be None, in which case the copy is
240 skipped (but the other side effects still happen). substitute is an
241 optional dict of {output filename: contents} to be output instead of
242 certain input files.
243 """
244
245 symlinks = []
246
247 for info in input_zip.infolist():
248 if info.filename.startswith("SYSTEM/"):
249 basefilename = info.filename[7:]
250 if IsSymlink(info):
251 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700252 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700253 else:
254 info2 = copy.copy(info)
255 fn = info2.filename = "system/" + basefilename
256 if substitute and fn in substitute and substitute[fn] is None:
257 continue
258 if output_zip is not None:
259 if substitute and fn in substitute:
260 data = substitute[fn]
261 else:
262 data = input_zip.read(info.filename)
263 output_zip.writestr(info2, data)
264 if fn.endswith("/"):
265 Item.Get(fn[:-1], dir=True)
266 else:
267 Item.Get(fn, dir=False)
268
269 symlinks.sort()
270 return symlinks
271
272
Doug Zongkereef39442009-04-02 12:14:19 -0700273def SignOutput(temp_zip_name, output_zip_name):
274 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
275 pw = key_passwords[OPTIONS.package_key]
276
277 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
278
279
Doug Zongkereef39442009-04-02 12:14:19 -0700280def FixPermissions(script):
281 Item.GetMetadata()
282 root = Item.Get("system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700283 root.SetPermissions(script)
Doug Zongkereef39442009-04-02 12:14:19 -0700284
Doug Zongkereef39442009-04-02 12:14:19 -0700285
286def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700287 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700288 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700289
290 info = input_zip.read("OTA/android-info.txt")
291 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
Doug Zongker171f1cd2009-06-15 22:36:37 -0700292 if m:
293 bootloaders = m.group(1).split("|")
Doug Zongkerac4920a2009-06-18 13:42:20 -0700294 script.AssertSomeBootloader(*bootloaders)
Doug Zongkereef39442009-04-02 12:14:19 -0700295
296
297def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongker03061472009-07-13 18:36:37 -0700298 if OPTIONS.script_mode == "auto":
299 script = both_generator.BothGenerator(2)
300 elif OPTIONS.script_mode == "amend":
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700301 script = amend_generator.AmendGenerator()
302 else:
303 # TODO: how to determine this? We don't know what version it will
304 # be installed on top of. For now, we expect the API just won't
305 # change very often.
Doug Zongker03061472009-07-13 18:36:37 -0700306 script = edify_generator.EdifyGenerator(2)
Doug Zongkereef39442009-04-02 12:14:19 -0700307
Doug Zongker05d3dea2009-06-22 11:32:31 -0700308 device_specific = common.DeviceSpecificParams(
309 input_zip=input_zip,
310 output_zip=output_zip,
311 script=script,
312 input_tmp=OPTIONS.input_tmp)
313
Doug Zongker962069c2009-04-23 11:41:58 -0700314 if not OPTIONS.omit_prereq:
315 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700316 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700317
318 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700319 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700320
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700321 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700322
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700323 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700324 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700325
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700326 script.FormatPartition("system")
327 script.Mount("MTD", "system", "/system")
328 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700329
330 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700331 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700332
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700333 if common.BuildAndAddBootableImage(
334 os.path.join(OPTIONS.input_tmp, "RECOVERY"),
335 "system/recovery.img", output_zip):
336 Item.Get("system/recovery.img", dir=False)
Doug Zongkereef39442009-04-02 12:14:19 -0700337
338 FixPermissions(script)
339
340 common.AddBoot(output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700341
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700342 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700343 script.WriteRawImage("boot", "boot.img")
344
345 script.ShowProgress(0.1, 0)
346 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700347
Doug Zongker1c390a22009-05-14 19:06:36 -0700348 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700349 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700350
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700351 script.AddToZip(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700352
353
354class File(object):
355 def __init__(self, name, data):
356 self.name = name
357 self.data = data
358 self.size = len(data)
359 self.sha1 = sha.sha(data).hexdigest()
360
361 def WriteToTemp(self):
362 t = tempfile.NamedTemporaryFile()
363 t.write(self.data)
364 t.flush()
365 return t
366
367 def AddToZip(self, z):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700368 common.ZipWriteStr(z, self.name, self.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700369
370
371def LoadSystemFiles(z):
372 """Load all the files from SYSTEM/... in a given target-files
373 ZipFile, and return a dict of {filename: File object}."""
374 out = {}
375 for info in z.infolist():
376 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
377 fn = "system/" + info.filename[7:]
378 data = z.read(info.filename)
379 out[fn] = File(fn, data)
380 return out
381
382
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700383def Difference(tf, sf, diff_program):
384 """Return the patch (as a string of data) needed to turn sf into tf.
385 diff_program is the name of an external program (or list, if
386 additional arguments are desired) to run to generate the diff.
387 """
Doug Zongkereef39442009-04-02 12:14:19 -0700388
389 ttemp = tf.WriteToTemp()
390 stemp = sf.WriteToTemp()
391
392 ext = os.path.splitext(tf.name)[1]
393
394 try:
395 ptemp = tempfile.NamedTemporaryFile()
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700396 if isinstance(diff_program, list):
397 cmd = copy.copy(diff_program)
398 else:
399 cmd = [diff_program]
400 cmd.append(stemp.name)
401 cmd.append(ttemp.name)
402 cmd.append(ptemp.name)
403 p = common.Run(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700404 _, err = p.communicate()
Doug Zongker5da317e2009-06-02 13:38:17 -0700405 if err or p.returncode != 0:
406 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
407 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700408 diff = ptemp.read()
Doug Zongkereef39442009-04-02 12:14:19 -0700409 finally:
Doug Zongker5da317e2009-06-02 13:38:17 -0700410 ptemp.close()
Doug Zongkereef39442009-04-02 12:14:19 -0700411 stemp.close()
412 ttemp.close()
413
414 return diff
415
416
417def GetBuildProp(property, z):
418 """Return the fingerprint of the build of a given target-files
419 ZipFile object."""
420 bp = z.read("SYSTEM/build.prop")
421 if not property:
422 return bp
423 m = re.search(re.escape(property) + r"=(.*)\n", bp)
424 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700425 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700426 return m.group(1).strip()
427
428
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700429def GetRecoveryAPIVersion(zip):
430 """Returns the version of the recovery API. Version 0 is the older
431 amend code (no separate binary)."""
432 try:
433 version = zip.read("META/recovery-api-version.txt")
434 return int(version)
435 except KeyError:
436 try:
437 # version one didn't have the recovery-api-version.txt file, but
438 # it did include an updater binary.
439 zip.getinfo("OTA/bin/updater")
440 return 1
441 except KeyError:
442 return 0
443
Doug Zongkereef39442009-04-02 12:14:19 -0700444def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700445 source_version = GetRecoveryAPIVersion(source_zip)
446
447 if OPTIONS.script_mode == 'amend':
448 script = amend_generator.AmendGenerator()
449 elif OPTIONS.script_mode == 'edify':
450 if source_version == 0:
451 print ("WARNING: generating edify script for a source that "
452 "can't install it.")
453 script = edify_generator.EdifyGenerator(source_version)
454 elif OPTIONS.script_mode == 'auto':
455 if source_version > 0:
456 script = edify_generator.EdifyGenerator(source_version)
457 else:
458 script = amend_generator.AmendGenerator()
459 else:
460 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
Doug Zongkereef39442009-04-02 12:14:19 -0700461
Doug Zongker05d3dea2009-06-22 11:32:31 -0700462 device_specific = common.DeviceSpecificParams(
463 source_zip=source_zip,
464 target_zip=target_zip,
465 output_zip=output_zip,
466 script=script)
467
Doug Zongkereef39442009-04-02 12:14:19 -0700468 print "Loading target..."
469 target_data = LoadSystemFiles(target_zip)
470 print "Loading source..."
471 source_data = LoadSystemFiles(source_zip)
472
473 verbatim_targets = []
474 patch_list = []
475 largest_source_size = 0
476 for fn in sorted(target_data.keys()):
477 tf = target_data[fn]
478 sf = source_data.get(fn, None)
479
480 if sf is None or fn in OPTIONS.require_verbatim:
481 # This file should be included verbatim
482 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700483 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700484 print "send", fn, "verbatim"
485 tf.AddToZip(output_zip)
486 verbatim_targets.append((fn, tf.size))
487 elif tf.sha1 != sf.sha1:
488 # File is different; consider sending as a patch
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700489 diff_method = "bsdiff"
490 if tf.name.endswith(".gz"):
491 diff_method = "imgdiff"
492 d = Difference(tf, sf, diff_method)
Doug Zongker5da317e2009-06-02 13:38:17 -0700493 if d is not None:
494 print fn, tf.size, len(d), (float(len(d)) / tf.size)
495 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
Doug Zongkereef39442009-04-02 12:14:19 -0700496 # patch is almost as big as the file; don't bother patching
497 tf.AddToZip(output_zip)
498 verbatim_targets.append((fn, tf.size))
499 else:
Doug Zongker048e7ca2009-06-15 14:31:53 -0700500 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
Doug Zongkereef39442009-04-02 12:14:19 -0700501 patch_list.append((fn, tf, sf, tf.size))
502 largest_source_size = max(largest_source_size, sf.size)
503 else:
504 # Target file identical to source.
505 pass
506
507 total_verbatim_size = sum([i[1] for i in verbatim_targets])
508 total_patched_size = sum([i[3] for i in patch_list])
509
510 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
511 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
512
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700513 script.Mount("MTD", "system", "/system")
514 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700515
Doug Zongker5da317e2009-06-02 13:38:17 -0700516 source_boot = File("/tmp/boot.img",
517 common.BuildBootableImage(
518 os.path.join(OPTIONS.source_tmp, "BOOT")))
519 target_boot = File("/tmp/boot.img",
520 common.BuildBootableImage(
521 os.path.join(OPTIONS.target_tmp, "BOOT")))
522 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700523
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700524 source_recovery = File("system/recovery.img",
525 common.BuildBootableImage(
526 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
527 target_recovery = File("system/recovery.img",
528 common.BuildBootableImage(
529 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
530 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700531
Doug Zongker05d3dea2009-06-22 11:32:31 -0700532 # We reserve the last 0.3 of the progress bar for the
533 # device-specific IncrementalOTA_InstallEnd() call at the end, which
534 # will typically install a radio image.
535 progress_bar_total = 0.7
Doug Zongkereef39442009-04-02 12:14:19 -0700536 if updating_boot:
537 progress_bar_total -= 0.1
Doug Zongkereef39442009-04-02 12:14:19 -0700538
539 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700540 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700541
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700542 script.Print("Verifying current system...")
543
Doug Zongkereef39442009-04-02 12:14:19 -0700544 pb_verify = progress_bar_total * 0.3 * \
545 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700546 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700547
548 for i, (fn, tf, sf, size) in enumerate(patch_list):
549 if i % 5 == 0:
550 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700551 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
552
553 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongkereef39442009-04-02 12:14:19 -0700554
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700555 if updating_recovery:
556 d = Difference(target_recovery, source_recovery, "imgdiff")
557 print "recovery target: %d source: %d diff: %d" % (
558 target_recovery.size, source_recovery.size, len(d))
559
Doug Zongker048e7ca2009-06-15 14:31:53 -0700560 common.ZipWriteStr(output_zip, "patch/recovery.img.p", d)
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700561
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700562 script.PatchCheck("MTD:recovery:%d:%s:%d:%s" %
563 (source_recovery.size, source_recovery.sha1,
564 target_recovery.size, target_recovery.sha1))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700565
Doug Zongker5da317e2009-06-02 13:38:17 -0700566 if updating_boot:
567 d = Difference(target_boot, source_boot, "imgdiff")
568 print "boot target: %d source: %d diff: %d" % (
569 target_boot.size, source_boot.size, len(d))
570
Doug Zongker048e7ca2009-06-15 14:31:53 -0700571 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700572
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700573 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
574 (source_boot.size, source_boot.sha1,
575 target_boot.size, target_boot.sha1))
Doug Zongker5da317e2009-06-02 13:38:17 -0700576
577 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700578 script.CacheFreeSpaceCheck(largest_source_size)
579 script.Print("Unpacking patches...")
580 script.UnpackPackageDir("patch", "/tmp/patchtmp")
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700581
Doug Zongker05d3dea2009-06-22 11:32:31 -0700582 device_specific.IncrementalOTA_VerifyEnd()
583
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700584 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700585
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700586 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700587 script.Print("Erasing user data...")
588 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700589
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700590 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700591 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
592 ["/"+i for i in sorted(source_data)
593 if i not in target_data])
Doug Zongkereef39442009-04-02 12:14:19 -0700594
595 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700596 # Produce the boot image by applying a patch to the current
597 # contents of the boot partition, and write it back to the
598 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700599 script.Print("Patching boot image...")
600 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
601 % (source_boot.size, source_boot.sha1,
602 target_boot.size, target_boot.sha1),
603 "-",
604 target_boot.size, target_boot.sha1,
605 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700606 print "boot image changed; including."
607 else:
608 print "boot image unchanged; skipping."
609
610 if updating_recovery:
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700611 # Produce /system/recovery.img by applying a patch to the current
612 # contents of the recovery partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700613 script.Print("Patching recovery image...")
614 script.ApplyPatch("MTD:recovery:%d:%s:%d:%s"
615 % (source_recovery.size, source_recovery.sha1,
616 target_recovery.size, target_recovery.sha1),
617 "/system/recovery.img",
618 target_recovery.size, target_recovery.sha1,
619 source_recovery.sha1, "/tmp/patchtmp/recovery.img.p")
Doug Zongkereef39442009-04-02 12:14:19 -0700620 print "recovery image changed; including."
621 else:
622 print "recovery image unchanged; skipping."
623
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700624 script.Print("Patching system files...")
Doug Zongkereef39442009-04-02 12:14:19 -0700625 pb_apply = progress_bar_total * 0.7 * \
626 (total_patched_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700627 float(total_patched_size+total_verbatim_size+1))
Doug Zongkereef39442009-04-02 12:14:19 -0700628 for i, (fn, tf, sf, size) in enumerate(patch_list):
629 if i % 5 == 0:
630 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700631 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
632 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
633 sf.sha1, "/tmp/patchtmp/"+fn+".p")
Doug Zongkereef39442009-04-02 12:14:19 -0700634
635 target_symlinks = CopySystemFiles(target_zip, None)
636
637 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700638 temp_script = script.MakeTemporary()
Doug Zongkereef39442009-04-02 12:14:19 -0700639 FixPermissions(temp_script)
640
641 # Note that this call will mess up the tree of Items, so make sure
642 # we're done with it.
643 source_symlinks = CopySystemFiles(source_zip, None)
644 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
645
646 # Delete all the symlinks in source that aren't in target. This
647 # needs to happen before verbatim files are unpacked, in case a
648 # symlink in the source is replaced by a real file in the target.
649 to_delete = []
650 for dest, link in source_symlinks:
651 if link not in target_symlinks_d:
652 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700653 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700654
655 if verbatim_targets:
656 pb_verbatim = progress_bar_total * \
657 (total_verbatim_size /
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700658 float(total_patched_size+total_verbatim_size+1))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700659 script.ShowProgress(pb_verbatim, 5)
660 script.Print("Unpacking new files...")
661 script.UnpackPackageDir("system", "/system")
662
Doug Zongker05d3dea2009-06-22 11:32:31 -0700663 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700664
665 # Create all the symlinks that don't already exist, or point to
666 # somewhere different than what we want. Delete each symlink before
667 # creating it, since the 'symlink' command won't overwrite.
668 to_create = []
669 for dest, link in target_symlinks:
670 if link in source_symlinks_d:
671 if dest != source_symlinks_d[link]:
672 to_create.append((dest, link))
673 else:
674 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700675 script.DeleteFiles([i[1] for i in to_create])
676 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700677
678 # Now that the symlinks are created, we can set all the
679 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700680 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700681
Doug Zongker05d3dea2009-06-22 11:32:31 -0700682 # Write the radio image, if necessary.
683 script.ShowProgress(0.3, 10)
684 device_specific.IncrementalOTA_InstallEnd()
685
Doug Zongker1c390a22009-05-14 19:06:36 -0700686 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700687 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700688
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700689 script.AddToZip(target_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700690
691
692def main(argv):
693
694 def option_handler(o, a):
695 if o in ("-b", "--board_config"):
696 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700697 elif o in ("-k", "--package_key"):
698 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700699 elif o in ("-i", "--incremental_from"):
700 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700701 elif o in ("-w", "--wipe_user_data"):
702 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700703 elif o in ("-n", "--no_prereq"):
704 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700705 elif o in ("-e", "--extra_script"):
706 OPTIONS.extra_script = a
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700707 elif o in ("-m", "--script_mode"):
708 OPTIONS.script_mode = a
Doug Zongkereef39442009-04-02 12:14:19 -0700709 else:
710 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700711 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700712
713 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700714 extra_opts="b:k:i:d:wne:m:",
Doug Zongkereef39442009-04-02 12:14:19 -0700715 extra_long_opts=["board_config=",
716 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700717 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700718 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700719 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700720 "extra_script=",
721 "script_mode="],
Doug Zongkereef39442009-04-02 12:14:19 -0700722 extra_option_handler=option_handler)
723
724 if len(args) != 2:
725 common.Usage(__doc__)
726 sys.exit(1)
727
728 if not OPTIONS.max_image_size:
729 print
730 print " WARNING: No board config specified; will not check image"
731 print " sizes against limits. Use -b to make sure the generated"
732 print " images don't exceed partition sizes."
733 print
734
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700735 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
736 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
737
Doug Zongker1c390a22009-05-14 19:06:36 -0700738 if OPTIONS.extra_script is not None:
739 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
740
Doug Zongkereef39442009-04-02 12:14:19 -0700741 print "unzipping target target-files..."
742 OPTIONS.input_tmp = common.UnzipTemp(args[0])
743 OPTIONS.target_tmp = OPTIONS.input_tmp
744 input_zip = zipfile.ZipFile(args[0], "r")
745 if OPTIONS.package_key:
746 temp_zip_file = tempfile.NamedTemporaryFile()
747 output_zip = zipfile.ZipFile(temp_zip_file, "w",
748 compression=zipfile.ZIP_DEFLATED)
749 else:
750 output_zip = zipfile.ZipFile(args[1], "w",
751 compression=zipfile.ZIP_DEFLATED)
752
753 if OPTIONS.incremental_source is None:
754 WriteFullOTAPackage(input_zip, output_zip)
755 else:
756 print "unzipping source target-files..."
757 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
758 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
759 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
760
761 output_zip.close()
762 if OPTIONS.package_key:
763 SignOutput(temp_zip_file.name, args[1])
764 temp_zip_file.close()
765
766 common.Cleanup()
767
768 print "done."
769
770
771if __name__ == '__main__':
772 try:
773 main(sys.argv[1:])
774 except common.ExternalError, e:
775 print
776 print " ERROR: %s" % (e,)
777 print
778 sys.exit(1)