blob: 39c9b3dc0892d9783adc88bda44505638beff253 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Doug Zongkerb34fcce2014-09-11 09:34:56 -070033from rangelib import *
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Doug Zongker55d93282011-01-25 17:03:34 -080035try:
davidcad0bb92011-03-15 14:21:38 +000036 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037except ImportError:
davidcad0bb92011-03-15 14:21:38 +000038 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080039
Doug Zongkereef39442009-04-02 12:14:19 -070040# missing in Python 2.4 and before
41if not hasattr(os, "SEEK_SET"):
42 os.SEEK_SET = 0
43
44class Options(object): pass
45OPTIONS = Options()
Doug Zongker85448772014-09-09 14:59:20 -070046
47DEFAULT_SEARCH_PATH_BY_PLATFORM = {
48 "linux2": "out/host/linux-x86",
49 "darwin": "out/host/darwin-x86",
50 }
51OPTIONS.search_path = DEFAULT_SEARCH_PATH_BY_PLATFORM.get(sys.platform, None)
52
T.R. Fullhart37e10522013-03-18 10:31:26 -070053OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
54OPTIONS.extra_signapk_args = []
55OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070056OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070057OPTIONS.public_key_suffix = ".x509.pem"
58OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070059OPTIONS.verbose = False
60OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070061OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080062OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070063OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070064
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080065
66# Values for "certificate" in apkcerts that mean special things.
67SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
68
69
Doug Zongkereef39442009-04-02 12:14:19 -070070class ExternalError(RuntimeError): pass
71
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Doug Zongkerc9253822014-02-04 12:17:58 -080097def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
102 if isinstance(input, zipfile.ZipFile):
103 return input.read(fn)
104 else:
105 path = os.path.join(input, *fn.split("/"))
106 try:
107 with open(path) as f:
108 return f.read()
109 except IOError, e:
110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800125 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700126 except KeyError:
127 # ok if flags don't exist
128 pass
129
130 if "recovery_api_version" not in d:
131 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800132 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700133 except KeyError:
134 raise ValueError("can't find recovery API version in input target-files")
135
136 if "tool_extensions" not in d:
137 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800138 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700139 except KeyError:
140 # ok if extensions don't exist
141 pass
142
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800143 if "fstab_version" not in d:
144 d["fstab_version"] = "1"
145
Doug Zongker37974732010-09-16 17:44:38 -0700146 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700148 for line in data.split("\n"):
149 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700150 name, value = line.split(" ", 1)
151 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700152 if name == "blocksize":
153 d[name] = value
154 else:
155 d[name + "_size"] = value
156 except KeyError:
157 pass
158
159 def makeint(key):
160 if key in d:
161 d[key] = int(d[key], 0)
162
163 makeint("recovery_api_version")
164 makeint("blocksize")
165 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700166 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700167 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700168 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700169 makeint("recovery_size")
170 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800171 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700172
Doug Zongkerc9253822014-02-04 12:17:58 -0800173 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
174 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700175 return d
176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700178 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800179 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700180 except KeyError:
181 print "Warning: could not find SYSTEM/build.prop in %s" % zip
182 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700183 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184
Michael Runge6e836112014-04-15 17:40:21 -0700185def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700186 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700187 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188 line = line.strip()
189 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700190 if "=" in line:
191 name, value = line.split("=", 1)
192 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700193 return d
194
Doug Zongkerc9253822014-02-04 12:17:58 -0800195def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700196 class Partition(object):
197 pass
198
199 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800200 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800202 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700203 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800205 if fstab_version == 1:
206 d = {}
207 for line in data.split("\n"):
208 line = line.strip()
209 if not line or line.startswith("#"): continue
210 pieces = line.split()
211 if not (3 <= len(pieces) <= 4):
212 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700213
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800214 p = Partition()
215 p.mount_point = pieces[0]
216 p.fs_type = pieces[1]
217 p.device = pieces[2]
218 p.length = 0
219 options = None
220 if len(pieces) >= 4:
221 if pieces[3].startswith("/"):
222 p.device2 = pieces[3]
223 if len(pieces) >= 5:
224 options = pieces[4]
225 else:
226 p.device2 = None
227 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800228 else:
229 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700230
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 if options:
232 options = options.split(",")
233 for i in options:
234 if i.startswith("length="):
235 p.length = int(i[7:])
236 else:
237 print "%s: unknown option \"%s\"" % (p.mount_point, i)
238
239 d[p.mount_point] = p
240
241 elif fstab_version == 2:
242 d = {}
243 for line in data.split("\n"):
244 line = line.strip()
245 if not line or line.startswith("#"): continue
246 pieces = line.split()
247 if len(pieces) != 5:
248 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
249
250 # Ignore entries that are managed by vold
251 options = pieces[4]
252 if "voldmanaged=" in options: continue
253
254 # It's a good line, parse it
255 p = Partition()
256 p.device = pieces[0]
257 p.mount_point = pieces[1]
258 p.fs_type = pieces[2]
259 p.device2 = None
260 p.length = 0
261
Doug Zongker086cbb02011-02-17 15:54:20 -0800262 options = options.split(",")
263 for i in options:
264 if i.startswith("length="):
265 p.length = int(i[7:])
266 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 # Ignore all unknown options in the unified fstab
268 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800269
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270 d[p.mount_point] = p
271
272 else:
273 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
274
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700275 return d
276
277
Doug Zongker37974732010-09-16 17:44:38 -0700278def DumpInfoDict(d):
279 for k, v in sorted(d.items()):
280 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700281
Doug Zongkerd5131602012-08-02 14:46:42 -0700282def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700283 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700284 'sourcedir'), and turn them into a boot image. Return the image
285 data, or None if sourcedir does not appear to contains files for
286 building the requested image."""
287
288 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
289 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
290 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700291
Doug Zongkerd5131602012-08-02 14:46:42 -0700292 if info_dict is None:
293 info_dict = OPTIONS.info_dict
294
Doug Zongkereef39442009-04-02 12:14:19 -0700295 ramdisk_img = tempfile.NamedTemporaryFile()
296 img = tempfile.NamedTemporaryFile()
297
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700298 if os.access(fs_config_file, os.F_OK):
299 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
300 else:
301 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
302 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700303 p2 = Run(["minigzip"],
304 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700305
306 p2.wait()
307 p1.wait()
308 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700309 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700310
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800311 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
312 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
313
314 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700315
Benoit Fradina45a8682014-07-14 21:00:43 +0200316 fn = os.path.join(sourcedir, "second")
317 if os.access(fn, os.F_OK):
318 cmd.append("--second")
319 cmd.append(fn)
320
Doug Zongker171f1cd2009-06-15 22:36:37 -0700321 fn = os.path.join(sourcedir, "cmdline")
322 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700323 cmd.append("--cmdline")
324 cmd.append(open(fn).read().rstrip("\n"))
325
326 fn = os.path.join(sourcedir, "base")
327 if os.access(fn, os.F_OK):
328 cmd.append("--base")
329 cmd.append(open(fn).read().rstrip("\n"))
330
Ying Wang4de6b5b2010-08-25 14:29:34 -0700331 fn = os.path.join(sourcedir, "pagesize")
332 if os.access(fn, os.F_OK):
333 cmd.append("--pagesize")
334 cmd.append(open(fn).read().rstrip("\n"))
335
Doug Zongkerd5131602012-08-02 14:46:42 -0700336 args = info_dict.get("mkbootimg_args", None)
337 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700338 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700339
Doug Zongker38a649f2009-06-17 09:07:09 -0700340 cmd.extend(["--ramdisk", ramdisk_img.name,
341 "--output", img.name])
342
343 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700344 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700345 assert p.returncode == 0, "mkbootimg of %s image failed" % (
346 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700347
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700348 if info_dict.get("verity_key", None):
349 path = "/" + os.path.basename(sourcedir).lower()
Sami Tolvanen8d212ea2014-11-06 20:38:00 -0800350 cmd = ["boot_signer", path, img.name, info_dict["verity_key"] + ".pk8", info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700351 p = Run(cmd, stdout=subprocess.PIPE)
352 p.communicate()
353 assert p.returncode == 0, "boot_signer of %s image failed" % path
354
Doug Zongkereef39442009-04-02 12:14:19 -0700355 img.seek(os.SEEK_SET, 0)
356 data = img.read()
357
358 ramdisk_img.close()
359 img.close()
360
361 return data
362
363
Doug Zongkerd5131602012-08-02 14:46:42 -0700364def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
365 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800366 """Return a File object (with name 'name') with the desired bootable
367 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700368 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
369 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800370 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700371
Doug Zongker55d93282011-01-25 17:03:34 -0800372 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
373 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700374 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800375 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700376
377 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
378 if os.path.exists(prebuilt_path):
379 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
380 return File.FromLocalFile(name, prebuilt_path)
381
382 print "building image from target_files %s..." % (tree_subdir,)
383 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
384 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
385 os.path.join(unpack_dir, fs_config),
386 info_dict)
387 if data:
388 return File(name, data)
389 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800390
Doug Zongkereef39442009-04-02 12:14:19 -0700391
Doug Zongker75f17362009-12-08 13:46:44 -0800392def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800393 """Unzip the given archive into a temporary directory and return the name.
394
395 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
396 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
397
398 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
399 main file), open for reading.
400 """
Doug Zongkereef39442009-04-02 12:14:19 -0700401
402 tmp = tempfile.mkdtemp(prefix="targetfiles-")
403 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800404
405 def unzip_to_dir(filename, dirname):
406 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
407 if pattern is not None:
408 cmd.append(pattern)
409 p = Run(cmd, stdout=subprocess.PIPE)
410 p.communicate()
411 if p.returncode != 0:
412 raise ExternalError("failed to unzip input target-files \"%s\"" %
413 (filename,))
414
415 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
416 if m:
417 unzip_to_dir(m.group(1), tmp)
418 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
419 filename = m.group(1)
420 else:
421 unzip_to_dir(filename, tmp)
422
423 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700424
425
426def GetKeyPasswords(keylist):
427 """Given a list of keys, prompt the user to enter passwords for
428 those which require them. Return a {key: password} dict. password
429 will be None if the key has no password."""
430
Doug Zongker8ce7c252009-05-22 13:34:54 -0700431 no_passwords = []
432 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700433 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700434 devnull = open("/dev/null", "w+b")
435 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800436 # We don't need a password for things that aren't really keys.
437 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700438 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700439 continue
440
T.R. Fullhart37e10522013-03-18 10:31:26 -0700441 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700442 "-inform", "DER", "-nocrypt"],
443 stdin=devnull.fileno(),
444 stdout=devnull.fileno(),
445 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700446 p.communicate()
447 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700448 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700449 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700450 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700451 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
452 "-inform", "DER", "-passin", "pass:"],
453 stdin=devnull.fileno(),
454 stdout=devnull.fileno(),
455 stderr=subprocess.PIPE)
456 stdout, stderr = p.communicate()
457 if p.returncode == 0:
458 # Encrypted key with empty string as password.
459 key_passwords[k] = ''
460 elif stderr.startswith('Error decrypting key'):
461 # Definitely encrypted key.
462 # It would have said "Error reading key" if it didn't parse correctly.
463 need_passwords.append(k)
464 else:
465 # Potentially, a type of key that openssl doesn't understand.
466 # We'll let the routines in signapk.jar handle it.
467 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700468 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700469
T.R. Fullhart37e10522013-03-18 10:31:26 -0700470 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700471 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700472 return key_passwords
473
474
Doug Zongker951495f2009-08-14 12:44:19 -0700475def SignFile(input_name, output_name, key, password, align=None,
476 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700477 """Sign the input_name zip/jar/apk, producing output_name. Use the
478 given key and password (the latter may be None if the key does not
479 have a password.
480
481 If align is an integer > 1, zipalign is run to align stored files in
482 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700483
484 If whole_file is true, use the "-w" option to SignApk to embed a
485 signature that covers the whole file in the archive comment of the
486 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700487 """
Doug Zongker951495f2009-08-14 12:44:19 -0700488
Doug Zongkereef39442009-04-02 12:14:19 -0700489 if align == 0 or align == 1:
490 align = None
491
492 if align:
493 temp = tempfile.NamedTemporaryFile()
494 sign_name = temp.name
495 else:
496 sign_name = output_name
497
Baligh Uddin339ee492014-09-05 11:18:07 -0700498 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700499 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
500 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700501 if whole_file:
502 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700503 cmd.extend([key + OPTIONS.public_key_suffix,
504 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700505 input_name, sign_name])
506
507 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700508 if password is not None:
509 password += "\n"
510 p.communicate(password)
511 if p.returncode != 0:
512 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
513
514 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700515 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700516 p.communicate()
517 if p.returncode != 0:
518 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
519 temp.close()
520
521
Doug Zongker37974732010-09-16 17:44:38 -0700522def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700523 """Check the data string passed against the max size limit, if
524 any, for the given target. Raise exception if the data is too big.
525 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700526
Doug Zongker1684d9c2010-09-17 07:44:38 -0700527 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700528 mount_point = "/" + target
529
Ying Wangf8824af2014-06-03 14:07:27 -0700530 fs_type = None
531 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700532 if info_dict["fstab"]:
533 if mount_point == "/userdata": mount_point = "/data"
534 p = info_dict["fstab"][mount_point]
535 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800536 device = p.device
537 if "/" in device:
538 device = device[device.rfind("/")+1:]
539 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700540 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700541
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700542 if fs_type == "yaffs2":
543 # image size should be increased by 1/64th to account for the
544 # spare area (64 bytes per 2k page)
545 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800546 size = len(data)
547 pct = float(size) * 100.0 / limit
548 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
549 if pct >= 99.0:
550 raise ExternalError(msg)
551 elif pct >= 95.0:
552 print
553 print " WARNING: ", msg
554 print
555 elif OPTIONS.verbose:
556 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700557
558
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800559def ReadApkCerts(tf_zip):
560 """Given a target_files ZipFile, parse the META/apkcerts.txt file
561 and return a {package: cert} dict."""
562 certmap = {}
563 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
564 line = line.strip()
565 if not line: continue
566 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
567 r'private_key="(.*)"$', line)
568 if m:
569 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700570 public_key_suffix_len = len(OPTIONS.public_key_suffix)
571 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800572 if cert in SPECIAL_CERT_STRINGS and not privkey:
573 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700574 elif (cert.endswith(OPTIONS.public_key_suffix) and
575 privkey.endswith(OPTIONS.private_key_suffix) and
576 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
577 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800578 else:
579 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
580 return certmap
581
582
Doug Zongkereef39442009-04-02 12:14:19 -0700583COMMON_DOCSTRING = """
584 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700585 Prepend <dir>/bin to the list of places to search for binaries
586 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700587
Doug Zongker05d3dea2009-06-22 11:32:31 -0700588 -s (--device_specific) <file>
589 Path to the python module containing device-specific
590 releasetools code.
591
Doug Zongker8bec09e2009-11-30 15:37:14 -0800592 -x (--extra) <key=value>
593 Add a key/value pair to the 'extras' dict, which device-specific
594 extension code may look at.
595
Doug Zongkereef39442009-04-02 12:14:19 -0700596 -v (--verbose)
597 Show command lines being executed.
598
599 -h (--help)
600 Display this usage message and exit.
601"""
602
603def Usage(docstring):
604 print docstring.rstrip("\n")
605 print COMMON_DOCSTRING
606
607
608def ParseOptions(argv,
609 docstring,
610 extra_opts="", extra_long_opts=(),
611 extra_option_handler=None):
612 """Parse the options in argv and return any arguments that aren't
613 flags. docstring is the calling module's docstring, to be displayed
614 for errors and -h. extra_opts and extra_long_opts are for flags
615 defined by the caller, which are processed by passing them to
616 extra_option_handler."""
617
618 try:
619 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800620 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700621 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700622 "java_path=", "java_args=", "public_key_suffix=",
623 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700625 except getopt.GetoptError, err:
626 Usage(docstring)
627 print "**", str(err), "**"
628 sys.exit(2)
629
630 path_specified = False
631
632 for o, a in opts:
633 if o in ("-h", "--help"):
634 Usage(docstring)
635 sys.exit()
636 elif o in ("-v", "--verbose"):
637 OPTIONS.verbose = True
638 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700639 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700640 elif o in ("--signapk_path",):
641 OPTIONS.signapk_path = a
642 elif o in ("--extra_signapk_args",):
643 OPTIONS.extra_signapk_args = shlex.split(a)
644 elif o in ("--java_path",):
645 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700646 elif o in ("--java_args",):
647 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700648 elif o in ("--public_key_suffix",):
649 OPTIONS.public_key_suffix = a
650 elif o in ("--private_key_suffix",):
651 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700652 elif o in ("-s", "--device_specific"):
653 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800654 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800655 key, value = a.split("=", 1)
656 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700657 else:
658 if extra_option_handler is None or not extra_option_handler(o, a):
659 assert False, "unknown option \"%s\"" % (o,)
660
Doug Zongker85448772014-09-09 14:59:20 -0700661 if OPTIONS.search_path:
662 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
663 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700664
665 return args
666
667
Doug Zongkerfc44a512014-08-26 13:10:25 -0700668def MakeTempFile(prefix=None, suffix=None):
669 """Make a temp file and add it to the list of things to be deleted
670 when Cleanup() is called. Return the filename."""
671 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
672 os.close(fd)
673 OPTIONS.tempfiles.append(fn)
674 return fn
675
676
Doug Zongkereef39442009-04-02 12:14:19 -0700677def Cleanup():
678 for i in OPTIONS.tempfiles:
679 if os.path.isdir(i):
680 shutil.rmtree(i)
681 else:
682 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700683
684
685class PasswordManager(object):
686 def __init__(self):
687 self.editor = os.getenv("EDITOR", None)
688 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
689
690 def GetPasswords(self, items):
691 """Get passwords corresponding to each string in 'items',
692 returning a dict. (The dict may have keys in addition to the
693 values in 'items'.)
694
695 Uses the passwords in $ANDROID_PW_FILE if available, letting the
696 user edit that file to add more needed passwords. If no editor is
697 available, or $ANDROID_PW_FILE isn't define, prompts the user
698 interactively in the ordinary way.
699 """
700
701 current = self.ReadFile()
702
703 first = True
704 while True:
705 missing = []
706 for i in items:
707 if i not in current or not current[i]:
708 missing.append(i)
709 # Are all the passwords already in the file?
710 if not missing: return current
711
712 for i in missing:
713 current[i] = ""
714
715 if not first:
716 print "key file %s still missing some passwords." % (self.pwfile,)
717 answer = raw_input("try to edit again? [y]> ").strip()
718 if answer and answer[0] not in 'yY':
719 raise RuntimeError("key passwords unavailable")
720 first = False
721
722 current = self.UpdateAndReadFile(current)
723
724 def PromptResult(self, current):
725 """Prompt the user to enter a value (password) for each key in
726 'current' whose value is fales. Returns a new dict with all the
727 values.
728 """
729 result = {}
730 for k, v in sorted(current.iteritems()):
731 if v:
732 result[k] = v
733 else:
734 while True:
735 result[k] = getpass.getpass("Enter password for %s key> "
736 % (k,)).strip()
737 if result[k]: break
738 return result
739
740 def UpdateAndReadFile(self, current):
741 if not self.editor or not self.pwfile:
742 return self.PromptResult(current)
743
744 f = open(self.pwfile, "w")
745 os.chmod(self.pwfile, 0600)
746 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
747 f.write("# (Additional spaces are harmless.)\n\n")
748
749 first_line = None
750 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
751 sorted.sort()
752 for i, (_, k, v) in enumerate(sorted):
753 f.write("[[[ %s ]]] %s\n" % (v, k))
754 if not v and first_line is None:
755 # position cursor on first line with no password.
756 first_line = i + 4
757 f.close()
758
759 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
760 _, _ = p.communicate()
761
762 return self.ReadFile()
763
764 def ReadFile(self):
765 result = {}
766 if self.pwfile is None: return result
767 try:
768 f = open(self.pwfile, "r")
769 for line in f:
770 line = line.strip()
771 if not line or line[0] == '#': continue
772 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
773 if not m:
774 print "failed to parse password file: ", line
775 else:
776 result[m.group(2)] = m.group(1)
777 f.close()
778 except IOError, e:
779 if e.errno != errno.ENOENT:
780 print "error reading password file: ", str(e)
781 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700782
783
Geremy Condra36bd3652014-02-06 19:45:10 -0800784def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700785 # use a fixed timestamp so the output is repeatable.
786 zinfo = zipfile.ZipInfo(filename=filename,
787 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800788 if compression is None:
789 zinfo.compress_type = zip.compression
790 else:
791 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700792 zinfo.external_attr = perms << 16
793 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700794
795
796class DeviceSpecificParams(object):
797 module = None
798 def __init__(self, **kwargs):
799 """Keyword arguments to the constructor become attributes of this
800 object, which is passed to all functions in the device-specific
801 module."""
802 for k, v in kwargs.iteritems():
803 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800804 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700805
806 if self.module is None:
807 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700808 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700809 try:
810 if os.path.isdir(path):
811 info = imp.find_module("releasetools", [path])
812 else:
813 d, f = os.path.split(path)
814 b, x = os.path.splitext(f)
815 if x == ".py":
816 f = b
817 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800818 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700819 self.module = imp.load_module("device_specific", *info)
820 except ImportError:
821 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700822
823 def _DoCall(self, function_name, *args, **kwargs):
824 """Call the named function in the device-specific module, passing
825 the given args and kwargs. The first argument to the call will be
826 the DeviceSpecific object itself. If there is no module, or the
827 module does not define the function, return the value of the
828 'default' kwarg (which itself defaults to None)."""
829 if self.module is None or not hasattr(self.module, function_name):
830 return kwargs.get("default", None)
831 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
832
833 def FullOTA_Assertions(self):
834 """Called after emitting the block of assertions at the top of a
835 full OTA package. Implementations can add whatever additional
836 assertions they like."""
837 return self._DoCall("FullOTA_Assertions")
838
Doug Zongkere5ff5902012-01-17 10:55:37 -0800839 def FullOTA_InstallBegin(self):
840 """Called at the start of full OTA installation."""
841 return self._DoCall("FullOTA_InstallBegin")
842
Doug Zongker05d3dea2009-06-22 11:32:31 -0700843 def FullOTA_InstallEnd(self):
844 """Called at the end of full OTA installation; typically this is
845 used to install the image for the device's baseband processor."""
846 return self._DoCall("FullOTA_InstallEnd")
847
848 def IncrementalOTA_Assertions(self):
849 """Called after emitting the block of assertions at the top of an
850 incremental OTA package. Implementations can add whatever
851 additional assertions they like."""
852 return self._DoCall("IncrementalOTA_Assertions")
853
Doug Zongkere5ff5902012-01-17 10:55:37 -0800854 def IncrementalOTA_VerifyBegin(self):
855 """Called at the start of the verification phase of incremental
856 OTA installation; additional checks can be placed here to abort
857 the script before any changes are made."""
858 return self._DoCall("IncrementalOTA_VerifyBegin")
859
Doug Zongker05d3dea2009-06-22 11:32:31 -0700860 def IncrementalOTA_VerifyEnd(self):
861 """Called at the end of the verification phase of incremental OTA
862 installation; additional checks can be placed here to abort the
863 script before any changes are made."""
864 return self._DoCall("IncrementalOTA_VerifyEnd")
865
Doug Zongkere5ff5902012-01-17 10:55:37 -0800866 def IncrementalOTA_InstallBegin(self):
867 """Called at the start of incremental OTA installation (after
868 verification is complete)."""
869 return self._DoCall("IncrementalOTA_InstallBegin")
870
Doug Zongker05d3dea2009-06-22 11:32:31 -0700871 def IncrementalOTA_InstallEnd(self):
872 """Called at the end of incremental OTA installation; typically
873 this is used to install the image for the device's baseband
874 processor."""
875 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700876
877class File(object):
878 def __init__(self, name, data):
879 self.name = name
880 self.data = data
881 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800882 self.sha1 = sha1(data).hexdigest()
883
884 @classmethod
885 def FromLocalFile(cls, name, diskname):
886 f = open(diskname, "rb")
887 data = f.read()
888 f.close()
889 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700890
891 def WriteToTemp(self):
892 t = tempfile.NamedTemporaryFile()
893 t.write(self.data)
894 t.flush()
895 return t
896
Geremy Condra36bd3652014-02-06 19:45:10 -0800897 def AddToZip(self, z, compression=None):
898 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700899
900DIFF_PROGRAM_BY_EXT = {
901 ".gz" : "imgdiff",
902 ".zip" : ["imgdiff", "-z"],
903 ".jar" : ["imgdiff", "-z"],
904 ".apk" : ["imgdiff", "-z"],
905 ".img" : "imgdiff",
906 }
907
908class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700909 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700910 self.tf = tf
911 self.sf = sf
912 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700913 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700914
915 def ComputePatch(self):
916 """Compute the patch (as a string of data) needed to turn sf into
917 tf. Returns the same tuple as GetPatch()."""
918
919 tf = self.tf
920 sf = self.sf
921
Doug Zongker24cd2802012-08-14 16:36:15 -0700922 if self.diff_program:
923 diff_program = self.diff_program
924 else:
925 ext = os.path.splitext(tf.name)[1]
926 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700927
928 ttemp = tf.WriteToTemp()
929 stemp = sf.WriteToTemp()
930
931 ext = os.path.splitext(tf.name)[1]
932
933 try:
934 ptemp = tempfile.NamedTemporaryFile()
935 if isinstance(diff_program, list):
936 cmd = copy.copy(diff_program)
937 else:
938 cmd = [diff_program]
939 cmd.append(stemp.name)
940 cmd.append(ttemp.name)
941 cmd.append(ptemp.name)
942 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700943 err = []
944 def run():
945 _, e = p.communicate()
946 if e: err.append(e)
947 th = threading.Thread(target=run)
948 th.start()
949 th.join(timeout=300) # 5 mins
950 if th.is_alive():
951 print "WARNING: diff command timed out"
952 p.terminate()
953 th.join(5)
954 if th.is_alive():
955 p.kill()
956 th.join()
957
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700958 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700959 print "WARNING: failure running %s:\n%s\n" % (
960 diff_program, "".join(err))
961 self.patch = None
962 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700963 diff = ptemp.read()
964 finally:
965 ptemp.close()
966 stemp.close()
967 ttemp.close()
968
969 self.patch = diff
970 return self.tf, self.sf, self.patch
971
972
973 def GetPatch(self):
974 """Return a tuple (target_file, source_file, patch_data).
975 patch_data may be None if ComputePatch hasn't been called, or if
976 computing the patch failed."""
977 return self.tf, self.sf, self.patch
978
979
980def ComputeDifferences(diffs):
981 """Call ComputePatch on all the Difference objects in 'diffs'."""
982 print len(diffs), "diffs to compute"
983
984 # Do the largest files first, to try and reduce the long-pole effect.
985 by_size = [(i.tf.size, i) for i in diffs]
986 by_size.sort(reverse=True)
987 by_size = [i[1] for i in by_size]
988
989 lock = threading.Lock()
990 diff_iter = iter(by_size) # accessed under lock
991
992 def worker():
993 try:
994 lock.acquire()
995 for d in diff_iter:
996 lock.release()
997 start = time.time()
998 d.ComputePatch()
999 dur = time.time() - start
1000 lock.acquire()
1001
1002 tf, sf, patch = d.GetPatch()
1003 if sf.name == tf.name:
1004 name = tf.name
1005 else:
1006 name = "%s (%s)" % (tf.name, sf.name)
1007 if patch is None:
1008 print "patching failed! %s" % (name,)
1009 else:
1010 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1011 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1012 lock.release()
1013 except Exception, e:
1014 print e
1015 raise
1016
1017 # start worker threads; wait for them all to finish.
1018 threads = [threading.Thread(target=worker)
1019 for i in range(OPTIONS.worker_threads)]
1020 for th in threads:
1021 th.start()
1022 while threads:
1023 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001024
1025
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001026class BlockDifference:
Tao Baodd2a5892015-03-12 12:32:37 -07001027 def __init__(self, partition, tgt, src=None, check_first_block=False, version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001028 self.tgt = tgt
1029 self.src = src
1030 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001031 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001032
Tao Baodd2a5892015-03-12 12:32:37 -07001033 if version is None:
1034 version = 1
1035 if OPTIONS.info_dict:
1036 version = max(
1037 int(i) for i in
1038 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1039 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001040
1041 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001042 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001043 tmpdir = tempfile.mkdtemp()
1044 OPTIONS.tempfiles.append(tmpdir)
1045 self.path = os.path.join(tmpdir, partition)
1046 b.Compute(self.path)
1047
1048 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1049
1050 def WriteScript(self, script, output_zip, progress=None):
1051 if not self.src:
1052 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001053 script.Print("Patching %s image unconditionally..." % (self.partition,))
1054 else:
1055 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001056
Jesse Zhao75bcea02015-01-06 10:59:53 -08001057 if progress: script.ShowProgress(progress, 0)
1058 self._WriteUpdate(script, output_zip)
1059
1060 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001061 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001062 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001063 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001064 else:
Michael Runge910b0052015-02-11 19:28:08 -08001065 if self.version >= 3:
1066 script.AppendExtra(('if block_image_verify("%s", '
1067 'package_extract_file("%s.transfer.list"), '
1068 '"%s.new.dat", "%s.patch.dat") then') %
1069 (self.device, partition, partition, partition))
1070 else:
1071 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1072 (self.device, self.src.care_map.to_string_raw(),
1073 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001074 script.Print('Verified %s image...' % (partition,))
Sami Tolvanendd67a292014-12-09 16:40:34 +00001075 script.AppendExtra('else');
1076
Tao Baodd2a5892015-03-12 12:32:37 -07001077 # When generating incrementals for the system and vendor partitions,
1078 # explicitly check the first block (which contains the superblock) of
1079 # the partition to see if it's what we expect. If this check fails,
1080 # give an explicit log message about the partition having been
1081 # remounted R/W (the most likely explanation) and the need to flash to
1082 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001083 if self.check_first_block:
1084 self._CheckFirstBlock(script)
1085
Tao Baodd2a5892015-03-12 12:32:37 -07001086 # Abort the OTA update. Note that the incremental OTA cannot be applied
1087 # even if it may match the checksum of the target partition.
1088 # a) If version < 3, operations like move and erase will make changes
1089 # unconditionally and damage the partition.
1090 # b) If version >= 3, it won't even reach here.
1091 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1092 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001093
1094 def _WriteUpdate(self, script, output_zip):
Justin Harrison2de68bb2015-02-13 18:47:51 +00001095 partition = self.partition
1096 with open(self.path + ".transfer.list", "rb") as f:
1097 ZipWriteStr(output_zip, partition + ".transfer.list", f.read())
1098 with open(self.path + ".new.dat", "rb") as f:
1099 ZipWriteStr(output_zip, partition + ".new.dat", f.read())
1100 with open(self.path + ".patch.dat", "rb") as f:
1101 ZipWriteStr(output_zip, partition + ".patch.dat", f.read(),
1102 compression=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001103
Justin Harrison2de68bb2015-02-13 18:47:51 +00001104 call = (('block_image_update("%s", '
1105 'package_extract_file("%s.transfer.list"), '
1106 '"%s.new.dat", "%s.patch.dat");\n') %
1107 (self.device, partition, partition, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001108 script.AppendExtra(script._WordWrap(call))
1109
Sami Tolvanendd67a292014-12-09 16:40:34 +00001110 def _HashBlocks(self, source, ranges):
1111 data = source.ReadRangeSet(ranges)
1112 ctx = sha1()
1113
1114 for p in data:
1115 ctx.update(p)
1116
1117 return ctx.hexdigest()
1118
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001119 def _CheckFirstBlock(self, script):
1120 r = RangeSet((0, 1))
Sami Tolvanendd67a292014-12-09 16:40:34 +00001121 srchash = self._HashBlocks(self.src, r);
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001122
1123 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1124 'abort("%s has been remounted R/W; '
1125 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001126 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001127 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001128
1129DataImage = blockimgdiff.DataImage
1130
1131
Doug Zongker96a57e72010-09-26 14:57:41 -07001132# map recovery.fstab's fs_types to mount/format "partition types"
1133PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001134 "ext4": "EMMC", "emmc": "EMMC",
1135 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001136
1137def GetTypeAndDevice(mount_point, info):
1138 fstab = info["fstab"]
1139 if fstab:
1140 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1141 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001142 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001143
1144
1145def ParseCertificate(data):
1146 """Parse a PEM-format certificate."""
1147 cert = []
1148 save = False
1149 for line in data.split("\n"):
1150 if "--END CERTIFICATE--" in line:
1151 break
1152 if save:
1153 cert.append(line)
1154 if "--BEGIN CERTIFICATE--" in line:
1155 save = True
1156 cert = "".join(cert).decode('base64')
1157 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001158
Doug Zongker412c02f2014-02-13 10:58:24 -08001159def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1160 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001161 """Generate a binary patch that creates the recovery image starting
1162 with the boot image. (Most of the space in these images is just the
1163 kernel, which is identical for the two, so the resulting patch
1164 should be efficient.) Add it to the output zip, along with a shell
1165 script that is run from init.rc on first boot to actually do the
1166 patching and install the new recovery image.
1167
1168 recovery_img and boot_img should be File objects for the
1169 corresponding images. info should be the dictionary returned by
1170 common.LoadInfoDict() on the input target_files.
1171 """
1172
Doug Zongker412c02f2014-02-13 10:58:24 -08001173 if info_dict is None:
1174 info_dict = OPTIONS.info_dict
1175
Doug Zongkerc9253822014-02-04 12:17:58 -08001176 diff_program = ["imgdiff"]
1177 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1178 if os.path.exists(path):
1179 diff_program.append("-b")
1180 diff_program.append(path)
1181 bonus_args = "-b /system/etc/recovery-resource.dat"
1182 else:
1183 bonus_args = ""
1184
1185 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1186 _, _, patch = d.ComputePatch()
1187 output_sink("recovery-from-boot.p", patch)
1188
Ying Wanga961a092014-07-29 11:42:37 -07001189 td_pair = GetTypeAndDevice("/boot", info_dict)
1190 if not td_pair:
1191 return
1192 boot_type, boot_device = td_pair
1193 td_pair = GetTypeAndDevice("/recovery", info_dict)
1194 if not td_pair:
1195 return
1196 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001197
1198 sh = """#!/system/bin/sh
1199if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1200 applypatch %(bonus_args)s %(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 && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1201else
1202 log -t recovery "Recovery image already installed"
1203fi
1204""" % { 'boot_size': boot_img.size,
1205 'boot_sha1': boot_img.sha1,
1206 'recovery_size': recovery_img.size,
1207 'recovery_sha1': recovery_img.sha1,
1208 'boot_type': boot_type,
1209 'boot_device': boot_device,
1210 'recovery_type': recovery_type,
1211 'recovery_device': recovery_device,
1212 'bonus_args': bonus_args,
1213 }
1214
1215 # The install script location moved from /system/etc to /system/bin
1216 # in the L release. Parse the init.rc file to find out where the
1217 # target-files expects it to be, and put it there.
1218 sh_location = "etc/install-recovery.sh"
1219 try:
1220 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1221 for line in f:
1222 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1223 if m:
1224 sh_location = m.group(1)
1225 print "putting script in", sh_location
1226 break
1227 except (OSError, IOError), e:
1228 print "failed to read init.rc: %s" % (e,)
1229
1230 output_sink(sh_location, sh)