blob: cd10d7cf33595e09e4001c23538e2a24a81ce475 [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
Doug Zongkereef39442009-04-02 12:14:19 -070094class Item:
95 """Items represent the metadata (user, group, mode) of files and
96 directories in the system image."""
97 ITEMS = {}
98 def __init__(self, name, dir=False):
99 self.name = name
100 self.uid = None
101 self.gid = None
102 self.mode = None
103 self.dir = dir
104
105 if name:
106 self.parent = Item.Get(os.path.dirname(name), dir=True)
107 self.parent.children.append(self)
108 else:
109 self.parent = None
110 if dir:
111 self.children = []
112
113 def Dump(self, indent=0):
114 if self.uid is not None:
115 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
116 else:
117 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
118 if self.dir:
119 print "%s%s" % (" "*indent, self.descendants)
120 print "%s%s" % (" "*indent, self.best_subtree)
121 for i in self.children:
122 i.Dump(indent=indent+1)
123
124 @classmethod
125 def Get(cls, name, dir=False):
126 if name not in cls.ITEMS:
127 cls.ITEMS[name] = Item(name, dir=dir)
128 return cls.ITEMS[name]
129
130 @classmethod
Doug Zongker283e2a12010-03-15 17:52:32 -0700131 def GetMetadata(cls, input_zip):
132
133 try:
134 # See if the target_files contains a record of what the uid,
135 # gid, and mode is supposed to be.
136 output = input_zip.read("META/filesystem_config.txt")
137 except KeyError:
138 # Run the external 'fs_config' program to determine the desired
139 # uid, gid, and mode for every Item object. Note this uses the
140 # one in the client now, which might not be the same as the one
141 # used when this target_files was built.
142 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
143 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144 suffix = { False: "", True: "/" }
145 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
146 for i in cls.ITEMS.itervalues() if i.name])
Doug Zongker3475d362010-03-17 16:39:30 -0700147 output, error = p.communicate(input)
Doug Zongker283e2a12010-03-15 17:52:32 -0700148 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700149
150 for line in output.split("\n"):
151 if not line: continue
152 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700153 i = cls.ITEMS.get(name, None)
154 if i is not None:
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 # set metadata for the files generated by this script.
162 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
163 if i: i.uid, i.gid, i.mode = 0, 0, 0644
164 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
165 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700166
167 def CountChildMetadata(self):
168 """Count up the (uid, gid, mode) tuples for all children and
169 determine the best strategy for using set_perm_recursive and
170 set_perm to correctly chown/chmod all the files to their desired
171 values. Recursively calls itself for all descendants.
172
173 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
174 all descendants of this node. (dmode or fmode may be None.) Also
175 sets the best_subtree of each directory Item to the (uid, gid,
176 dmode, fmode) tuple that will match the most descendants of that
177 Item.
178 """
179
180 assert self.dir
181 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
182 for i in self.children:
183 if i.dir:
184 for k, v in i.CountChildMetadata().iteritems():
185 d[k] = d.get(k, 0) + v
186 else:
187 k = (i.uid, i.gid, None, i.mode)
188 d[k] = d.get(k, 0) + 1
189
190 # Find the (uid, gid, dmode, fmode) tuple that matches the most
191 # descendants.
192
193 # First, find the (uid, gid) pair that matches the most
194 # descendants.
195 ug = {}
196 for (uid, gid, _, _), count in d.iteritems():
197 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
198 ug = MostPopularKey(ug, (0, 0))
199
200 # Now find the dmode and fmode that match the most descendants
201 # with that (uid, gid), and choose those.
202 best_dmode = (0, 0755)
203 best_fmode = (0, 0644)
204 for k, count in d.iteritems():
205 if k[:2] != ug: continue
206 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
207 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
208 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
209
210 return d
211
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700212 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700213 """Append set_perm/set_perm_recursive commands to 'script' to
214 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700215 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700216
217 self.CountChildMetadata()
218
219 def recurse(item, current):
220 # current is the (uid, gid, dmode, fmode) tuple that the current
221 # item (and all its children) have already been set to. We only
222 # need to issue set_perm/set_perm_recursive commands if we're
223 # supposed to be something different.
224 if item.dir:
225 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700226 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700227 current = item.best_subtree
228
229 if item.uid != current[0] or item.gid != current[1] or \
230 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700231 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700232
233 for i in item.children:
234 recurse(i, current)
235 else:
236 if item.uid != current[0] or item.gid != current[1] or \
237 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700238 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700239
240 recurse(self, (-1, -1, -1, -1))
241
242
243def CopySystemFiles(input_zip, output_zip=None,
244 substitute=None):
245 """Copies files underneath system/ in the input zip to the output
246 zip. Populates the Item class with their metadata, and returns a
247 list of symlinks. output_zip may be None, in which case the copy is
248 skipped (but the other side effects still happen). substitute is an
249 optional dict of {output filename: contents} to be output instead of
250 certain input files.
251 """
252
253 symlinks = []
254
255 for info in input_zip.infolist():
256 if info.filename.startswith("SYSTEM/"):
257 basefilename = info.filename[7:]
258 if IsSymlink(info):
259 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700260 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700261 else:
262 info2 = copy.copy(info)
263 fn = info2.filename = "system/" + basefilename
264 if substitute and fn in substitute and substitute[fn] is None:
265 continue
266 if output_zip is not None:
267 if substitute and fn in substitute:
268 data = substitute[fn]
269 else:
270 data = input_zip.read(info.filename)
271 output_zip.writestr(info2, data)
272 if fn.endswith("/"):
273 Item.Get(fn[:-1], dir=True)
274 else:
275 Item.Get(fn, dir=False)
276
277 symlinks.sort()
278 return symlinks
279
280
Doug Zongkereef39442009-04-02 12:14:19 -0700281def SignOutput(temp_zip_name, output_zip_name):
282 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
283 pw = key_passwords[OPTIONS.package_key]
284
Doug Zongker951495f2009-08-14 12:44:19 -0700285 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
286 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700287
288
Doug Zongkereef39442009-04-02 12:14:19 -0700289def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700290 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700291 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700292
Doug Zongkereef39442009-04-02 12:14:19 -0700293
Doug Zongker73ef8252009-07-23 15:12:53 -0700294def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
295 """Generate a binary patch that creates the recovery image starting
296 with the boot image. (Most of the space in these images is just the
297 kernel, which is identical for the two, so the resulting patch
298 should be efficient.) Add it to the output zip, along with a shell
299 script that is run from init.rc on first boot to actually do the
300 patching and install the new recovery image.
301
302 recovery_img and boot_img should be File objects for the
303 corresponding images.
304
305 Returns an Item for the shell script, which must be made
306 executable.
307 """
308
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700309 d = common.Difference(recovery_img, boot_img)
Doug Zongker761e6422009-09-25 10:45:39 -0700310 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700311 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700312 Item.Get("system/recovery-from-boot.p", dir=False)
313
Doug Zongker96a57e72010-09-26 14:57:41 -0700314 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
315 recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
Doug Zongker780c2372010-09-22 10:12:54 -0700316
Doug Zongker73ef8252009-07-23 15:12:53 -0700317 # Images with different content will have a different first page, so
318 # we check to see if this recovery has already been installed by
319 # testing just the first 2k.
320 HEADER_SIZE = 2048
321 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
322 sh = """#!/system/bin/sh
Doug Zongker780c2372010-09-22 10:12:54 -0700323if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then
Doug Zongker73ef8252009-07-23 15:12:53 -0700324 log -t recovery "Installing new recovery image"
Doug Zongker780c2372010-09-22 10:12:54 -0700325 applypatch %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
Doug Zongker73ef8252009-07-23 15:12:53 -0700326else
327 log -t recovery "Recovery image already installed"
328fi
329""" % { 'boot_size': boot_img.size,
330 'boot_sha1': boot_img.sha1,
331 'header_size': HEADER_SIZE,
332 'header_sha1': header_sha1,
333 'recovery_size': recovery_img.size,
Doug Zongker780c2372010-09-22 10:12:54 -0700334 'recovery_sha1': recovery_img.sha1,
335 'boot_type': boot_type,
336 'boot_device': boot_device,
337 'recovery_type': recovery_type,
338 'recovery_device': recovery_device,
339 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700340 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700341 return Item.Get("system/etc/install-recovery.sh", dir=False)
342
343
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700344def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc637db12010-04-21 14:08:44 -0700345 # TODO: how to determine this? We don't know what version it will
346 # be installed on top of. For now, we expect the API just won't
347 # change very often.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700348 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700349
Doug Zongker2ea21062010-04-28 16:05:21 -0700350 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
351 "pre-device": GetBuildProp("ro.product.device", input_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700352 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700353 }
354
Doug Zongker05d3dea2009-06-22 11:32:31 -0700355 device_specific = common.DeviceSpecificParams(
356 input_zip=input_zip,
Doug Zongker37974732010-09-16 17:44:38 -0700357 input_version=OPTIONS.info_dict["recovery_api_version"],
Doug Zongker05d3dea2009-06-22 11:32:31 -0700358 output_zip=output_zip,
359 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700360 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700361 metadata=metadata,
362 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700363
Doug Zongker962069c2009-04-23 11:41:58 -0700364 if not OPTIONS.omit_prereq:
365 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700366 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700367
368 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700369 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700370
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700371 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700372
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700373 if OPTIONS.wipe_user_data:
Doug Zongker258bf462010-09-20 18:04:41 -0700374 script.FormatPartition("/data")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700375
Doug Zongker258bf462010-09-20 18:04:41 -0700376 script.FormatPartition("/system")
377 script.Mount("/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700378 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700379 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700380
381 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700382 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700383
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700384 boot_img = common.File("boot.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700385 os.path.join(OPTIONS.input_tmp, "BOOT")))
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700386 recovery_img = common.File("recovery.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700387 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700388 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700389
Doug Zongker283e2a12010-03-15 17:52:32 -0700390 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700391 Item.Get("system").SetPermissions(script)
392
Doug Zongker37974732010-09-16 17:44:38 -0700393 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
Doug Zongker73ef8252009-07-23 15:12:53 -0700394 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700395 script.ShowProgress(0.2, 0)
396
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700397 script.ShowProgress(0.2, 10)
Doug Zongker258bf462010-09-20 18:04:41 -0700398 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -0700399
400 script.ShowProgress(0.1, 0)
401 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Doug Zongker1c390a22009-05-14 19:06:36 -0700403 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700404 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700405
Doug Zongker14833602010-02-02 13:12:04 -0800406 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700407 script.AddToZip(input_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700408 WriteMetadata(metadata, output_zip)
409
410
411def WriteMetadata(metadata, output_zip):
412 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
413 "".join(["%s=%s\n" % kv
414 for kv in sorted(metadata.iteritems())]))
Doug Zongkereef39442009-04-02 12:14:19 -0700415
416
Doug Zongkereef39442009-04-02 12:14:19 -0700417
418
419def LoadSystemFiles(z):
420 """Load all the files from SYSTEM/... in a given target-files
421 ZipFile, and return a dict of {filename: File object}."""
422 out = {}
423 for info in z.infolist():
424 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
425 fn = "system/" + info.filename[7:]
426 data = z.read(info.filename)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700427 out[fn] = common.File(fn, data)
Doug Zongkereef39442009-04-02 12:14:19 -0700428 return out
429
430
Doug Zongkereef39442009-04-02 12:14:19 -0700431def GetBuildProp(property, z):
432 """Return the fingerprint of the build of a given target-files
433 ZipFile object."""
434 bp = z.read("SYSTEM/build.prop")
435 if not property:
436 return bp
437 m = re.search(re.escape(property) + r"=(.*)\n", bp)
438 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700439 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700440 return m.group(1).strip()
441
442
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700443def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongker37974732010-09-16 17:44:38 -0700444 source_version = OPTIONS.source_info_dict["recovery_api_version"]
445 target_version = OPTIONS.target_info_dict["recovery_api_version"]
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700446
Doug Zongkerc637db12010-04-21 14:08:44 -0700447 if source_version == 0:
448 print ("WARNING: generating edify script for a source that "
449 "can't install it.")
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700450 script = edify_generator.EdifyGenerator(source_version, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700451
Doug Zongker2ea21062010-04-28 16:05:21 -0700452 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700453 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700454 }
455
Doug Zongker05d3dea2009-06-22 11:32:31 -0700456 device_specific = common.DeviceSpecificParams(
457 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800458 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700459 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800460 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700461 output_zip=output_zip,
Doug Zongker2ea21062010-04-28 16:05:21 -0700462 script=script,
Doug Zongker96a57e72010-09-26 14:57:41 -0700463 metadata=metadata,
464 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700465
Doug Zongkereef39442009-04-02 12:14:19 -0700466 print "Loading target..."
467 target_data = LoadSystemFiles(target_zip)
468 print "Loading source..."
469 source_data = LoadSystemFiles(source_zip)
470
471 verbatim_targets = []
472 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700473 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700474 largest_source_size = 0
475 for fn in sorted(target_data.keys()):
476 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700477 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700478 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 Zongkerea5d7a92010-09-12 15:26:16 -0700489 diffs.append(common.Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700490 else:
491 # Target file identical to source.
492 pass
493
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700494 common.ComputeDifferences(diffs)
Doug Zongker761e6422009-09-25 10:45:39 -0700495
496 for diff in diffs:
497 tf, sf, d = diff.GetPatch()
498 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
499 # patch is almost as big as the file; don't bother patching
500 tf.AddToZip(output_zip)
501 verbatim_targets.append((tf.name, tf.size))
502 else:
503 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800504 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700505 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700506
507 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
508 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700509 metadata["pre-build"] = source_fp
510 metadata["post-build"] = target_fp
Doug Zongkereef39442009-04-02 12:14:19 -0700511
Doug Zongker258bf462010-09-20 18:04:41 -0700512 script.Mount("/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700513 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700514
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700515 source_boot = common.File("/tmp/boot.img",
516 common.BuildBootableImage(
517 os.path.join(OPTIONS.source_tmp, "BOOT")))
518 target_boot = common.File("/tmp/boot.img",
519 common.BuildBootableImage(
520 os.path.join(OPTIONS.target_tmp, "BOOT")))
Doug Zongker5da317e2009-06-02 13:38:17 -0700521 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700522
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700523 source_recovery = common.File("system/recovery.img",
524 common.BuildBootableImage(
525 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
526 target_recovery = common.File("system/recovery.img",
527 common.BuildBootableImage(
528 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700529 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700530
Doug Zongker881dd402009-09-20 14:03:55 -0700531 # Here's how we divide up the progress bar:
532 # 0.1 for verifying the start state (PatchCheck calls)
533 # 0.8 for applying patches (ApplyPatch calls)
534 # 0.1 for unpacking verbatim files, symlinking, and doing the
535 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700536
537 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700538 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700539
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700540 script.Print("Verifying current system...")
541
Doug Zongker881dd402009-09-20 14:03:55 -0700542 script.ShowProgress(0.1, 0)
543 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
544 if updating_boot:
545 total_verify_size += source_boot.size
546 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700547
Doug Zongker5a482092010-02-17 16:09:18 -0800548 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700549 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700550 so_far += sf.size
551 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700552
Doug Zongker5da317e2009-06-02 13:38:17 -0700553 if updating_boot:
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700554 d = common.Difference(target_boot, source_boot)
Doug Zongker761e6422009-09-25 10:45:39 -0700555 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700556 print "boot target: %d source: %d diff: %d" % (
557 target_boot.size, source_boot.size, len(d))
558
Doug Zongker048e7ca2009-06-15 14:31:53 -0700559 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700560
Doug Zongker96a57e72010-09-26 14:57:41 -0700561 boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
Doug Zongker780c2372010-09-22 10:12:54 -0700562
563 script.PatchCheck("%s:%s:%d:%s:%d:%s" %
564 (boot_type, boot_device,
565 source_boot.size, source_boot.sha1,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700566 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700567 so_far += source_boot.size
568 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700569
570 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700571 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800572
Doug Zongker05d3dea2009-06-22 11:32:31 -0700573 device_specific.IncrementalOTA_VerifyEnd()
574
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700575 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700576
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700577 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700578 script.Print("Erasing user data...")
Doug Zongker258bf462010-09-20 18:04:41 -0700579 script.FormatPartition("/data")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700580
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700581 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700582 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
583 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700584 if i not in target_data] +
585 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Doug Zongker881dd402009-09-20 14:03:55 -0700587 script.ShowProgress(0.8, 0)
588 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
589 if updating_boot:
590 total_patch_size += target_boot.size
591 so_far = 0
592
593 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800594 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800595 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700596 so_far += tf.size
597 script.SetProgress(so_far / total_patch_size)
598
Doug Zongkereef39442009-04-02 12:14:19 -0700599 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700600 # Produce the boot image by applying a patch to the current
601 # contents of the boot partition, and write it back to the
602 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700603 script.Print("Patching boot image...")
Doug Zongker780c2372010-09-22 10:12:54 -0700604 script.ApplyPatch("%s:%s:%d:%s:%d:%s"
605 % (boot_type, boot_device,
606 source_boot.size, source_boot.sha1,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700607 target_boot.size, target_boot.sha1),
608 "-",
609 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800610 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700611 so_far += target_boot.size
612 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700613 print "boot image changed; including."
614 else:
615 print "boot image unchanged; skipping."
616
617 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700618 # Is it better to generate recovery as a patch from the current
619 # boot image, or from the previous recovery image? For large
620 # updates with significant kernel changes, probably the former.
621 # For small updates where the kernel hasn't changed, almost
622 # certainly the latter. We pick the first option. Future
623 # complicated schemes may let us effectively use both.
624 #
625 # A wacky possibility: as long as there is room in the boot
626 # partition, include the binaries and image files from recovery in
627 # the boot image (though not in the ramdisk) so they can be used
628 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700629 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800630 script.DeleteFiles(["/system/recovery-from-boot.p",
631 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700632 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700633 else:
634 print "recovery image unchanged; skipping."
635
Doug Zongker881dd402009-09-20 14:03:55 -0700636 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700637
638 target_symlinks = CopySystemFiles(target_zip, None)
639
640 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700641 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700642 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700643 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700644
645 # Note that this call will mess up the tree of Items, so make sure
646 # we're done with it.
647 source_symlinks = CopySystemFiles(source_zip, None)
648 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
649
650 # Delete all the symlinks in source that aren't in target. This
651 # needs to happen before verbatim files are unpacked, in case a
652 # symlink in the source is replaced by a real file in the target.
653 to_delete = []
654 for dest, link in source_symlinks:
655 if link not in target_symlinks_d:
656 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700657 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700658
659 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700660 script.Print("Unpacking new files...")
661 script.UnpackPackageDir("system", "/system")
662
Doug Zongker42265392010-02-12 10:21:00 -0800663 if updating_recovery:
664 script.Print("Unpacking new recovery...")
665 script.UnpackPackageDir("recovery", "/system")
666
Doug Zongker05d3dea2009-06-22 11:32:31 -0700667 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700668
669 # Create all the symlinks that don't already exist, or point to
670 # somewhere different than what we want. Delete each symlink before
671 # creating it, since the 'symlink' command won't overwrite.
672 to_create = []
673 for dest, link in target_symlinks:
674 if link in source_symlinks_d:
675 if dest != source_symlinks_d[link]:
676 to_create.append((dest, link))
677 else:
678 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700679 script.DeleteFiles([i[1] for i in to_create])
680 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700681
682 # Now that the symlinks are created, we can set all the
683 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700684 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700685
Doug Zongker881dd402009-09-20 14:03:55 -0700686 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700687 device_specific.IncrementalOTA_InstallEnd()
688
Doug Zongker1c390a22009-05-14 19:06:36 -0700689 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700690 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700691
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700692 script.AddToZip(target_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700693 WriteMetadata(metadata, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700694
695
696def main(argv):
697
698 def option_handler(o, a):
699 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700700 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700701 elif o in ("-k", "--package_key"):
702 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700703 elif o in ("-i", "--incremental_from"):
704 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700705 elif o in ("-w", "--wipe_user_data"):
706 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700707 elif o in ("-n", "--no_prereq"):
708 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700709 elif o in ("-e", "--extra_script"):
710 OPTIONS.extra_script = a
Doug Zongker761e6422009-09-25 10:45:39 -0700711 elif o in ("--worker_threads"):
712 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700713 else:
714 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700715 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700716
717 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc637db12010-04-21 14:08:44 -0700718 extra_opts="b:k:i:d:wne:",
Doug Zongkereef39442009-04-02 12:14:19 -0700719 extra_long_opts=["board_config=",
720 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700721 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700722 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700723 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700724 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700725 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700726 extra_option_handler=option_handler)
727
728 if len(args) != 2:
729 common.Usage(__doc__)
730 sys.exit(1)
731
Doug Zongker1c390a22009-05-14 19:06:36 -0700732 if OPTIONS.extra_script is not None:
733 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
734
Doug Zongkereef39442009-04-02 12:14:19 -0700735 print "unzipping target target-files..."
736 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700737
Doug Zongkereef39442009-04-02 12:14:19 -0700738 OPTIONS.target_tmp = OPTIONS.input_tmp
739 input_zip = zipfile.ZipFile(args[0], "r")
Doug Zongker37974732010-09-16 17:44:38 -0700740 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
741 if OPTIONS.verbose:
742 print "--- target info ---"
743 common.DumpInfoDict(OPTIONS.info_dict)
744
745 if OPTIONS.device_specific is None:
746 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
747 if OPTIONS.device_specific is not None:
748 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
749 print "using device-specific extensions in", OPTIONS.device_specific
750
Doug Zongkereef39442009-04-02 12:14:19 -0700751 if OPTIONS.package_key:
752 temp_zip_file = tempfile.NamedTemporaryFile()
753 output_zip = zipfile.ZipFile(temp_zip_file, "w",
754 compression=zipfile.ZIP_DEFLATED)
755 else:
756 output_zip = zipfile.ZipFile(args[1], "w",
757 compression=zipfile.ZIP_DEFLATED)
758
759 if OPTIONS.incremental_source is None:
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700760 WriteFullOTAPackage(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700761 else:
762 print "unzipping source target-files..."
763 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
764 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
Doug Zongker37974732010-09-16 17:44:38 -0700765 OPTIONS.target_info_dict = OPTIONS.info_dict
766 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
767 if OPTIONS.verbose:
768 print "--- source info ---"
769 common.DumpInfoDict(OPTIONS.source_info_dict)
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700770 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700771
772 output_zip.close()
773 if OPTIONS.package_key:
774 SignOutput(temp_zip_file.name, args[1])
775 temp_zip_file.close()
776
777 common.Cleanup()
778
779 print "done."
780
781
782if __name__ == '__main__':
783 try:
Ying Wangf9bbfb52010-12-13 16:25:36 -0800784 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -0700785 main(sys.argv[1:])
786 except common.ExternalError, e:
787 print
788 print " ERROR: %s" % (e,)
789 print
790 sys.exit(1)