blob: 89caa5c90fcf44f1eeb1a875c54611ee2466a1b5 [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 Zongkereef39442009-04-02 12:14:19 -070047"""
48
49import sys
50
51if sys.hexversion < 0x02040000:
52 print >> sys.stderr, "Python 2.4 or newer is required."
53 sys.exit(1)
54
55import copy
Doug Zongkerc18736b2009-09-30 09:20:32 -070056import errno
Doug Zongkereef39442009-04-02 12:14:19 -070057import os
58import re
59import sha
60import subprocess
61import tempfile
62import time
63import zipfile
64
65import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -070066import edify_generator
Doug Zongkereef39442009-04-02 12:14:19 -070067
68OPTIONS = common.OPTIONS
69OPTIONS.package_key = "build/target/product/security/testkey"
70OPTIONS.incremental_source = None
71OPTIONS.require_verbatim = set()
72OPTIONS.prohibit_verbatim = set(("system/build.prop",))
73OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070074OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070075OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070076OPTIONS.extra_script = None
Doug Zongker761e6422009-09-25 10:45:39 -070077OPTIONS.worker_threads = 3
Doug Zongkereef39442009-04-02 12:14:19 -070078
79def MostPopularKey(d, default):
80 """Given a dict, return the key corresponding to the largest
81 value. Returns 'default' if the dict is empty."""
82 x = [(v, k) for (k, v) in d.iteritems()]
83 if not x: return default
84 x.sort()
85 return x[-1][1]
86
87
88def IsSymlink(info):
89 """Return true if the zipfile.ZipInfo object passed in represents a
90 symlink."""
91 return (info.external_attr >> 16) == 0120777
92
93
94
95class Item:
96 """Items represent the metadata (user, group, mode) of files and
97 directories in the system image."""
98 ITEMS = {}
99 def __init__(self, name, dir=False):
100 self.name = name
101 self.uid = None
102 self.gid = None
103 self.mode = None
104 self.dir = dir
105
106 if name:
107 self.parent = Item.Get(os.path.dirname(name), dir=True)
108 self.parent.children.append(self)
109 else:
110 self.parent = None
111 if dir:
112 self.children = []
113
114 def Dump(self, indent=0):
115 if self.uid is not None:
116 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
117 else:
118 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
119 if self.dir:
120 print "%s%s" % (" "*indent, self.descendants)
121 print "%s%s" % (" "*indent, self.best_subtree)
122 for i in self.children:
123 i.Dump(indent=indent+1)
124
125 @classmethod
126 def Get(cls, name, dir=False):
127 if name not in cls.ITEMS:
128 cls.ITEMS[name] = Item(name, dir=dir)
129 return cls.ITEMS[name]
130
131 @classmethod
Doug Zongker283e2a12010-03-15 17:52:32 -0700132 def GetMetadata(cls, input_zip):
133
134 try:
135 # See if the target_files contains a record of what the uid,
136 # gid, and mode is supposed to be.
137 output = input_zip.read("META/filesystem_config.txt")
138 except KeyError:
139 # Run the external 'fs_config' program to determine the desired
140 # uid, gid, and mode for every Item object. Note this uses the
141 # one in the client now, which might not be the same as the one
142 # used when this target_files was built.
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])
Doug Zongker3475d362010-03-17 16:39:30 -0700148 output, error = p.communicate(input)
Doug Zongker283e2a12010-03-15 17:52:32 -0700149 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700150
151 for line in output.split("\n"):
152 if not line: continue
153 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700154 i = cls.ITEMS.get(name, None)
155 if i is not None:
156 i.uid = int(uid)
157 i.gid = int(gid)
158 i.mode = int(mode, 8)
159 if i.dir:
160 i.children.sort(key=lambda i: i.name)
161
162 # set metadata for the files generated by this script.
163 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
164 if i: i.uid, i.gid, i.mode = 0, 0, 0644
165 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
166 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700167
168 def CountChildMetadata(self):
169 """Count up the (uid, gid, mode) tuples for all children and
170 determine the best strategy for using set_perm_recursive and
171 set_perm to correctly chown/chmod all the files to their desired
172 values. Recursively calls itself for all descendants.
173
174 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
175 all descendants of this node. (dmode or fmode may be None.) Also
176 sets the best_subtree of each directory Item to the (uid, gid,
177 dmode, fmode) tuple that will match the most descendants of that
178 Item.
179 """
180
181 assert self.dir
182 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
183 for i in self.children:
184 if i.dir:
185 for k, v in i.CountChildMetadata().iteritems():
186 d[k] = d.get(k, 0) + v
187 else:
188 k = (i.uid, i.gid, None, i.mode)
189 d[k] = d.get(k, 0) + 1
190
191 # Find the (uid, gid, dmode, fmode) tuple that matches the most
192 # descendants.
193
194 # First, find the (uid, gid) pair that matches the most
195 # descendants.
196 ug = {}
197 for (uid, gid, _, _), count in d.iteritems():
198 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
199 ug = MostPopularKey(ug, (0, 0))
200
201 # Now find the dmode and fmode that match the most descendants
202 # with that (uid, gid), and choose those.
203 best_dmode = (0, 0755)
204 best_fmode = (0, 0644)
205 for k, count in d.iteritems():
206 if k[:2] != ug: continue
207 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
208 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
209 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
210
211 return d
212
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700213 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700214 """Append set_perm/set_perm_recursive commands to 'script' to
215 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700216 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700217
218 self.CountChildMetadata()
219
220 def recurse(item, current):
221 # current is the (uid, gid, dmode, fmode) tuple that the current
222 # item (and all its children) have already been set to. We only
223 # need to issue set_perm/set_perm_recursive commands if we're
224 # supposed to be something different.
225 if item.dir:
226 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700227 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700228 current = item.best_subtree
229
230 if item.uid != current[0] or item.gid != current[1] or \
231 item.mode != current[2]:
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 for i in item.children:
235 recurse(i, current)
236 else:
237 if item.uid != current[0] or item.gid != current[1] or \
238 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700239 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700240
241 recurse(self, (-1, -1, -1, -1))
242
243
244def CopySystemFiles(input_zip, output_zip=None,
245 substitute=None):
246 """Copies files underneath system/ in the input zip to the output
247 zip. Populates the Item class with their metadata, and returns a
248 list of symlinks. output_zip may be None, in which case the copy is
249 skipped (but the other side effects still happen). substitute is an
250 optional dict of {output filename: contents} to be output instead of
251 certain input files.
252 """
253
254 symlinks = []
255
256 for info in input_zip.infolist():
257 if info.filename.startswith("SYSTEM/"):
258 basefilename = info.filename[7:]
259 if IsSymlink(info):
260 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700261 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700262 else:
263 info2 = copy.copy(info)
264 fn = info2.filename = "system/" + basefilename
265 if substitute and fn in substitute and substitute[fn] is None:
266 continue
267 if output_zip is not None:
268 if substitute and fn in substitute:
269 data = substitute[fn]
270 else:
271 data = input_zip.read(info.filename)
272 output_zip.writestr(info2, data)
273 if fn.endswith("/"):
274 Item.Get(fn[:-1], dir=True)
275 else:
276 Item.Get(fn, dir=False)
277
278 symlinks.sort()
279 return symlinks
280
281
Doug Zongkereef39442009-04-02 12:14:19 -0700282def SignOutput(temp_zip_name, output_zip_name):
283 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
284 pw = key_passwords[OPTIONS.package_key]
285
Doug Zongker951495f2009-08-14 12:44:19 -0700286 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
287 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700288
289
Doug Zongkereef39442009-04-02 12:14:19 -0700290def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700291 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700292 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700293
Doug Zongkereef39442009-04-02 12:14:19 -0700294
Doug Zongker73ef8252009-07-23 15:12:53 -0700295def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
296 """Generate a binary patch that creates the recovery image starting
297 with the boot image. (Most of the space in these images is just the
298 kernel, which is identical for the two, so the resulting patch
299 should be efficient.) Add it to the output zip, along with a shell
300 script that is run from init.rc on first boot to actually do the
301 patching and install the new recovery image.
302
303 recovery_img and boot_img should be File objects for the
304 corresponding images.
305
306 Returns an Item for the shell script, which must be made
307 executable.
308 """
309
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700310 d = common.Difference(recovery_img, boot_img)
Doug Zongker761e6422009-09-25 10:45:39 -0700311 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700312 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700313 Item.Get("system/recovery-from-boot.p", dir=False)
314
315 # Images with different content will have a different first page, so
316 # we check to see if this recovery has already been installed by
317 # testing just the first 2k.
318 HEADER_SIZE = 2048
319 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
320 sh = """#!/system/bin/sh
321if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
322 log -t recovery "Installing new recovery image"
323 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
324else
325 log -t recovery "Recovery image already installed"
326fi
327""" % { 'boot_size': boot_img.size,
328 'boot_sha1': boot_img.sha1,
329 'header_size': HEADER_SIZE,
330 'header_sha1': header_sha1,
331 'recovery_size': recovery_img.size,
332 'recovery_sha1': recovery_img.sha1 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700333 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700334 return Item.Get("system/etc/install-recovery.sh", dir=False)
335
336
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700337def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc637db12010-04-21 14:08:44 -0700338 # TODO: how to determine this? We don't know what version it will
339 # be installed on top of. For now, we expect the API just won't
340 # change very often.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700341 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700342
Doug Zongker2ea21062010-04-28 16:05:21 -0700343 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
344 "pre-device": GetBuildProp("ro.product.device", input_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700345 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700346 }
347
Doug Zongker05d3dea2009-06-22 11:32:31 -0700348 device_specific = common.DeviceSpecificParams(
349 input_zip=input_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800350 input_version=GetRecoveryAPIVersion(input_zip),
Doug Zongker05d3dea2009-06-22 11:32:31 -0700351 output_zip=output_zip,
352 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700353 input_tmp=OPTIONS.input_tmp,
354 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700355
Doug Zongker962069c2009-04-23 11:41:58 -0700356 if not OPTIONS.omit_prereq:
357 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700358 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700359
360 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700361 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700362
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700363 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700365 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700366 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700367
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700368 script.FormatPartition("system")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700369 script.Mount("system", "/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700370 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700371 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700372
373 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700374 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700375
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700376 boot_img = common.File("boot.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700377 os.path.join(OPTIONS.input_tmp, "BOOT")))
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700378 recovery_img = common.File("recovery.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700379 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700380 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700381
Doug Zongker283e2a12010-03-15 17:52:32 -0700382 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700383 Item.Get("system").SetPermissions(script)
384
385 common.CheckSize(boot_img.data, "boot.img")
386 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.ShowProgress(0.2, 0)
388
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700389 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700390 script.WriteRawImage("boot", "boot.img")
391
392 script.ShowProgress(0.1, 0)
393 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700394
Doug Zongker1c390a22009-05-14 19:06:36 -0700395 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700396 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700397
Doug Zongker14833602010-02-02 13:12:04 -0800398 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700399 script.AddToZip(input_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700400 WriteMetadata(metadata, output_zip)
401
402
403def WriteMetadata(metadata, output_zip):
404 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
405 "".join(["%s=%s\n" % kv
406 for kv in sorted(metadata.iteritems())]))
Doug Zongkereef39442009-04-02 12:14:19 -0700407
408
Doug Zongkereef39442009-04-02 12:14:19 -0700409
410
411def LoadSystemFiles(z):
412 """Load all the files from SYSTEM/... in a given target-files
413 ZipFile, and return a dict of {filename: File object}."""
414 out = {}
415 for info in z.infolist():
416 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
417 fn = "system/" + info.filename[7:]
418 data = z.read(info.filename)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700419 out[fn] = common.File(fn, data)
Doug Zongkereef39442009-04-02 12:14:19 -0700420 return out
421
422
Doug Zongkereef39442009-04-02 12:14:19 -0700423def GetBuildProp(property, z):
424 """Return the fingerprint of the build of a given target-files
425 ZipFile object."""
426 bp = z.read("SYSTEM/build.prop")
427 if not property:
428 return bp
429 m = re.search(re.escape(property) + r"=(.*)\n", bp)
430 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700431 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700432 return m.group(1).strip()
433
434
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700435def GetRecoveryAPIVersion(zip):
436 """Returns the version of the recovery API. Version 0 is the older
437 amend code (no separate binary)."""
438 try:
439 version = zip.read("META/recovery-api-version.txt")
440 return int(version)
441 except KeyError:
442 try:
443 # version one didn't have the recovery-api-version.txt file, but
444 # it did include an updater binary.
445 zip.getinfo("OTA/bin/updater")
446 return 1
447 except KeyError:
448 return 0
449
Doug Zongker15604b82009-09-01 17:53:34 -0700450
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700451def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700452 source_version = GetRecoveryAPIVersion(source_zip)
Doug Zongker14833602010-02-02 13:12:04 -0800453 target_version = GetRecoveryAPIVersion(target_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700454
Doug Zongkerc637db12010-04-21 14:08:44 -0700455 if source_version == 0:
456 print ("WARNING: generating edify script for a source that "
457 "can't install it.")
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700458 script = edify_generator.EdifyGenerator(source_version, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700459
Doug Zongker2ea21062010-04-28 16:05:21 -0700460 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700461 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700462 }
463
Doug Zongker05d3dea2009-06-22 11:32:31 -0700464 device_specific = common.DeviceSpecificParams(
465 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800466 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700467 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800468 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700469 output_zip=output_zip,
Doug Zongker2ea21062010-04-28 16:05:21 -0700470 script=script,
471 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700472
Doug Zongkereef39442009-04-02 12:14:19 -0700473 print "Loading target..."
474 target_data = LoadSystemFiles(target_zip)
475 print "Loading source..."
476 source_data = LoadSystemFiles(source_zip)
477
478 verbatim_targets = []
479 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700480 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700481 largest_source_size = 0
482 for fn in sorted(target_data.keys()):
483 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700484 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700485 sf = source_data.get(fn, None)
486
487 if sf is None or fn in OPTIONS.require_verbatim:
488 # This file should be included verbatim
489 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700490 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700491 print "send", fn, "verbatim"
492 tf.AddToZip(output_zip)
493 verbatim_targets.append((fn, tf.size))
494 elif tf.sha1 != sf.sha1:
495 # File is different; consider sending as a patch
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700496 diffs.append(common.Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700497 else:
498 # Target file identical to source.
499 pass
500
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700501 common.ComputeDifferences(diffs)
Doug Zongker761e6422009-09-25 10:45:39 -0700502
503 for diff in diffs:
504 tf, sf, d = diff.GetPatch()
505 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
506 # patch is almost as big as the file; don't bother patching
507 tf.AddToZip(output_zip)
508 verbatim_targets.append((tf.name, tf.size))
509 else:
510 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800511 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700512 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700513
514 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
515 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700516 metadata["pre-build"] = source_fp
517 metadata["post-build"] = target_fp
Doug Zongkereef39442009-04-02 12:14:19 -0700518
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700519 script.Mount("system", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700520 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700521
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700522 source_boot = common.File("/tmp/boot.img",
523 common.BuildBootableImage(
524 os.path.join(OPTIONS.source_tmp, "BOOT")))
525 target_boot = common.File("/tmp/boot.img",
526 common.BuildBootableImage(
527 os.path.join(OPTIONS.target_tmp, "BOOT")))
Doug Zongker5da317e2009-06-02 13:38:17 -0700528 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700529
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700530 source_recovery = common.File("system/recovery.img",
531 common.BuildBootableImage(
532 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
533 target_recovery = common.File("system/recovery.img",
534 common.BuildBootableImage(
535 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700536 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700537
Doug Zongker881dd402009-09-20 14:03:55 -0700538 # Here's how we divide up the progress bar:
539 # 0.1 for verifying the start state (PatchCheck calls)
540 # 0.8 for applying patches (ApplyPatch calls)
541 # 0.1 for unpacking verbatim files, symlinking, and doing the
542 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700543
544 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700545 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700546
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700547 script.Print("Verifying current system...")
548
Doug Zongker881dd402009-09-20 14:03:55 -0700549 script.ShowProgress(0.1, 0)
550 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
551 if updating_boot:
552 total_verify_size += source_boot.size
553 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700554
Doug Zongker5a482092010-02-17 16:09:18 -0800555 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700556 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700557 so_far += sf.size
558 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700559
Doug Zongker5da317e2009-06-02 13:38:17 -0700560 if updating_boot:
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700561 d = common.Difference(target_boot, source_boot)
Doug Zongker761e6422009-09-25 10:45:39 -0700562 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700563 print "boot target: %d source: %d diff: %d" % (
564 target_boot.size, source_boot.size, len(d))
565
Doug Zongker048e7ca2009-06-15 14:31:53 -0700566 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700567
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700568 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
569 (source_boot.size, source_boot.sha1,
570 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700571 so_far += source_boot.size
572 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700573
574 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700575 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800576
Doug Zongker05d3dea2009-06-22 11:32:31 -0700577 device_specific.IncrementalOTA_VerifyEnd()
578
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700579 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700581 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700582 script.Print("Erasing user data...")
583 script.FormatPartition("userdata")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700584
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700585 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700586 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
587 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700588 if i not in target_data] +
589 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700590
Doug Zongker881dd402009-09-20 14:03:55 -0700591 script.ShowProgress(0.8, 0)
592 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
593 if updating_boot:
594 total_patch_size += target_boot.size
595 so_far = 0
596
597 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800598 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800599 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700600 so_far += tf.size
601 script.SetProgress(so_far / total_patch_size)
602
Doug Zongkereef39442009-04-02 12:14:19 -0700603 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700604 # Produce the boot image by applying a patch to the current
605 # contents of the boot partition, and write it back to the
606 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700607 script.Print("Patching boot image...")
608 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
609 % (source_boot.size, source_boot.sha1,
610 target_boot.size, target_boot.sha1),
611 "-",
612 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800613 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700614 so_far += target_boot.size
615 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700616 print "boot image changed; including."
617 else:
618 print "boot image unchanged; skipping."
619
620 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700621 # Is it better to generate recovery as a patch from the current
622 # boot image, or from the previous recovery image? For large
623 # updates with significant kernel changes, probably the former.
624 # For small updates where the kernel hasn't changed, almost
625 # certainly the latter. We pick the first option. Future
626 # complicated schemes may let us effectively use both.
627 #
628 # A wacky possibility: as long as there is room in the boot
629 # partition, include the binaries and image files from recovery in
630 # the boot image (though not in the ramdisk) so they can be used
631 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700632 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800633 script.DeleteFiles(["/system/recovery-from-boot.p",
634 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700635 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700636 else:
637 print "recovery image unchanged; skipping."
638
Doug Zongker881dd402009-09-20 14:03:55 -0700639 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700640
641 target_symlinks = CopySystemFiles(target_zip, None)
642
643 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700644 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700645 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700646 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700647
648 # Note that this call will mess up the tree of Items, so make sure
649 # we're done with it.
650 source_symlinks = CopySystemFiles(source_zip, None)
651 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
652
653 # Delete all the symlinks in source that aren't in target. This
654 # needs to happen before verbatim files are unpacked, in case a
655 # symlink in the source is replaced by a real file in the target.
656 to_delete = []
657 for dest, link in source_symlinks:
658 if link not in target_symlinks_d:
659 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700660 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700661
662 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700663 script.Print("Unpacking new files...")
664 script.UnpackPackageDir("system", "/system")
665
Doug Zongker42265392010-02-12 10:21:00 -0800666 if updating_recovery:
667 script.Print("Unpacking new recovery...")
668 script.UnpackPackageDir("recovery", "/system")
669
Doug Zongker05d3dea2009-06-22 11:32:31 -0700670 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700671
672 # Create all the symlinks that don't already exist, or point to
673 # somewhere different than what we want. Delete each symlink before
674 # creating it, since the 'symlink' command won't overwrite.
675 to_create = []
676 for dest, link in target_symlinks:
677 if link in source_symlinks_d:
678 if dest != source_symlinks_d[link]:
679 to_create.append((dest, link))
680 else:
681 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700682 script.DeleteFiles([i[1] for i in to_create])
683 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700684
685 # Now that the symlinks are created, we can set all the
686 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700687 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700688
Doug Zongker881dd402009-09-20 14:03:55 -0700689 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700690 device_specific.IncrementalOTA_InstallEnd()
691
Doug Zongker1c390a22009-05-14 19:06:36 -0700692 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700693 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700694
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700695 script.AddToZip(target_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700696 WriteMetadata(metadata, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700697
698
699def main(argv):
700
701 def option_handler(o, a):
702 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700703 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700704 elif o in ("-k", "--package_key"):
705 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700706 elif o in ("-i", "--incremental_from"):
707 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700708 elif o in ("-w", "--wipe_user_data"):
709 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700710 elif o in ("-n", "--no_prereq"):
711 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700712 elif o in ("-e", "--extra_script"):
713 OPTIONS.extra_script = a
Doug Zongker761e6422009-09-25 10:45:39 -0700714 elif o in ("--worker_threads"):
715 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700716 else:
717 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700718 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700719
720 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc637db12010-04-21 14:08:44 -0700721 extra_opts="b:k:i:d:wne:",
Doug Zongkereef39442009-04-02 12:14:19 -0700722 extra_long_opts=["board_config=",
723 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700724 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700725 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700726 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700727 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700728 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700729 extra_option_handler=option_handler)
730
731 if len(args) != 2:
732 common.Usage(__doc__)
733 sys.exit(1)
734
Doug Zongker1c390a22009-05-14 19:06:36 -0700735 if OPTIONS.extra_script is not None:
736 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
737
Doug Zongkereef39442009-04-02 12:14:19 -0700738 print "unzipping target target-files..."
739 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700740
Doug Zongkerc18736b2009-09-30 09:20:32 -0700741 if OPTIONS.device_specific is None:
742 # look for the device-specific tools extension location in the input
743 try:
744 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
745 ds = f.read().strip()
746 f.close()
747 if ds:
748 ds = os.path.normpath(ds)
749 print "using device-specific extensions in", ds
750 OPTIONS.device_specific = ds
751 except IOError, e:
752 if e.errno == errno.ENOENT:
753 # nothing specified in the file
754 pass
755 else:
756 raise
757
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700758 OPTIONS.info_dict = common.LoadInfoDict()
759 common.LoadMaxSizes(OPTIONS.info_dict)
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700760 if not OPTIONS.max_image_size:
761 print
762 print " WARNING: Failed to load max image sizes; will not enforce"
763 print " image size limits."
764 print
765
Doug Zongkereef39442009-04-02 12:14:19 -0700766 OPTIONS.target_tmp = OPTIONS.input_tmp
767 input_zip = zipfile.ZipFile(args[0], "r")
768 if OPTIONS.package_key:
769 temp_zip_file = tempfile.NamedTemporaryFile()
770 output_zip = zipfile.ZipFile(temp_zip_file, "w",
771 compression=zipfile.ZIP_DEFLATED)
772 else:
773 output_zip = zipfile.ZipFile(args[1], "w",
774 compression=zipfile.ZIP_DEFLATED)
775
776 if OPTIONS.incremental_source is None:
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700777 WriteFullOTAPackage(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700778 else:
779 print "unzipping source target-files..."
780 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
781 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700782 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700783
784 output_zip.close()
785 if OPTIONS.package_key:
786 SignOutput(temp_zip_file.name, args[1])
787 temp_zip_file.close()
788
789 common.Cleanup()
790
791 print "done."
792
793
794if __name__ == '__main__':
795 try:
796 main(sys.argv[1:])
797 except common.ExternalError, e:
798 print
799 print " ERROR: %s" % (e,)
800 print
801 sys.exit(1)