blob: 98a4d192349b32451388be873a536abfc19d891f [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
Doug Zongker780c2372010-09-22 10:12:54 -070079# TODO: this is duplicated from edify_generator.py; fix.
80PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
81 "ext4": "EMMC", "emmc": "EMMC" }
82
Doug Zongkereef39442009-04-02 12:14:19 -070083def MostPopularKey(d, default):
84 """Given a dict, return the key corresponding to the largest
85 value. Returns 'default' if the dict is empty."""
86 x = [(v, k) for (k, v) in d.iteritems()]
87 if not x: return default
88 x.sort()
89 return x[-1][1]
90
91
92def IsSymlink(info):
93 """Return true if the zipfile.ZipInfo object passed in represents a
94 symlink."""
95 return (info.external_attr >> 16) == 0120777
96
97
Doug Zongker780c2372010-09-22 10:12:54 -070098def GetTypeAndDevice(mount_point, info):
99 fstab = info["fstab"]
100 if fstab:
101 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
102 else:
103 devices = {"/boot": "boot",
104 "/recovery": "recovery",
105 "/radio": "radio",
106 "/data": "userdata",
107 "/cache": "cache"}
108 return info["partition_type"], info.get("partition_path", "") + devices[mount_point]
109
Doug Zongkereef39442009-04-02 12:14:19 -0700110
111class Item:
112 """Items represent the metadata (user, group, mode) of files and
113 directories in the system image."""
114 ITEMS = {}
115 def __init__(self, name, dir=False):
116 self.name = name
117 self.uid = None
118 self.gid = None
119 self.mode = None
120 self.dir = dir
121
122 if name:
123 self.parent = Item.Get(os.path.dirname(name), dir=True)
124 self.parent.children.append(self)
125 else:
126 self.parent = None
127 if dir:
128 self.children = []
129
130 def Dump(self, indent=0):
131 if self.uid is not None:
132 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
133 else:
134 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
135 if self.dir:
136 print "%s%s" % (" "*indent, self.descendants)
137 print "%s%s" % (" "*indent, self.best_subtree)
138 for i in self.children:
139 i.Dump(indent=indent+1)
140
141 @classmethod
142 def Get(cls, name, dir=False):
143 if name not in cls.ITEMS:
144 cls.ITEMS[name] = Item(name, dir=dir)
145 return cls.ITEMS[name]
146
147 @classmethod
Doug Zongker283e2a12010-03-15 17:52:32 -0700148 def GetMetadata(cls, input_zip):
149
150 try:
151 # See if the target_files contains a record of what the uid,
152 # gid, and mode is supposed to be.
153 output = input_zip.read("META/filesystem_config.txt")
154 except KeyError:
155 # Run the external 'fs_config' program to determine the desired
156 # uid, gid, and mode for every Item object. Note this uses the
157 # one in the client now, which might not be the same as the one
158 # used when this target_files was built.
159 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
160 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
161 suffix = { False: "", True: "/" }
162 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
163 for i in cls.ITEMS.itervalues() if i.name])
Doug Zongker3475d362010-03-17 16:39:30 -0700164 output, error = p.communicate(input)
Doug Zongker283e2a12010-03-15 17:52:32 -0700165 assert not error
Doug Zongkereef39442009-04-02 12:14:19 -0700166
167 for line in output.split("\n"):
168 if not line: continue
169 name, uid, gid, mode = line.split()
Doug Zongker283e2a12010-03-15 17:52:32 -0700170 i = cls.ITEMS.get(name, None)
171 if i is not None:
172 i.uid = int(uid)
173 i.gid = int(gid)
174 i.mode = int(mode, 8)
175 if i.dir:
176 i.children.sort(key=lambda i: i.name)
177
178 # set metadata for the files generated by this script.
179 i = cls.ITEMS.get("system/recovery-from-boot.p", None)
180 if i: i.uid, i.gid, i.mode = 0, 0, 0644
181 i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
182 if i: i.uid, i.gid, i.mode = 0, 0, 0544
Doug Zongkereef39442009-04-02 12:14:19 -0700183
184 def CountChildMetadata(self):
185 """Count up the (uid, gid, mode) tuples for all children and
186 determine the best strategy for using set_perm_recursive and
187 set_perm to correctly chown/chmod all the files to their desired
188 values. Recursively calls itself for all descendants.
189
190 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
191 all descendants of this node. (dmode or fmode may be None.) Also
192 sets the best_subtree of each directory Item to the (uid, gid,
193 dmode, fmode) tuple that will match the most descendants of that
194 Item.
195 """
196
197 assert self.dir
198 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
199 for i in self.children:
200 if i.dir:
201 for k, v in i.CountChildMetadata().iteritems():
202 d[k] = d.get(k, 0) + v
203 else:
204 k = (i.uid, i.gid, None, i.mode)
205 d[k] = d.get(k, 0) + 1
206
207 # Find the (uid, gid, dmode, fmode) tuple that matches the most
208 # descendants.
209
210 # First, find the (uid, gid) pair that matches the most
211 # descendants.
212 ug = {}
213 for (uid, gid, _, _), count in d.iteritems():
214 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
215 ug = MostPopularKey(ug, (0, 0))
216
217 # Now find the dmode and fmode that match the most descendants
218 # with that (uid, gid), and choose those.
219 best_dmode = (0, 0755)
220 best_fmode = (0, 0644)
221 for k, count in d.iteritems():
222 if k[:2] != ug: continue
223 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
224 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
225 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
226
227 return d
228
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700229 def SetPermissions(self, script):
Doug Zongkereef39442009-04-02 12:14:19 -0700230 """Append set_perm/set_perm_recursive commands to 'script' to
231 set all permissions, users, and groups for the tree of files
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700232 rooted at 'self'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700233
234 self.CountChildMetadata()
235
236 def recurse(item, current):
237 # current is the (uid, gid, dmode, fmode) tuple that the current
238 # item (and all its children) have already been set to. We only
239 # need to issue set_perm/set_perm_recursive commands if we're
240 # supposed to be something different.
241 if item.dir:
242 if current != item.best_subtree:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700243 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
Doug Zongkereef39442009-04-02 12:14:19 -0700244 current = item.best_subtree
245
246 if item.uid != current[0] or item.gid != current[1] or \
247 item.mode != current[2]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700248 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700249
250 for i in item.children:
251 recurse(i, current)
252 else:
253 if item.uid != current[0] or item.gid != current[1] or \
254 item.mode != current[3]:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700255 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
Doug Zongkereef39442009-04-02 12:14:19 -0700256
257 recurse(self, (-1, -1, -1, -1))
258
259
260def CopySystemFiles(input_zip, output_zip=None,
261 substitute=None):
262 """Copies files underneath system/ in the input zip to the output
263 zip. Populates the Item class with their metadata, and returns a
264 list of symlinks. output_zip may be None, in which case the copy is
265 skipped (but the other side effects still happen). substitute is an
266 optional dict of {output filename: contents} to be output instead of
267 certain input files.
268 """
269
270 symlinks = []
271
272 for info in input_zip.infolist():
273 if info.filename.startswith("SYSTEM/"):
274 basefilename = info.filename[7:]
275 if IsSymlink(info):
276 symlinks.append((input_zip.read(info.filename),
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700277 "/system/" + basefilename))
Doug Zongkereef39442009-04-02 12:14:19 -0700278 else:
279 info2 = copy.copy(info)
280 fn = info2.filename = "system/" + basefilename
281 if substitute and fn in substitute and substitute[fn] is None:
282 continue
283 if output_zip is not None:
284 if substitute and fn in substitute:
285 data = substitute[fn]
286 else:
287 data = input_zip.read(info.filename)
288 output_zip.writestr(info2, data)
289 if fn.endswith("/"):
290 Item.Get(fn[:-1], dir=True)
291 else:
292 Item.Get(fn, dir=False)
293
294 symlinks.sort()
295 return symlinks
296
297
Doug Zongkereef39442009-04-02 12:14:19 -0700298def SignOutput(temp_zip_name, output_zip_name):
299 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
300 pw = key_passwords[OPTIONS.package_key]
301
Doug Zongker951495f2009-08-14 12:44:19 -0700302 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
303 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700304
305
Doug Zongkereef39442009-04-02 12:14:19 -0700306def AppendAssertions(script, input_zip):
Doug Zongkereef39442009-04-02 12:14:19 -0700307 device = GetBuildProp("ro.product.device", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700308 script.AssertDevice(device)
Doug Zongkereef39442009-04-02 12:14:19 -0700309
Doug Zongkereef39442009-04-02 12:14:19 -0700310
Doug Zongker73ef8252009-07-23 15:12:53 -0700311def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
312 """Generate a binary patch that creates the recovery image starting
313 with the boot image. (Most of the space in these images is just the
314 kernel, which is identical for the two, so the resulting patch
315 should be efficient.) Add it to the output zip, along with a shell
316 script that is run from init.rc on first boot to actually do the
317 patching and install the new recovery image.
318
319 recovery_img and boot_img should be File objects for the
320 corresponding images.
321
322 Returns an Item for the shell script, which must be made
323 executable.
324 """
325
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700326 d = common.Difference(recovery_img, boot_img)
Doug Zongker761e6422009-09-25 10:45:39 -0700327 _, _, patch = d.ComputePatch()
Doug Zongkercfd7db62009-10-07 11:35:53 -0700328 common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Doug Zongker73ef8252009-07-23 15:12:53 -0700329 Item.Get("system/recovery-from-boot.p", dir=False)
330
Doug Zongker780c2372010-09-22 10:12:54 -0700331 boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict)
332 recovery_type, recovery_device = GetTypeAndDevice("/recovery", OPTIONS.info_dict)
333
Doug Zongker73ef8252009-07-23 15:12:53 -0700334 # Images with different content will have a different first page, so
335 # we check to see if this recovery has already been installed by
336 # testing just the first 2k.
337 HEADER_SIZE = 2048
338 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
339 sh = """#!/system/bin/sh
Doug Zongker780c2372010-09-22 10:12:54 -0700340if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(header_size)d:%(header_sha1)s; then
Doug Zongker73ef8252009-07-23 15:12:53 -0700341 log -t recovery "Installing new recovery image"
Doug Zongker780c2372010-09-22 10:12:54 -0700342 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 -0700343else
344 log -t recovery "Recovery image already installed"
345fi
346""" % { 'boot_size': boot_img.size,
347 'boot_sha1': boot_img.sha1,
348 'header_size': HEADER_SIZE,
349 'header_sha1': header_sha1,
350 'recovery_size': recovery_img.size,
Doug Zongker780c2372010-09-22 10:12:54 -0700351 'recovery_sha1': recovery_img.sha1,
352 'boot_type': boot_type,
353 'boot_device': boot_device,
354 'recovery_type': recovery_type,
355 'recovery_device': recovery_device,
356 }
Doug Zongkercfd7db62009-10-07 11:35:53 -0700357 common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
Doug Zongker73ef8252009-07-23 15:12:53 -0700358 return Item.Get("system/etc/install-recovery.sh", dir=False)
359
360
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700361def WriteFullOTAPackage(input_zip, output_zip):
Doug Zongkerc637db12010-04-21 14:08:44 -0700362 # TODO: how to determine this? We don't know what version it will
363 # be installed on top of. For now, we expect the API just won't
364 # change very often.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700365 script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700366
Doug Zongker2ea21062010-04-28 16:05:21 -0700367 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
368 "pre-device": GetBuildProp("ro.product.device", input_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700369 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700370 }
371
Doug Zongker05d3dea2009-06-22 11:32:31 -0700372 device_specific = common.DeviceSpecificParams(
373 input_zip=input_zip,
Doug Zongker37974732010-09-16 17:44:38 -0700374 input_version=OPTIONS.info_dict["recovery_api_version"],
Doug Zongker05d3dea2009-06-22 11:32:31 -0700375 output_zip=output_zip,
376 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700377 input_tmp=OPTIONS.input_tmp,
378 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700379
Doug Zongker962069c2009-04-23 11:41:58 -0700380 if not OPTIONS.omit_prereq:
381 ts = GetBuildProp("ro.build.date.utc", input_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700382 script.AssertOlderBuild(ts)
Doug Zongkereef39442009-04-02 12:14:19 -0700383
384 AppendAssertions(script, input_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700385 device_specific.FullOTA_Assertions()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700386
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700387 script.ShowProgress(0.5, 0)
Doug Zongkereef39442009-04-02 12:14:19 -0700388
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700389 if OPTIONS.wipe_user_data:
Doug Zongker258bf462010-09-20 18:04:41 -0700390 script.FormatPartition("/data")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700391
Doug Zongker258bf462010-09-20 18:04:41 -0700392 script.FormatPartition("/system")
393 script.Mount("/system")
Doug Zongkercfd7db62009-10-07 11:35:53 -0700394 script.UnpackPackageDir("recovery", "/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700395 script.UnpackPackageDir("system", "/system")
Doug Zongkereef39442009-04-02 12:14:19 -0700396
397 symlinks = CopySystemFiles(input_zip, output_zip)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700398 script.MakeSymlinks(symlinks)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700400 boot_img = common.File("boot.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700401 os.path.join(OPTIONS.input_tmp, "BOOT")))
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700402 recovery_img = common.File("recovery.img", common.BuildBootableImage(
Doug Zongker73ef8252009-07-23 15:12:53 -0700403 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
Doug Zongker283e2a12010-03-15 17:52:32 -0700404 MakeRecoveryPatch(output_zip, recovery_img, boot_img)
Doug Zongkereef39442009-04-02 12:14:19 -0700405
Doug Zongker283e2a12010-03-15 17:52:32 -0700406 Item.GetMetadata(input_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700407 Item.Get("system").SetPermissions(script)
408
Doug Zongker37974732010-09-16 17:44:38 -0700409 common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
Doug Zongker73ef8252009-07-23 15:12:53 -0700410 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700411 script.ShowProgress(0.2, 0)
412
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700413 script.ShowProgress(0.2, 10)
Doug Zongker258bf462010-09-20 18:04:41 -0700414 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -0700415
416 script.ShowProgress(0.1, 0)
417 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700418
Doug Zongker1c390a22009-05-14 19:06:36 -0700419 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700420 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700421
Doug Zongker14833602010-02-02 13:12:04 -0800422 script.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700423 script.AddToZip(input_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700424 WriteMetadata(metadata, output_zip)
425
426
427def WriteMetadata(metadata, output_zip):
428 common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
429 "".join(["%s=%s\n" % kv
430 for kv in sorted(metadata.iteritems())]))
Doug Zongkereef39442009-04-02 12:14:19 -0700431
432
Doug Zongkereef39442009-04-02 12:14:19 -0700433
434
435def LoadSystemFiles(z):
436 """Load all the files from SYSTEM/... in a given target-files
437 ZipFile, and return a dict of {filename: File object}."""
438 out = {}
439 for info in z.infolist():
440 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
441 fn = "system/" + info.filename[7:]
442 data = z.read(info.filename)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700443 out[fn] = common.File(fn, data)
Doug Zongkereef39442009-04-02 12:14:19 -0700444 return out
445
446
Doug Zongkereef39442009-04-02 12:14:19 -0700447def GetBuildProp(property, z):
448 """Return the fingerprint of the build of a given target-files
449 ZipFile object."""
450 bp = z.read("SYSTEM/build.prop")
451 if not property:
452 return bp
453 m = re.search(re.escape(property) + r"=(.*)\n", bp)
454 if not m:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700455 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
Doug Zongkereef39442009-04-02 12:14:19 -0700456 return m.group(1).strip()
457
458
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700459def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
Doug Zongker37974732010-09-16 17:44:38 -0700460 source_version = OPTIONS.source_info_dict["recovery_api_version"]
461 target_version = OPTIONS.target_info_dict["recovery_api_version"]
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700462
Doug Zongkerc637db12010-04-21 14:08:44 -0700463 if source_version == 0:
464 print ("WARNING: generating edify script for a source that "
465 "can't install it.")
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700466 script = edify_generator.EdifyGenerator(source_version, OPTIONS.info_dict)
Doug Zongkereef39442009-04-02 12:14:19 -0700467
Doug Zongker2ea21062010-04-28 16:05:21 -0700468 metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
Doug Zongker3b852692010-06-21 15:30:45 -0700469 "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
Doug Zongker2ea21062010-04-28 16:05:21 -0700470 }
471
Doug Zongker05d3dea2009-06-22 11:32:31 -0700472 device_specific = common.DeviceSpecificParams(
473 source_zip=source_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800474 source_version=source_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700475 target_zip=target_zip,
Doug Zongker14833602010-02-02 13:12:04 -0800476 target_version=target_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700477 output_zip=output_zip,
Doug Zongker2ea21062010-04-28 16:05:21 -0700478 script=script,
479 metadata=metadata)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700480
Doug Zongkereef39442009-04-02 12:14:19 -0700481 print "Loading target..."
482 target_data = LoadSystemFiles(target_zip)
483 print "Loading source..."
484 source_data = LoadSystemFiles(source_zip)
485
486 verbatim_targets = []
487 patch_list = []
Doug Zongker761e6422009-09-25 10:45:39 -0700488 diffs = []
Doug Zongkereef39442009-04-02 12:14:19 -0700489 largest_source_size = 0
490 for fn in sorted(target_data.keys()):
491 tf = target_data[fn]
Doug Zongker761e6422009-09-25 10:45:39 -0700492 assert fn == tf.name
Doug Zongkereef39442009-04-02 12:14:19 -0700493 sf = source_data.get(fn, None)
494
495 if sf is None or fn in OPTIONS.require_verbatim:
496 # This file should be included verbatim
497 if fn in OPTIONS.prohibit_verbatim:
Doug Zongker9fc74c72009-06-23 16:27:38 -0700498 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
Doug Zongkereef39442009-04-02 12:14:19 -0700499 print "send", fn, "verbatim"
500 tf.AddToZip(output_zip)
501 verbatim_targets.append((fn, tf.size))
502 elif tf.sha1 != sf.sha1:
503 # File is different; consider sending as a patch
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700504 diffs.append(common.Difference(tf, sf))
Doug Zongkereef39442009-04-02 12:14:19 -0700505 else:
506 # Target file identical to source.
507 pass
508
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700509 common.ComputeDifferences(diffs)
Doug Zongker761e6422009-09-25 10:45:39 -0700510
511 for diff in diffs:
512 tf, sf, d = diff.GetPatch()
513 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
514 # patch is almost as big as the file; don't bother patching
515 tf.AddToZip(output_zip)
516 verbatim_targets.append((tf.name, tf.size))
517 else:
518 common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
Doug Zongker5a482092010-02-17 16:09:18 -0800519 patch_list.append((tf.name, tf, sf, tf.size, sha.sha(d).hexdigest()))
Doug Zongker761e6422009-09-25 10:45:39 -0700520 largest_source_size = max(largest_source_size, sf.size)
Doug Zongkereef39442009-04-02 12:14:19 -0700521
522 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
523 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700524 metadata["pre-build"] = source_fp
525 metadata["post-build"] = target_fp
Doug Zongkereef39442009-04-02 12:14:19 -0700526
Doug Zongker258bf462010-09-20 18:04:41 -0700527 script.Mount("/system")
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700528 script.AssertSomeFingerprint(source_fp, target_fp)
Doug Zongkereef39442009-04-02 12:14:19 -0700529
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700530 source_boot = common.File("/tmp/boot.img",
531 common.BuildBootableImage(
532 os.path.join(OPTIONS.source_tmp, "BOOT")))
533 target_boot = common.File("/tmp/boot.img",
534 common.BuildBootableImage(
535 os.path.join(OPTIONS.target_tmp, "BOOT")))
Doug Zongker5da317e2009-06-02 13:38:17 -0700536 updating_boot = (source_boot.data != target_boot.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700537
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700538 source_recovery = common.File("system/recovery.img",
539 common.BuildBootableImage(
540 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
541 target_recovery = common.File("system/recovery.img",
542 common.BuildBootableImage(
543 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
Doug Zongkerf6a8bad2009-05-29 11:41:21 -0700544 updating_recovery = (source_recovery.data != target_recovery.data)
Doug Zongkereef39442009-04-02 12:14:19 -0700545
Doug Zongker881dd402009-09-20 14:03:55 -0700546 # Here's how we divide up the progress bar:
547 # 0.1 for verifying the start state (PatchCheck calls)
548 # 0.8 for applying patches (ApplyPatch calls)
549 # 0.1 for unpacking verbatim files, symlinking, and doing the
550 # device-specific commands.
Doug Zongkereef39442009-04-02 12:14:19 -0700551
552 AppendAssertions(script, target_zip)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700553 device_specific.IncrementalOTA_Assertions()
Doug Zongkereef39442009-04-02 12:14:19 -0700554
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700555 script.Print("Verifying current system...")
556
Doug Zongker881dd402009-09-20 14:03:55 -0700557 script.ShowProgress(0.1, 0)
558 total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
559 if updating_boot:
560 total_verify_size += source_boot.size
561 so_far = 0
Doug Zongkereef39442009-04-02 12:14:19 -0700562
Doug Zongker5a482092010-02-17 16:09:18 -0800563 for fn, tf, sf, size, patch_sha in patch_list:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700564 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
Doug Zongker881dd402009-09-20 14:03:55 -0700565 so_far += sf.size
566 script.SetProgress(so_far / total_verify_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700567
Doug Zongker5da317e2009-06-02 13:38:17 -0700568 if updating_boot:
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700569 d = common.Difference(target_boot, source_boot)
Doug Zongker761e6422009-09-25 10:45:39 -0700570 _, _, d = d.ComputePatch()
Doug Zongker5da317e2009-06-02 13:38:17 -0700571 print "boot target: %d source: %d diff: %d" % (
572 target_boot.size, source_boot.size, len(d))
573
Doug Zongker048e7ca2009-06-15 14:31:53 -0700574 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
Doug Zongker5da317e2009-06-02 13:38:17 -0700575
Doug Zongker780c2372010-09-22 10:12:54 -0700576 boot_type, boot_device = GetTypeAndDevice("/boot", OPTIONS.info_dict)
577
578 script.PatchCheck("%s:%s:%d:%s:%d:%s" %
579 (boot_type, boot_device,
580 source_boot.size, source_boot.sha1,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700581 target_boot.size, target_boot.sha1))
Doug Zongker881dd402009-09-20 14:03:55 -0700582 so_far += source_boot.size
583 script.SetProgress(so_far / total_verify_size)
Doug Zongker5da317e2009-06-02 13:38:17 -0700584
585 if patch_list or updating_recovery or updating_boot:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700586 script.CacheFreeSpaceCheck(largest_source_size)
Doug Zongker5a482092010-02-17 16:09:18 -0800587
Doug Zongker05d3dea2009-06-22 11:32:31 -0700588 device_specific.IncrementalOTA_VerifyEnd()
589
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700590 script.Comment("---- start making changes here ----")
Doug Zongkereef39442009-04-02 12:14:19 -0700591
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700592 if OPTIONS.wipe_user_data:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700593 script.Print("Erasing user data...")
Doug Zongker258bf462010-09-20 18:04:41 -0700594 script.FormatPartition("/data")
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700595
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700596 script.Print("Removing unneeded files...")
Doug Zongker0f3298a2009-06-30 08:16:58 -0700597 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
598 ["/"+i for i in sorted(source_data)
Doug Zongker3b949f02009-08-24 10:24:32 -0700599 if i not in target_data] +
600 ["/system/recovery.img"])
Doug Zongkereef39442009-04-02 12:14:19 -0700601
Doug Zongker881dd402009-09-20 14:03:55 -0700602 script.ShowProgress(0.8, 0)
603 total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
604 if updating_boot:
605 total_patch_size += target_boot.size
606 so_far = 0
607
608 script.Print("Patching system files...")
Doug Zongker5a482092010-02-17 16:09:18 -0800609 for fn, tf, sf, size, _ in patch_list:
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800610 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
Doug Zongker881dd402009-09-20 14:03:55 -0700611 so_far += tf.size
612 script.SetProgress(so_far / total_patch_size)
613
Doug Zongkereef39442009-04-02 12:14:19 -0700614 if updating_boot:
Doug Zongker5da317e2009-06-02 13:38:17 -0700615 # Produce the boot image by applying a patch to the current
616 # contents of the boot partition, and write it back to the
617 # partition.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700618 script.Print("Patching boot image...")
Doug Zongker780c2372010-09-22 10:12:54 -0700619 script.ApplyPatch("%s:%s:%d:%s:%d:%s"
620 % (boot_type, boot_device,
621 source_boot.size, source_boot.sha1,
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700622 target_boot.size, target_boot.sha1),
623 "-",
624 target_boot.size, target_boot.sha1,
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800625 source_boot.sha1, "patch/boot.img.p")
Doug Zongker881dd402009-09-20 14:03:55 -0700626 so_far += target_boot.size
627 script.SetProgress(so_far / total_patch_size)
Doug Zongkereef39442009-04-02 12:14:19 -0700628 print "boot image changed; including."
629 else:
630 print "boot image unchanged; skipping."
631
632 if updating_recovery:
Doug Zongker73ef8252009-07-23 15:12:53 -0700633 # Is it better to generate recovery as a patch from the current
634 # boot image, or from the previous recovery image? For large
635 # updates with significant kernel changes, probably the former.
636 # For small updates where the kernel hasn't changed, almost
637 # certainly the latter. We pick the first option. Future
638 # complicated schemes may let us effectively use both.
639 #
640 # A wacky possibility: as long as there is room in the boot
641 # partition, include the binaries and image files from recovery in
642 # the boot image (though not in the ramdisk) so they can be used
643 # as fodder for constructing the recovery image.
Doug Zongker283e2a12010-03-15 17:52:32 -0700644 MakeRecoveryPatch(output_zip, target_recovery, target_boot)
Doug Zongker42265392010-02-12 10:21:00 -0800645 script.DeleteFiles(["/system/recovery-from-boot.p",
646 "/system/etc/install-recovery.sh"])
Doug Zongker73ef8252009-07-23 15:12:53 -0700647 print "recovery image changed; including as patch from boot."
Doug Zongkereef39442009-04-02 12:14:19 -0700648 else:
649 print "recovery image unchanged; skipping."
650
Doug Zongker881dd402009-09-20 14:03:55 -0700651 script.ShowProgress(0.1, 10)
Doug Zongkereef39442009-04-02 12:14:19 -0700652
653 target_symlinks = CopySystemFiles(target_zip, None)
654
655 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700656 temp_script = script.MakeTemporary()
Doug Zongker283e2a12010-03-15 17:52:32 -0700657 Item.GetMetadata(target_zip)
Doug Zongker73ef8252009-07-23 15:12:53 -0700658 Item.Get("system").SetPermissions(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700659
660 # Note that this call will mess up the tree of Items, so make sure
661 # we're done with it.
662 source_symlinks = CopySystemFiles(source_zip, None)
663 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
664
665 # Delete all the symlinks in source that aren't in target. This
666 # needs to happen before verbatim files are unpacked, in case a
667 # symlink in the source is replaced by a real file in the target.
668 to_delete = []
669 for dest, link in source_symlinks:
670 if link not in target_symlinks_d:
671 to_delete.append(link)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700672 script.DeleteFiles(to_delete)
Doug Zongkereef39442009-04-02 12:14:19 -0700673
674 if verbatim_targets:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700675 script.Print("Unpacking new files...")
676 script.UnpackPackageDir("system", "/system")
677
Doug Zongker42265392010-02-12 10:21:00 -0800678 if updating_recovery:
679 script.Print("Unpacking new recovery...")
680 script.UnpackPackageDir("recovery", "/system")
681
Doug Zongker05d3dea2009-06-22 11:32:31 -0700682 script.Print("Symlinks and permissions...")
Doug Zongkereef39442009-04-02 12:14:19 -0700683
684 # Create all the symlinks that don't already exist, or point to
685 # somewhere different than what we want. Delete each symlink before
686 # creating it, since the 'symlink' command won't overwrite.
687 to_create = []
688 for dest, link in target_symlinks:
689 if link in source_symlinks_d:
690 if dest != source_symlinks_d[link]:
691 to_create.append((dest, link))
692 else:
693 to_create.append((dest, link))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700694 script.DeleteFiles([i[1] for i in to_create])
695 script.MakeSymlinks(to_create)
Doug Zongkereef39442009-04-02 12:14:19 -0700696
697 # Now that the symlinks are created, we can set all the
698 # permissions.
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700699 script.AppendScript(temp_script)
Doug Zongkereef39442009-04-02 12:14:19 -0700700
Doug Zongker881dd402009-09-20 14:03:55 -0700701 # Do device-specific installation (eg, write radio image).
Doug Zongker05d3dea2009-06-22 11:32:31 -0700702 device_specific.IncrementalOTA_InstallEnd()
703
Doug Zongker1c390a22009-05-14 19:06:36 -0700704 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700705 scirpt.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700706
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700707 script.AddToZip(target_zip, output_zip)
Doug Zongker2ea21062010-04-28 16:05:21 -0700708 WriteMetadata(metadata, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700709
710
711def main(argv):
712
713 def option_handler(o, a):
714 if o in ("-b", "--board_config"):
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700715 pass # deprecated
Doug Zongkereef39442009-04-02 12:14:19 -0700716 elif o in ("-k", "--package_key"):
717 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700718 elif o in ("-i", "--incremental_from"):
719 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700720 elif o in ("-w", "--wipe_user_data"):
721 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700722 elif o in ("-n", "--no_prereq"):
723 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700724 elif o in ("-e", "--extra_script"):
725 OPTIONS.extra_script = a
Doug Zongker761e6422009-09-25 10:45:39 -0700726 elif o in ("--worker_threads"):
727 OPTIONS.worker_threads = int(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700728 else:
729 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700730 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700731
732 args = common.ParseOptions(argv, __doc__,
Doug Zongkerc637db12010-04-21 14:08:44 -0700733 extra_opts="b:k:i:d:wne:",
Doug Zongkereef39442009-04-02 12:14:19 -0700734 extra_long_opts=["board_config=",
735 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700736 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700737 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700738 "no_prereq",
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700739 "extra_script=",
Doug Zongker761e6422009-09-25 10:45:39 -0700740 "worker_threads="],
Doug Zongkereef39442009-04-02 12:14:19 -0700741 extra_option_handler=option_handler)
742
743 if len(args) != 2:
744 common.Usage(__doc__)
745 sys.exit(1)
746
Doug Zongker1c390a22009-05-14 19:06:36 -0700747 if OPTIONS.extra_script is not None:
748 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
749
Doug Zongkereef39442009-04-02 12:14:19 -0700750 print "unzipping target target-files..."
751 OPTIONS.input_tmp = common.UnzipTemp(args[0])
Doug Zongkerfdd8e692009-08-03 17:27:48 -0700752
Doug Zongkereef39442009-04-02 12:14:19 -0700753 OPTIONS.target_tmp = OPTIONS.input_tmp
754 input_zip = zipfile.ZipFile(args[0], "r")
Doug Zongker37974732010-09-16 17:44:38 -0700755 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
756 if OPTIONS.verbose:
757 print "--- target info ---"
758 common.DumpInfoDict(OPTIONS.info_dict)
759
760 if OPTIONS.device_specific is None:
761 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
762 if OPTIONS.device_specific is not None:
763 OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
764 print "using device-specific extensions in", OPTIONS.device_specific
765
Doug Zongkereef39442009-04-02 12:14:19 -0700766 if OPTIONS.package_key:
767 temp_zip_file = tempfile.NamedTemporaryFile()
768 output_zip = zipfile.ZipFile(temp_zip_file, "w",
769 compression=zipfile.ZIP_DEFLATED)
770 else:
771 output_zip = zipfile.ZipFile(args[1], "w",
772 compression=zipfile.ZIP_DEFLATED)
773
774 if OPTIONS.incremental_source is None:
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700775 WriteFullOTAPackage(input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700776 else:
777 print "unzipping source target-files..."
778 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
779 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
Doug Zongker37974732010-09-16 17:44:38 -0700780 OPTIONS.target_info_dict = OPTIONS.info_dict
781 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
782 if OPTIONS.verbose:
783 print "--- source info ---"
784 common.DumpInfoDict(OPTIONS.source_info_dict)
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700785 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700786
787 output_zip.close()
788 if OPTIONS.package_key:
789 SignOutput(temp_zip_file.name, args[1])
790 temp_zip_file.close()
791
792 common.Cleanup()
793
794 print "done."
795
796
797if __name__ == '__main__':
798 try:
799 main(sys.argv[1:])
800 except common.ExternalError, e:
801 print
802 print " ERROR: %s" % (e,)
803 print
804 sys.exit(1)