blob: 592ed19ad8aa7cb5e05fb986091d0ab8f8aba911 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
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
Dan Albert8b72aef2015-03-23 19:13:21 -070041class Options(object):
42 def __init__(self):
43 platform_search_path = {
44 "linux2": "out/host/linux-x86",
45 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070046 }
Doug Zongker85448772014-09-09 14:59:20 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.search_path = platform_search_path.get(sys.platform, None)
49 self.signapk_path = "framework/signapk.jar" # Relative to search_path
50 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
52 self.java_args = "-Xmx2048m" # JVM Args
53 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
55 self.verbose = False
56 self.tempfiles = []
57 self.device_specific = None
58 self.extras = {}
59 self.info_dict = None
60 self.worker_threads = None
61
62
63OPTIONS = Options()
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
Dan Albert8b72aef2015-03-23 19:13:21 -070070class ExternalError(RuntimeError):
71 pass
Doug Zongkereef39442009-04-02 12:14:19 -070072
73
74def Run(args, **kwargs):
75 """Create and return a subprocess.Popen object, printing the command
76 line on the terminal if -v was specified."""
77 if OPTIONS.verbose:
78 print " running: ", " ".join(args)
79 return subprocess.Popen(args, **kwargs)
80
81
Ying Wang7e6d4e42010-12-13 16:25:36 -080082def CloseInheritedPipes():
83 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
84 before doing other work."""
85 if platform.system() != "Darwin":
86 return
87 for d in range(3, 1025):
88 try:
89 stat = os.fstat(d)
90 if stat is not None:
91 pipebit = stat[0] & 0x1000
92 if pipebit != 0:
93 os.close(d)
94 except OSError:
95 pass
96
97
Dan Albert8b72aef2015-03-23 19:13:21 -070098def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070099 """Read and parse the META/misc_info.txt key/value pairs from the
100 input target files and return a dict."""
101
Doug Zongkerc9253822014-02-04 12:17:58 -0800102 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700103 if isinstance(input_file, zipfile.ZipFile):
104 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800105 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700106 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 try:
108 with open(path) as f:
109 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800111 if e.errno == errno.ENOENT:
112 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700113 d = {}
114 try:
Michael Runge6e836112014-04-15 17:40:21 -0700115 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700116 except KeyError:
117 # ok if misc_info.txt doesn't exist
118 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700119
Doug Zongker37974732010-09-16 17:44:38 -0700120 # backwards compatibility: These values used to be in their own
121 # files. Look for them, in case we're processing an old
122 # target_files zip.
123
124 if "mkyaffs2_extra_flags" not in d:
125 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700126 d["mkyaffs2_extra_flags"] = read_helper(
127 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700128 except KeyError:
129 # ok if flags don't exist
130 pass
131
132 if "recovery_api_version" not in d:
133 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700134 d["recovery_api_version"] = read_helper(
135 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700136 except KeyError:
137 raise ValueError("can't find recovery API version in input target-files")
138
139 if "tool_extensions" not in d:
140 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800141 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700142 except KeyError:
143 # ok if extensions don't exist
144 pass
145
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800146 if "fstab_version" not in d:
147 d["fstab_version"] = "1"
148
Doug Zongker37974732010-09-16 17:44:38 -0700149 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800150 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700151 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700152 if not line:
153 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700154 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700155 if not value:
156 continue
Doug Zongker37974732010-09-16 17:44:38 -0700157 if name == "blocksize":
158 d[name] = value
159 else:
160 d[name + "_size"] = value
161 except KeyError:
162 pass
163
164 def makeint(key):
165 if key in d:
166 d[key] = int(d[key], 0)
167
168 makeint("recovery_api_version")
169 makeint("blocksize")
170 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700171 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700172 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700173 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("recovery_size")
175 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800176 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700177
Doug Zongkerc9253822014-02-04 12:17:58 -0800178 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
179 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700180 return d
181
Doug Zongkerc9253822014-02-04 12:17:58 -0800182def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700183 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800184 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 except KeyError:
186 print "Warning: could not find SYSTEM/build.prop in %s" % zip
187 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700188 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700189
Michael Runge6e836112014-04-15 17:40:21 -0700190def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700192 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700194 if not line or line.startswith("#"):
195 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700196 if "=" in line:
197 name, value = line.split("=", 1)
198 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700199 return d
200
Doug Zongkerc9253822014-02-04 12:17:58 -0800201def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700202 class Partition(object):
Dan Albert8b72aef2015-03-23 19:13:21 -0700203 def __init__(self, mount_point, fs_type, device, length, device2):
204 self.mount_point = mount_point
205 self.fs_type = fs_type
206 self.device = device
207 self.length = length
208 self.device2 = device2
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700209
210 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800211 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700214 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800216 if fstab_version == 1:
217 d = {}
218 for line in data.split("\n"):
219 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700220 if not line or line.startswith("#"):
221 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800222 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 options = None
226 if len(pieces) >= 4:
227 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800229 if len(pieces) >= 5:
230 options = pieces[4]
231 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700232 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700236
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 mount_point = pieces[0]
238 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800239 if options:
240 options = options.split(",")
241 for i in options:
242 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246
Dan Albert8b72aef2015-03-23 19:13:21 -0700247 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
248 device=pieces[2], length=length,
249 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250
251 elif fstab_version == 2:
252 d = {}
253 for line in data.split("\n"):
254 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700255 if not line or line.startswith("#"):
256 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 pieces = line.split()
258 if len(pieces) != 5:
259 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
260
261 # Ignore entries that are managed by vold
262 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700263 if "voldmanaged=" in options:
264 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800265
266 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800268 options = options.split(",")
269 for i in options:
270 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800273 # Ignore all unknown options in the unified fstab
274 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800275
Dan Albert8b72aef2015-03-23 19:13:21 -0700276 mount_point = pieces[1]
277 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
278 device=pieces[0], length=length, device2=None)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279
280 else:
281 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
282
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700283 return d
284
285
Doug Zongker37974732010-09-16 17:44:38 -0700286def DumpInfoDict(d):
287 for k, v in sorted(d.items()):
288 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700289
Dan Albert8b72aef2015-03-23 19:13:21 -0700290
Doug Zongkerd5131602012-08-02 14:46:42 -0700291def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700292 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700293 'sourcedir'), and turn them into a boot image. Return the image
294 data, or None if sourcedir does not appear to contains files for
295 building the requested image."""
296
297 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
298 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
299 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700300
Doug Zongkerd5131602012-08-02 14:46:42 -0700301 if info_dict is None:
302 info_dict = OPTIONS.info_dict
303
Doug Zongkereef39442009-04-02 12:14:19 -0700304 ramdisk_img = tempfile.NamedTemporaryFile()
305 img = tempfile.NamedTemporaryFile()
306
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700307 if os.access(fs_config_file, os.F_OK):
308 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
309 else:
310 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
311 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700312 p2 = Run(["minigzip"],
313 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700314
315 p2.wait()
316 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
318 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700319
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800320 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
321 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
322
323 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700324
Benoit Fradina45a8682014-07-14 21:00:43 +0200325 fn = os.path.join(sourcedir, "second")
326 if os.access(fn, os.F_OK):
327 cmd.append("--second")
328 cmd.append(fn)
329
Doug Zongker171f1cd2009-06-15 22:36:37 -0700330 fn = os.path.join(sourcedir, "cmdline")
331 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700332 cmd.append("--cmdline")
333 cmd.append(open(fn).read().rstrip("\n"))
334
335 fn = os.path.join(sourcedir, "base")
336 if os.access(fn, os.F_OK):
337 cmd.append("--base")
338 cmd.append(open(fn).read().rstrip("\n"))
339
Ying Wang4de6b5b2010-08-25 14:29:34 -0700340 fn = os.path.join(sourcedir, "pagesize")
341 if os.access(fn, os.F_OK):
342 cmd.append("--pagesize")
343 cmd.append(open(fn).read().rstrip("\n"))
344
Doug Zongkerd5131602012-08-02 14:46:42 -0700345 args = info_dict.get("mkbootimg_args", None)
346 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700347 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700348
Tao Baod95e9fd2015-03-29 23:07:41 -0700349 img_unsigned = None
350 if info_dict.get("vboot", None):
351 img_unsigned = tempfile.NamedTemporaryFile()
352 cmd.extend(["--ramdisk", ramdisk_img.name,
353 "--output", img_unsigned.name])
354 else:
355 cmd.extend(["--ramdisk", ramdisk_img.name,
356 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700357
358 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700359 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700360 assert p.returncode == 0, "mkbootimg of %s image failed" % (
361 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700362
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700363 if info_dict.get("verity_key", None):
364 path = "/" + os.path.basename(sourcedir).lower()
Dan Albert8b72aef2015-03-23 19:13:21 -0700365 cmd = ["boot_signer", path, img.name, info_dict["verity_key"] + ".pk8",
366 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700367 p = Run(cmd, stdout=subprocess.PIPE)
368 p.communicate()
369 assert p.returncode == 0, "boot_signer of %s image failed" % path
370
Tao Baod95e9fd2015-03-29 23:07:41 -0700371 # Sign the image if vboot is non-empty.
372 elif info_dict.get("vboot", None):
373 path = "/" + os.path.basename(sourcedir).lower()
374 img_keyblock = tempfile.NamedTemporaryFile()
375 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
376 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
377 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
378 img.name]
379 p = Run(cmd, stdout=subprocess.PIPE)
380 p.communicate()
381 assert p.returncode == 0, "vboot_signer of %s image failed" % path
382
Doug Zongkereef39442009-04-02 12:14:19 -0700383 img.seek(os.SEEK_SET, 0)
384 data = img.read()
385
386 ramdisk_img.close()
387 img.close()
388
389 return data
390
391
Doug Zongkerd5131602012-08-02 14:46:42 -0700392def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
393 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800394 """Return a File object (with name 'name') with the desired bootable
395 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700396 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
397 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800398 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Doug Zongker55d93282011-01-25 17:03:34 -0800400 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
401 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700402 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800403 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700404
405 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
406 if os.path.exists(prebuilt_path):
407 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
408 return File.FromLocalFile(name, prebuilt_path)
409
410 print "building image from target_files %s..." % (tree_subdir,)
411 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
412 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
413 os.path.join(unpack_dir, fs_config),
414 info_dict)
415 if data:
416 return File(name, data)
417 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800418
Doug Zongkereef39442009-04-02 12:14:19 -0700419
Doug Zongker75f17362009-12-08 13:46:44 -0800420def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800421 """Unzip the given archive into a temporary directory and return the name.
422
423 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
424 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
425
426 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
427 main file), open for reading.
428 """
Doug Zongkereef39442009-04-02 12:14:19 -0700429
430 tmp = tempfile.mkdtemp(prefix="targetfiles-")
431 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800432
433 def unzip_to_dir(filename, dirname):
434 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
435 if pattern is not None:
436 cmd.append(pattern)
437 p = Run(cmd, stdout=subprocess.PIPE)
438 p.communicate()
439 if p.returncode != 0:
440 raise ExternalError("failed to unzip input target-files \"%s\"" %
441 (filename,))
442
443 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
444 if m:
445 unzip_to_dir(m.group(1), tmp)
446 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
447 filename = m.group(1)
448 else:
449 unzip_to_dir(filename, tmp)
450
451 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700452
453
454def GetKeyPasswords(keylist):
455 """Given a list of keys, prompt the user to enter passwords for
456 those which require them. Return a {key: password} dict. password
457 will be None if the key has no password."""
458
Doug Zongker8ce7c252009-05-22 13:34:54 -0700459 no_passwords = []
460 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700461 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700462 devnull = open("/dev/null", "w+b")
463 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800464 # We don't need a password for things that aren't really keys.
465 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700466 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700467 continue
468
T.R. Fullhart37e10522013-03-18 10:31:26 -0700469 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700470 "-inform", "DER", "-nocrypt"],
471 stdin=devnull.fileno(),
472 stdout=devnull.fileno(),
473 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700474 p.communicate()
475 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700476 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700477 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700478 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700479 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
480 "-inform", "DER", "-passin", "pass:"],
481 stdin=devnull.fileno(),
482 stdout=devnull.fileno(),
483 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700484 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700485 if p.returncode == 0:
486 # Encrypted key with empty string as password.
487 key_passwords[k] = ''
488 elif stderr.startswith('Error decrypting key'):
489 # Definitely encrypted key.
490 # It would have said "Error reading key" if it didn't parse correctly.
491 need_passwords.append(k)
492 else:
493 # Potentially, a type of key that openssl doesn't understand.
494 # We'll let the routines in signapk.jar handle it.
495 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700496 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700497
T.R. Fullhart37e10522013-03-18 10:31:26 -0700498 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700499 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700500 return key_passwords
501
502
Doug Zongker951495f2009-08-14 12:44:19 -0700503def SignFile(input_name, output_name, key, password, align=None,
504 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700505 """Sign the input_name zip/jar/apk, producing output_name. Use the
506 given key and password (the latter may be None if the key does not
507 have a password.
508
509 If align is an integer > 1, zipalign is run to align stored files in
510 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700511
512 If whole_file is true, use the "-w" option to SignApk to embed a
513 signature that covers the whole file in the archive comment of the
514 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700515 """
Doug Zongker951495f2009-08-14 12:44:19 -0700516
Doug Zongkereef39442009-04-02 12:14:19 -0700517 if align == 0 or align == 1:
518 align = None
519
520 if align:
521 temp = tempfile.NamedTemporaryFile()
522 sign_name = temp.name
523 else:
524 sign_name = output_name
525
Baligh Uddin339ee492014-09-05 11:18:07 -0700526 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700527 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
528 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700529 if whole_file:
530 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700531 cmd.extend([key + OPTIONS.public_key_suffix,
532 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700533 input_name, sign_name])
534
535 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700536 if password is not None:
537 password += "\n"
538 p.communicate(password)
539 if p.returncode != 0:
540 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
541
542 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700543 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700544 p.communicate()
545 if p.returncode != 0:
546 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
547 temp.close()
548
549
Doug Zongker37974732010-09-16 17:44:38 -0700550def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700551 """Check the data string passed against the max size limit, if
552 any, for the given target. Raise exception if the data is too big.
553 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700554
Dan Albert8b72aef2015-03-23 19:13:21 -0700555 if target.endswith(".img"):
556 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700557 mount_point = "/" + target
558
Ying Wangf8824af2014-06-03 14:07:27 -0700559 fs_type = None
560 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700561 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700562 if mount_point == "/userdata":
563 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700564 p = info_dict["fstab"][mount_point]
565 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800566 device = p.device
567 if "/" in device:
568 device = device[device.rfind("/")+1:]
569 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700570 if not fs_type or not limit:
571 return
Doug Zongkereef39442009-04-02 12:14:19 -0700572
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700573 if fs_type == "yaffs2":
574 # image size should be increased by 1/64th to account for the
575 # spare area (64 bytes per 2k page)
576 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800577 size = len(data)
578 pct = float(size) * 100.0 / limit
579 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
580 if pct >= 99.0:
581 raise ExternalError(msg)
582 elif pct >= 95.0:
583 print
584 print " WARNING: ", msg
585 print
586 elif OPTIONS.verbose:
587 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700588
589
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800590def ReadApkCerts(tf_zip):
591 """Given a target_files ZipFile, parse the META/apkcerts.txt file
592 and return a {package: cert} dict."""
593 certmap = {}
594 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
595 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700596 if not line:
597 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800598 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
599 r'private_key="(.*)"$', line)
600 if m:
601 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700602 public_key_suffix_len = len(OPTIONS.public_key_suffix)
603 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800604 if cert in SPECIAL_CERT_STRINGS and not privkey:
605 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700606 elif (cert.endswith(OPTIONS.public_key_suffix) and
607 privkey.endswith(OPTIONS.private_key_suffix) and
608 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
609 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800610 else:
611 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
612 return certmap
613
614
Doug Zongkereef39442009-04-02 12:14:19 -0700615COMMON_DOCSTRING = """
616 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700617 Prepend <dir>/bin to the list of places to search for binaries
618 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700619
Doug Zongker05d3dea2009-06-22 11:32:31 -0700620 -s (--device_specific) <file>
621 Path to the python module containing device-specific
622 releasetools code.
623
Doug Zongker8bec09e2009-11-30 15:37:14 -0800624 -x (--extra) <key=value>
625 Add a key/value pair to the 'extras' dict, which device-specific
626 extension code may look at.
627
Doug Zongkereef39442009-04-02 12:14:19 -0700628 -v (--verbose)
629 Show command lines being executed.
630
631 -h (--help)
632 Display this usage message and exit.
633"""
634
635def Usage(docstring):
636 print docstring.rstrip("\n")
637 print COMMON_DOCSTRING
638
639
640def ParseOptions(argv,
641 docstring,
642 extra_opts="", extra_long_opts=(),
643 extra_option_handler=None):
644 """Parse the options in argv and return any arguments that aren't
645 flags. docstring is the calling module's docstring, to be displayed
646 for errors and -h. extra_opts and extra_long_opts are for flags
647 defined by the caller, which are processed by passing them to
648 extra_option_handler."""
649
650 try:
651 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800652 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700653 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700654 "java_path=", "java_args=", "public_key_suffix=",
655 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700656 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700657 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700658 Usage(docstring)
659 print "**", str(err), "**"
660 sys.exit(2)
661
Doug Zongkereef39442009-04-02 12:14:19 -0700662 for o, a in opts:
663 if o in ("-h", "--help"):
664 Usage(docstring)
665 sys.exit()
666 elif o in ("-v", "--verbose"):
667 OPTIONS.verbose = True
668 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700669 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700670 elif o in ("--signapk_path",):
671 OPTIONS.signapk_path = a
672 elif o in ("--extra_signapk_args",):
673 OPTIONS.extra_signapk_args = shlex.split(a)
674 elif o in ("--java_path",):
675 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700676 elif o in ("--java_args",):
677 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700678 elif o in ("--public_key_suffix",):
679 OPTIONS.public_key_suffix = a
680 elif o in ("--private_key_suffix",):
681 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700682 elif o in ("-s", "--device_specific"):
683 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800684 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800685 key, value = a.split("=", 1)
686 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700687 else:
688 if extra_option_handler is None or not extra_option_handler(o, a):
689 assert False, "unknown option \"%s\"" % (o,)
690
Doug Zongker85448772014-09-09 14:59:20 -0700691 if OPTIONS.search_path:
692 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
693 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700694
695 return args
696
697
Doug Zongkerfc44a512014-08-26 13:10:25 -0700698def MakeTempFile(prefix=None, suffix=None):
699 """Make a temp file and add it to the list of things to be deleted
700 when Cleanup() is called. Return the filename."""
701 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
702 os.close(fd)
703 OPTIONS.tempfiles.append(fn)
704 return fn
705
706
Doug Zongkereef39442009-04-02 12:14:19 -0700707def Cleanup():
708 for i in OPTIONS.tempfiles:
709 if os.path.isdir(i):
710 shutil.rmtree(i)
711 else:
712 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700713
714
715class PasswordManager(object):
716 def __init__(self):
717 self.editor = os.getenv("EDITOR", None)
718 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
719
720 def GetPasswords(self, items):
721 """Get passwords corresponding to each string in 'items',
722 returning a dict. (The dict may have keys in addition to the
723 values in 'items'.)
724
725 Uses the passwords in $ANDROID_PW_FILE if available, letting the
726 user edit that file to add more needed passwords. If no editor is
727 available, or $ANDROID_PW_FILE isn't define, prompts the user
728 interactively in the ordinary way.
729 """
730
731 current = self.ReadFile()
732
733 first = True
734 while True:
735 missing = []
736 for i in items:
737 if i not in current or not current[i]:
738 missing.append(i)
739 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700740 if not missing:
741 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700742
743 for i in missing:
744 current[i] = ""
745
746 if not first:
747 print "key file %s still missing some passwords." % (self.pwfile,)
748 answer = raw_input("try to edit again? [y]> ").strip()
749 if answer and answer[0] not in 'yY':
750 raise RuntimeError("key passwords unavailable")
751 first = False
752
753 current = self.UpdateAndReadFile(current)
754
Dan Albert8b72aef2015-03-23 19:13:21 -0700755 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700756 """Prompt the user to enter a value (password) for each key in
757 'current' whose value is fales. Returns a new dict with all the
758 values.
759 """
760 result = {}
761 for k, v in sorted(current.iteritems()):
762 if v:
763 result[k] = v
764 else:
765 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700766 result[k] = getpass.getpass(
767 "Enter password for %s key> " % k).strip()
768 if result[k]:
769 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700770 return result
771
772 def UpdateAndReadFile(self, current):
773 if not self.editor or not self.pwfile:
774 return self.PromptResult(current)
775
776 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700777 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700778 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
779 f.write("# (Additional spaces are harmless.)\n\n")
780
781 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700782 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
783 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700784 f.write("[[[ %s ]]] %s\n" % (v, k))
785 if not v and first_line is None:
786 # position cursor on first line with no password.
787 first_line = i + 4
788 f.close()
789
790 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
791 _, _ = p.communicate()
792
793 return self.ReadFile()
794
795 def ReadFile(self):
796 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700797 if self.pwfile is None:
798 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700799 try:
800 f = open(self.pwfile, "r")
801 for line in f:
802 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700803 if not line or line[0] == '#':
804 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700805 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
806 if not m:
807 print "failed to parse password file: ", line
808 else:
809 result[m.group(2)] = m.group(1)
810 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 if e.errno != errno.ENOENT:
813 print "error reading password file: ", str(e)
814 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700815
816
Dan Albert8e0178d2015-01-27 15:53:15 -0800817def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
818 compress_type=None):
819 import datetime
820
821 # http://b/18015246
822 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
823 # for files larger than 2GiB. We can work around this by adjusting their
824 # limit. Note that `zipfile.writestr()` will not work for strings larger than
825 # 2GiB. The Python interpreter sometimes rejects strings that large (though
826 # it isn't clear to me exactly what circumstances cause this).
827 # `zipfile.write()` must be used directly to work around this.
828 #
829 # This mess can be avoided if we port to python3.
830 saved_zip64_limit = zipfile.ZIP64_LIMIT
831 zipfile.ZIP64_LIMIT = (1 << 32) - 1
832
833 if compress_type is None:
834 compress_type = zip_file.compression
835 if arcname is None:
836 arcname = filename
837
838 saved_stat = os.stat(filename)
839
840 try:
841 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
842 # file to be zipped and reset it when we're done.
843 os.chmod(filename, perms)
844
845 # Use a fixed timestamp so the output is repeatable.
846 epoch = datetime.datetime.fromtimestamp(0)
847 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
848 os.utime(filename, (timestamp, timestamp))
849
850 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
851 finally:
852 os.chmod(filename, saved_stat.st_mode)
853 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
854 zipfile.ZIP64_LIMIT = saved_zip64_limit
855
856
Dan Albert8b72aef2015-03-23 19:13:21 -0700857def ZipWriteStr(zip_file, filename, data, perms=0o644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700858 # use a fixed timestamp so the output is repeatable.
859 zinfo = zipfile.ZipInfo(filename=filename,
860 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800861 if compression is None:
Dan Albert8b72aef2015-03-23 19:13:21 -0700862 zinfo.compress_type = zip_file.compression
Geremy Condra36bd3652014-02-06 19:45:10 -0800863 else:
864 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700865 zinfo.external_attr = perms << 16
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 zip_file.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700867
868
869class DeviceSpecificParams(object):
870 module = None
871 def __init__(self, **kwargs):
872 """Keyword arguments to the constructor become attributes of this
873 object, which is passed to all functions in the device-specific
874 module."""
875 for k, v in kwargs.iteritems():
876 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800877 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700878
879 if self.module is None:
880 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700881 if not path:
882 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700883 try:
884 if os.path.isdir(path):
885 info = imp.find_module("releasetools", [path])
886 else:
887 d, f = os.path.split(path)
888 b, x = os.path.splitext(f)
889 if x == ".py":
890 f = b
891 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800892 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700893 self.module = imp.load_module("device_specific", *info)
894 except ImportError:
895 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700896
897 def _DoCall(self, function_name, *args, **kwargs):
898 """Call the named function in the device-specific module, passing
899 the given args and kwargs. The first argument to the call will be
900 the DeviceSpecific object itself. If there is no module, or the
901 module does not define the function, return the value of the
902 'default' kwarg (which itself defaults to None)."""
903 if self.module is None or not hasattr(self.module, function_name):
904 return kwargs.get("default", None)
905 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
906
907 def FullOTA_Assertions(self):
908 """Called after emitting the block of assertions at the top of a
909 full OTA package. Implementations can add whatever additional
910 assertions they like."""
911 return self._DoCall("FullOTA_Assertions")
912
Doug Zongkere5ff5902012-01-17 10:55:37 -0800913 def FullOTA_InstallBegin(self):
914 """Called at the start of full OTA installation."""
915 return self._DoCall("FullOTA_InstallBegin")
916
Doug Zongker05d3dea2009-06-22 11:32:31 -0700917 def FullOTA_InstallEnd(self):
918 """Called at the end of full OTA installation; typically this is
919 used to install the image for the device's baseband processor."""
920 return self._DoCall("FullOTA_InstallEnd")
921
922 def IncrementalOTA_Assertions(self):
923 """Called after emitting the block of assertions at the top of an
924 incremental OTA package. Implementations can add whatever
925 additional assertions they like."""
926 return self._DoCall("IncrementalOTA_Assertions")
927
Doug Zongkere5ff5902012-01-17 10:55:37 -0800928 def IncrementalOTA_VerifyBegin(self):
929 """Called at the start of the verification phase of incremental
930 OTA installation; additional checks can be placed here to abort
931 the script before any changes are made."""
932 return self._DoCall("IncrementalOTA_VerifyBegin")
933
Doug Zongker05d3dea2009-06-22 11:32:31 -0700934 def IncrementalOTA_VerifyEnd(self):
935 """Called at the end of the verification phase of incremental OTA
936 installation; additional checks can be placed here to abort the
937 script before any changes are made."""
938 return self._DoCall("IncrementalOTA_VerifyEnd")
939
Doug Zongkere5ff5902012-01-17 10:55:37 -0800940 def IncrementalOTA_InstallBegin(self):
941 """Called at the start of incremental OTA installation (after
942 verification is complete)."""
943 return self._DoCall("IncrementalOTA_InstallBegin")
944
Doug Zongker05d3dea2009-06-22 11:32:31 -0700945 def IncrementalOTA_InstallEnd(self):
946 """Called at the end of incremental OTA installation; typically
947 this is used to install the image for the device's baseband
948 processor."""
949 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700950
951class File(object):
952 def __init__(self, name, data):
953 self.name = name
954 self.data = data
955 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800956 self.sha1 = sha1(data).hexdigest()
957
958 @classmethod
959 def FromLocalFile(cls, name, diskname):
960 f = open(diskname, "rb")
961 data = f.read()
962 f.close()
963 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700964
965 def WriteToTemp(self):
966 t = tempfile.NamedTemporaryFile()
967 t.write(self.data)
968 t.flush()
969 return t
970
Geremy Condra36bd3652014-02-06 19:45:10 -0800971 def AddToZip(self, z, compression=None):
972 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700973
974DIFF_PROGRAM_BY_EXT = {
975 ".gz" : "imgdiff",
976 ".zip" : ["imgdiff", "-z"],
977 ".jar" : ["imgdiff", "-z"],
978 ".apk" : ["imgdiff", "-z"],
979 ".img" : "imgdiff",
980 }
981
982class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700983 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700984 self.tf = tf
985 self.sf = sf
986 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700987 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700988
989 def ComputePatch(self):
990 """Compute the patch (as a string of data) needed to turn sf into
991 tf. Returns the same tuple as GetPatch()."""
992
993 tf = self.tf
994 sf = self.sf
995
Doug Zongker24cd2802012-08-14 16:36:15 -0700996 if self.diff_program:
997 diff_program = self.diff_program
998 else:
999 ext = os.path.splitext(tf.name)[1]
1000 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001001
1002 ttemp = tf.WriteToTemp()
1003 stemp = sf.WriteToTemp()
1004
1005 ext = os.path.splitext(tf.name)[1]
1006
1007 try:
1008 ptemp = tempfile.NamedTemporaryFile()
1009 if isinstance(diff_program, list):
1010 cmd = copy.copy(diff_program)
1011 else:
1012 cmd = [diff_program]
1013 cmd.append(stemp.name)
1014 cmd.append(ttemp.name)
1015 cmd.append(ptemp.name)
1016 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001017 err = []
1018 def run():
1019 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001020 if e:
1021 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001022 th = threading.Thread(target=run)
1023 th.start()
1024 th.join(timeout=300) # 5 mins
1025 if th.is_alive():
1026 print "WARNING: diff command timed out"
1027 p.terminate()
1028 th.join(5)
1029 if th.is_alive():
1030 p.kill()
1031 th.join()
1032
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001033 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001034 print "WARNING: failure running %s:\n%s\n" % (
1035 diff_program, "".join(err))
1036 self.patch = None
1037 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001038 diff = ptemp.read()
1039 finally:
1040 ptemp.close()
1041 stemp.close()
1042 ttemp.close()
1043
1044 self.patch = diff
1045 return self.tf, self.sf, self.patch
1046
1047
1048 def GetPatch(self):
1049 """Return a tuple (target_file, source_file, patch_data).
1050 patch_data may be None if ComputePatch hasn't been called, or if
1051 computing the patch failed."""
1052 return self.tf, self.sf, self.patch
1053
1054
1055def ComputeDifferences(diffs):
1056 """Call ComputePatch on all the Difference objects in 'diffs'."""
1057 print len(diffs), "diffs to compute"
1058
1059 # Do the largest files first, to try and reduce the long-pole effect.
1060 by_size = [(i.tf.size, i) for i in diffs]
1061 by_size.sort(reverse=True)
1062 by_size = [i[1] for i in by_size]
1063
1064 lock = threading.Lock()
1065 diff_iter = iter(by_size) # accessed under lock
1066
1067 def worker():
1068 try:
1069 lock.acquire()
1070 for d in diff_iter:
1071 lock.release()
1072 start = time.time()
1073 d.ComputePatch()
1074 dur = time.time() - start
1075 lock.acquire()
1076
1077 tf, sf, patch = d.GetPatch()
1078 if sf.name == tf.name:
1079 name = tf.name
1080 else:
1081 name = "%s (%s)" % (tf.name, sf.name)
1082 if patch is None:
1083 print "patching failed! %s" % (name,)
1084 else:
1085 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1086 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1087 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001088 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001089 print e
1090 raise
1091
1092 # start worker threads; wait for them all to finish.
1093 threads = [threading.Thread(target=worker)
1094 for i in range(OPTIONS.worker_threads)]
1095 for th in threads:
1096 th.start()
1097 while threads:
1098 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001099
1100
Dan Albert8b72aef2015-03-23 19:13:21 -07001101class BlockDifference(object):
1102 def __init__(self, partition, tgt, src=None, check_first_block=False,
1103 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001104 self.tgt = tgt
1105 self.src = src
1106 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001107 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001108
Tao Baodd2a5892015-03-12 12:32:37 -07001109 if version is None:
1110 version = 1
1111 if OPTIONS.info_dict:
1112 version = max(
1113 int(i) for i in
1114 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1115 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001116
1117 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001118 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001119 tmpdir = tempfile.mkdtemp()
1120 OPTIONS.tempfiles.append(tmpdir)
1121 self.path = os.path.join(tmpdir, partition)
1122 b.Compute(self.path)
1123
1124 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1125
1126 def WriteScript(self, script, output_zip, progress=None):
1127 if not self.src:
1128 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001129 script.Print("Patching %s image unconditionally..." % (self.partition,))
1130 else:
1131 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001132
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if progress:
1134 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001135 self._WriteUpdate(script, output_zip)
1136
1137 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001138 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001139 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001140 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001141 else:
Michael Runge910b0052015-02-11 19:28:08 -08001142 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001143 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1144 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001145 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001146 '"%s.new.dat", "%s.patch.dat")) then') % (
1147 self.device, self.src.care_map.to_string_raw(),
1148 self.src.TotalSha1(),
1149 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001150 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001151 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1152 self.device, self.src.care_map.to_string_raw(),
1153 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001154 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001155 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001156
Tao Baodd2a5892015-03-12 12:32:37 -07001157 # When generating incrementals for the system and vendor partitions,
1158 # explicitly check the first block (which contains the superblock) of
1159 # the partition to see if it's what we expect. If this check fails,
1160 # give an explicit log message about the partition having been
1161 # remounted R/W (the most likely explanation) and the need to flash to
1162 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001163 if self.check_first_block:
1164 self._CheckFirstBlock(script)
1165
Tao Baodd2a5892015-03-12 12:32:37 -07001166 # Abort the OTA update. Note that the incremental OTA cannot be applied
1167 # even if it may match the checksum of the target partition.
1168 # a) If version < 3, operations like move and erase will make changes
1169 # unconditionally and damage the partition.
1170 # b) If version >= 3, it won't even reach here.
1171 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1172 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001173
1174 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001175 ZipWrite(output_zip,
1176 '{}.transfer.list'.format(self.path),
1177 '{}.transfer.list'.format(self.partition))
1178 ZipWrite(output_zip,
1179 '{}.new.dat'.format(self.path),
1180 '{}.new.dat'.format(self.partition))
1181 ZipWrite(output_zip,
1182 '{}.patch.dat'.format(self.path),
1183 '{}.patch.dat'.format(self.partition),
1184 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001185
Dan Albert8e0178d2015-01-27 15:53:15 -08001186 call = ('block_image_update("{device}", '
1187 'package_extract_file("{partition}.transfer.list"), '
1188 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1189 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001190 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001191
Dan Albert8b72aef2015-03-23 19:13:21 -07001192 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001193 data = source.ReadRangeSet(ranges)
1194 ctx = sha1()
1195
1196 for p in data:
1197 ctx.update(p)
1198
1199 return ctx.hexdigest()
1200
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001201 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001202 r = rangelib.RangeSet((0, 1))
1203 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001204
1205 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1206 'abort("%s has been remounted R/W; '
1207 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001208 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001209 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001210
1211DataImage = blockimgdiff.DataImage
1212
1213
Doug Zongker96a57e72010-09-26 14:57:41 -07001214# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001215PARTITION_TYPES = {
1216 "yaffs2": "MTD",
1217 "mtd": "MTD",
1218 "ext4": "EMMC",
1219 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001220 "f2fs": "EMMC",
1221 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001222}
Doug Zongker96a57e72010-09-26 14:57:41 -07001223
1224def GetTypeAndDevice(mount_point, info):
1225 fstab = info["fstab"]
1226 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001227 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1228 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001229 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001230 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001231
1232
1233def ParseCertificate(data):
1234 """Parse a PEM-format certificate."""
1235 cert = []
1236 save = False
1237 for line in data.split("\n"):
1238 if "--END CERTIFICATE--" in line:
1239 break
1240 if save:
1241 cert.append(line)
1242 if "--BEGIN CERTIFICATE--" in line:
1243 save = True
1244 cert = "".join(cert).decode('base64')
1245 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001246
Doug Zongker412c02f2014-02-13 10:58:24 -08001247def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1248 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001249 """Generate a binary patch that creates the recovery image starting
1250 with the boot image. (Most of the space in these images is just the
1251 kernel, which is identical for the two, so the resulting patch
1252 should be efficient.) Add it to the output zip, along with a shell
1253 script that is run from init.rc on first boot to actually do the
1254 patching and install the new recovery image.
1255
1256 recovery_img and boot_img should be File objects for the
1257 corresponding images. info should be the dictionary returned by
1258 common.LoadInfoDict() on the input target_files.
1259 """
1260
Doug Zongker412c02f2014-02-13 10:58:24 -08001261 if info_dict is None:
1262 info_dict = OPTIONS.info_dict
1263
Doug Zongkerc9253822014-02-04 12:17:58 -08001264 diff_program = ["imgdiff"]
1265 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1266 if os.path.exists(path):
1267 diff_program.append("-b")
1268 diff_program.append(path)
1269 bonus_args = "-b /system/etc/recovery-resource.dat"
1270 else:
1271 bonus_args = ""
1272
1273 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1274 _, _, patch = d.ComputePatch()
1275 output_sink("recovery-from-boot.p", patch)
1276
Dan Albertebb19aa2015-03-27 19:11:53 -07001277 try:
1278 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1279 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1280 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001281 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001282
1283 sh = """#!/system/bin/sh
1284if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1285 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"
1286else
1287 log -t recovery "Recovery image already installed"
1288fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001289""" % {'boot_size': boot_img.size,
1290 'boot_sha1': boot_img.sha1,
1291 'recovery_size': recovery_img.size,
1292 'recovery_sha1': recovery_img.sha1,
1293 'boot_type': boot_type,
1294 'boot_device': boot_device,
1295 'recovery_type': recovery_type,
1296 'recovery_device': recovery_device,
1297 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001298
1299 # The install script location moved from /system/etc to /system/bin
1300 # in the L release. Parse the init.rc file to find out where the
1301 # target-files expects it to be, and put it there.
1302 sh_location = "etc/install-recovery.sh"
1303 try:
1304 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1305 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001306 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001307 if m:
1308 sh_location = m.group(1)
1309 print "putting script in", sh_location
1310 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001311 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001312 print "failed to read init.rc: %s" % (e,)
1313
1314 output_sink(sh_location, sh)