blob: 8941f890ff3c78cdc229626def09f78a9919ce6e [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:
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001027 def __init__(self, partition, tgt, src=None, check_first_block=False):
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
Doug Zongker62338182014-09-08 08:29:55 -07001033 version = 1
1034 if OPTIONS.info_dict:
1035 version = max(
1036 int(i) for i in
1037 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1038
1039 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1040 version=version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001041 tmpdir = tempfile.mkdtemp()
1042 OPTIONS.tempfiles.append(tmpdir)
1043 self.path = os.path.join(tmpdir, partition)
1044 b.Compute(self.path)
1045
1046 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1047
1048 def WriteScript(self, script, output_zip, progress=None):
1049 if not self.src:
1050 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001051 script.Print("Patching %s image unconditionally..." % (self.partition,))
1052 else:
1053 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001054
Jesse Zhao75bcea02015-01-06 10:59:53 -08001055 if progress: script.ShowProgress(progress, 0)
1056 self._WriteUpdate(script, output_zip)
1057
1058 def WriteVerifyScript(self, script):
1059 if not self.src:
1060 script.Print("Image %s will be patched unconditionally." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001061 else:
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001062 if self.check_first_block:
1063 self._CheckFirstBlock(script)
1064
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001065 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1066 (self.device, self.src.care_map.to_string_raw(),
1067 self.src.TotalSha1()))
Jesse Zhao75bcea02015-01-06 10:59:53 -08001068 script.Print("Verified %s image..." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001069 script.AppendExtra(('else\n'
1070 ' (range_sha1("%s", "%s") == "%s") ||\n'
1071 ' abort("%s partition has unexpected contents");\n'
1072 'endif;') %
1073 (self.device, self.tgt.care_map.to_string_raw(),
1074 self.tgt.TotalSha1(), self.partition))
1075
1076 def _WriteUpdate(self, script, output_zip):
1077 partition = self.partition
1078 with open(self.path + ".transfer.list", "rb") as f:
1079 ZipWriteStr(output_zip, partition + ".transfer.list", f.read())
1080 with open(self.path + ".new.dat", "rb") as f:
1081 ZipWriteStr(output_zip, partition + ".new.dat", f.read())
1082 with open(self.path + ".patch.dat", "rb") as f:
1083 ZipWriteStr(output_zip, partition + ".patch.dat", f.read(),
1084 compression=zipfile.ZIP_STORED)
1085
1086 call = (('block_image_update("%s", '
1087 'package_extract_file("%s.transfer.list"), '
1088 '"%s.new.dat", "%s.patch.dat");\n') %
1089 (self.device, partition, partition, partition))
1090 script.AppendExtra(script._WordWrap(call))
1091
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001092 def _CheckFirstBlock(self, script):
1093 r = RangeSet((0, 1))
1094 h = sha1()
1095 for data in self.src.ReadRangeSet(r):
1096 h.update(data)
1097 h = h.hexdigest()
1098
1099 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1100 'abort("%s has been remounted R/W; '
1101 'reflash device to reenable OTA updates");')
1102 % (self.device, r.to_string_raw(), h, self.device))
1103
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001104
1105DataImage = blockimgdiff.DataImage
1106
1107
Doug Zongker96a57e72010-09-26 14:57:41 -07001108# map recovery.fstab's fs_types to mount/format "partition types"
1109PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001110 "ext4": "EMMC", "emmc": "EMMC",
1111 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001112
1113def GetTypeAndDevice(mount_point, info):
1114 fstab = info["fstab"]
1115 if fstab:
1116 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1117 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001118 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001119
1120
1121def ParseCertificate(data):
1122 """Parse a PEM-format certificate."""
1123 cert = []
1124 save = False
1125 for line in data.split("\n"):
1126 if "--END CERTIFICATE--" in line:
1127 break
1128 if save:
1129 cert.append(line)
1130 if "--BEGIN CERTIFICATE--" in line:
1131 save = True
1132 cert = "".join(cert).decode('base64')
1133 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001134
Doug Zongker412c02f2014-02-13 10:58:24 -08001135def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1136 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001137 """Generate a binary patch that creates the recovery image starting
1138 with the boot image. (Most of the space in these images is just the
1139 kernel, which is identical for the two, so the resulting patch
1140 should be efficient.) Add it to the output zip, along with a shell
1141 script that is run from init.rc on first boot to actually do the
1142 patching and install the new recovery image.
1143
1144 recovery_img and boot_img should be File objects for the
1145 corresponding images. info should be the dictionary returned by
1146 common.LoadInfoDict() on the input target_files.
1147 """
1148
Doug Zongker412c02f2014-02-13 10:58:24 -08001149 if info_dict is None:
1150 info_dict = OPTIONS.info_dict
1151
Doug Zongkerc9253822014-02-04 12:17:58 -08001152 diff_program = ["imgdiff"]
1153 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1154 if os.path.exists(path):
1155 diff_program.append("-b")
1156 diff_program.append(path)
1157 bonus_args = "-b /system/etc/recovery-resource.dat"
1158 else:
1159 bonus_args = ""
1160
1161 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1162 _, _, patch = d.ComputePatch()
1163 output_sink("recovery-from-boot.p", patch)
1164
Ying Wanga961a092014-07-29 11:42:37 -07001165 td_pair = GetTypeAndDevice("/boot", info_dict)
1166 if not td_pair:
1167 return
1168 boot_type, boot_device = td_pair
1169 td_pair = GetTypeAndDevice("/recovery", info_dict)
1170 if not td_pair:
1171 return
1172 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001173
1174 sh = """#!/system/bin/sh
1175if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1176 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"
1177else
1178 log -t recovery "Recovery image already installed"
1179fi
1180""" % { 'boot_size': boot_img.size,
1181 'boot_sha1': boot_img.sha1,
1182 'recovery_size': recovery_img.size,
1183 'recovery_sha1': recovery_img.sha1,
1184 'boot_type': boot_type,
1185 'boot_device': boot_device,
1186 'recovery_type': recovery_type,
1187 'recovery_device': recovery_device,
1188 'bonus_args': bonus_args,
1189 }
1190
1191 # The install script location moved from /system/etc to /system/bin
1192 # in the L release. Parse the init.rc file to find out where the
1193 # target-files expects it to be, and put it there.
1194 sh_location = "etc/install-recovery.sh"
1195 try:
1196 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1197 for line in f:
1198 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1199 if m:
1200 sh_location = m.group(1)
1201 print "putting script in", sh_location
1202 break
1203 except (OSError, IOError), e:
1204 print "failed to read init.rc: %s" % (e,)
1205
1206 output_sink(sh_location, sh)